Skip to content

Commit afcb9bc

Browse files
committed
divider resize
1 parent d707beb commit afcb9bc

4 files changed

Lines changed: 211 additions & 1 deletion

File tree

packages/core-ui/js/gui_components/import-export-controls.js

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ class ImportExportControls {
2424
constructor() {
2525
this.previewRowLimit = 10;
2626
this.textEditMode = 'preview';
27+
this.defaultOptionsPanelWidthPx = 272;
28+
this.minOptionsPanelWidthPx = 180;
29+
this.maxOptionsPanelWidthPx = 520;
30+
this.currentOptionsPanelWidthPx = null;
31+
this._activeSplitDrag = null;
2732
}
2833

2934
addHTMLtoGui(parentelement) {
@@ -293,6 +298,7 @@ class ImportExportControls {
293298

294299
const edit_area = document.querySelector('div.edit-area');
295300
const optionsparent = document.querySelector('div.options-parent');
301+
const splitter = document.querySelector('div.options-preview-splitter');
296302
const text_area = document.getElementById('markdown');
297303

298304
edit_area.style.width = '100%';
@@ -308,6 +314,9 @@ class ImportExportControls {
308314
console.log('undefined panel type for ' + type);
309315
edit_area.style.display = 'block';
310316
optionsparent.style.display = 'none';
317+
if (splitter) {
318+
splitter.style.display = 'none';
319+
}
311320
text_area.style.width = '100%';
312321
text_area.style.height = '100%';
313322
return;
@@ -318,7 +327,7 @@ class ImportExportControls {
318327
text_area.style.width = '100%';
319328
text_area.style.height = '100%';
320329

321-
optionsparent.style.width = '17em';
330+
this._setOptionsPanelWidth(optionsparent, this._getInitialOptionsPanelWidthPx());
322331
optionsparent.style.height = '100%';
323332

324333
optionsparent.innerHTML = '';
@@ -337,6 +346,7 @@ class ImportExportControls {
337346
}
338347

339348
optionsparent.style.display = 'block';
349+
this._configureOptionsPreviewSplitter(edit_area, optionsparent, splitter, text_area);
340350
}
341351

342352
setOptionsApplyDirtyState(optionsparent, isDirty) {
@@ -476,6 +486,109 @@ class ImportExportControls {
476486
}
477487
importButton.disabled = this.isPreviewTextMode();
478488
}
489+
490+
_configureOptionsPreviewSplitter(editArea, optionsParent, splitter, textArea) {
491+
if (!splitter || !editArea || !optionsParent || !textArea) {
492+
return;
493+
}
494+
495+
splitter.style.display = 'block';
496+
textArea.style.flex = '1 1 auto';
497+
498+
if (splitter.dataset.splitterInitialised === 'true') {
499+
return;
500+
}
501+
502+
splitter.dataset.splitterInitialised = 'true';
503+
splitter.addEventListener('pointerdown', (event) => this._beginSplitterDrag(event, editArea, optionsParent));
504+
}
505+
506+
_beginSplitterDrag(event, editArea, optionsParent) {
507+
if (!event || event.button > 0) {
508+
return;
509+
}
510+
511+
const pointerId = event.pointerId;
512+
const startX = event.clientX;
513+
const startWidth = this._readOptionsPanelWidthPx(optionsParent);
514+
515+
this._activeSplitDrag = {
516+
pointerId,
517+
startX,
518+
startWidth,
519+
editArea,
520+
optionsParent,
521+
};
522+
523+
event.preventDefault();
524+
document.body.classList.add('is-resizing-split');
525+
526+
const onMove = (moveEvent) => this._handleSplitterDragMove(moveEvent);
527+
const onEnd = (endEvent) => this._endSplitterDrag(endEvent, onMove, onEnd);
528+
document.addEventListener('pointermove', onMove);
529+
document.addEventListener('pointerup', onEnd);
530+
document.addEventListener('pointercancel', onEnd);
531+
}
532+
533+
_handleSplitterDragMove(event) {
534+
const dragState = this._activeSplitDrag;
535+
if (!dragState || event.pointerId !== dragState.pointerId) {
536+
return;
537+
}
538+
539+
event.preventDefault();
540+
const deltaX = event.clientX - dragState.startX;
541+
const requestedWidth = dragState.startWidth + deltaX;
542+
const boundedWidth = this._clampOptionsPanelWidth(requestedWidth, dragState.editArea);
543+
this._setOptionsPanelWidth(dragState.optionsParent, boundedWidth);
544+
}
545+
546+
_endSplitterDrag(event, onMove, onEnd) {
547+
if (!this._activeSplitDrag) {
548+
return;
549+
}
550+
if (event && event.pointerId !== this._activeSplitDrag.pointerId) {
551+
return;
552+
}
553+
554+
document.removeEventListener('pointermove', onMove);
555+
document.removeEventListener('pointerup', onEnd);
556+
document.removeEventListener('pointercancel', onEnd);
557+
document.body.classList.remove('is-resizing-split');
558+
this._activeSplitDrag = null;
559+
}
560+
561+
_setOptionsPanelWidth(optionsParent, widthPx) {
562+
const safeWidth = Math.round(widthPx);
563+
optionsParent.style.width = `${safeWidth}px`;
564+
optionsParent.style.minWidth = `${safeWidth}px`;
565+
optionsParent.style.maxWidth = `${safeWidth}px`;
566+
optionsParent.style.flex = '0 0 auto';
567+
this.currentOptionsPanelWidthPx = safeWidth;
568+
}
569+
570+
_readOptionsPanelWidthPx(optionsParent) {
571+
const parsed = Number.parseFloat(optionsParent?.style?.width || '');
572+
if (Number.isFinite(parsed)) {
573+
return parsed;
574+
}
575+
return this._getInitialOptionsPanelWidthPx();
576+
}
577+
578+
_getInitialOptionsPanelWidthPx() {
579+
if (Number.isFinite(this.currentOptionsPanelWidthPx)) {
580+
return this.currentOptionsPanelWidthPx;
581+
}
582+
return this.defaultOptionsPanelWidthPx;
583+
}
584+
585+
_clampOptionsPanelWidth(widthPx, editArea) {
586+
const editWidth = editArea?.getBoundingClientRect?.().width || 0;
587+
const maxByContainer =
588+
editWidth > 0 ? Math.max(this.minOptionsPanelWidthPx, editWidth - 220) : this.maxOptionsPanelWidthPx;
589+
const maxAllowed = Math.min(this.maxOptionsPanelWidthPx, maxByContainer);
590+
return Math.min(Math.max(widthPx, this.minOptionsPanelWidthPx), maxAllowed);
591+
}
479592
}
480593

481594
export { ImportExportControls };

packages/core-ui/js/gui_components/tabbed-text-control.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class TabbedTextControl {
5050
5151
<div class="edit-area">
5252
<div class="options-parent" style="display: none"></div>
53+
<div class="options-preview-splitter" style="display: none" role="separator" aria-orientation="vertical" aria-label="Resize options panel"></div>
5354
<div id="markdown" style="height: 30%; width:100%;">
5455
<textarea class="textrepresentation" name="Markdown" id="markdownarea"></textarea>
5556
</div>

styles.css

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,59 @@ button:disabled{
208208
resize: vertical;
209209
}
210210

211+
.edit-area{
212+
display: flex;
213+
align-items: stretch;
214+
width: 100%;
215+
min-height: 14rem;
216+
}
217+
218+
.options-parent{
219+
overflow: auto;
220+
}
221+
222+
.options-preview-splitter{
223+
width: 8px;
224+
cursor: col-resize;
225+
background: #f5f8fa;
226+
border-left: 1px solid #d7e2e8;
227+
border-right: 1px solid #d7e2e8;
228+
flex: 0 0 8px;
229+
opacity: 0.7;
230+
transition: background-color 120ms ease, opacity 120ms ease;
231+
}
232+
233+
.options-preview-splitter:hover{
234+
background: #ebf2f6;
235+
opacity: 0.95;
236+
}
237+
238+
body.is-resizing-split{
239+
user-select: none;
240+
cursor: col-resize;
241+
}
242+
243+
#markdown{
244+
flex: 1 1 auto;
245+
min-width: 0;
246+
}
247+
248+
@media (max-width: 640px){
249+
.edit-area{
250+
flex-direction: column;
251+
}
252+
253+
.options-preview-splitter{
254+
display: none !important;
255+
}
256+
257+
.options-parent{
258+
width: 100% !important;
259+
min-width: 0 !important;
260+
max-width: 100% !important;
261+
}
262+
}
263+
211264
.testDataDefn{
212265
height: 10em;
213266
width:95%;

tests/utils/import-export-controls-mode.test.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ describe('ImportExportControls file reading and visibility', () => {
169169
<div id="markdown"></div>
170170
<div class="edit-area"></div>
171171
<div class="options-parent"></div>
172+
<div class="options-preview-splitter"></div>
172173
<div id="importExportRoot"></div>
173174
<button id="copyTextButton">Copy</button>
174175
</body></html>`);
@@ -335,4 +336,46 @@ describe('ImportExportControls file reading and visibility', () => {
335336
expect(controls.exporter.setOptionsForType).toHaveBeenCalledWith('csv', { delimiter: '|' });
336337
expect(applyButton.disabled).toBe(true);
337338
});
339+
340+
test('shows splitter when options panel is active and hides for unsupported format', () => {
341+
const splitter = document.querySelector('div.options-preview-splitter');
342+
const editArea = document.querySelector('div.edit-area');
343+
const textAreaContainer = document.getElementById('markdown');
344+
345+
controls.setOptionsViewForFormatType();
346+
347+
expect(splitter.style.display).toBe('block');
348+
expect(editArea.style.display).toBe('flex');
349+
expect(textAreaContainer.style.flex).toBe('1 1 auto');
350+
351+
document.querySelector('li.active-type a').setAttribute('data-type', 'unknown');
352+
controls.setOptionsViewForFormatType();
353+
354+
expect(splitter.style.display).toBe('none');
355+
});
356+
357+
test('splitter drag resizes options panel width and clamps to min/max', () => {
358+
controls.setOptionsViewForFormatType();
359+
const splitter = document.querySelector('div.options-preview-splitter');
360+
const optionsParent = document.querySelector('div.options-parent');
361+
const editArea = document.querySelector('div.edit-area');
362+
363+
editArea.getBoundingClientRect = () => ({ width: 1000 });
364+
365+
splitter.dispatchEvent(
366+
new dom.window.MouseEvent('pointerdown', { bubbles: true, button: 0, clientX: 300, pointerId: 1 })
367+
);
368+
document.dispatchEvent(new dom.window.MouseEvent('pointermove', { bubbles: true, clientX: 380, pointerId: 1 }));
369+
expect(Number.parseFloat(optionsParent.style.width)).toBeGreaterThan(272);
370+
expect(document.body.classList.contains('is-resizing-split')).toBe(true);
371+
372+
document.dispatchEvent(new dom.window.MouseEvent('pointermove', { bubbles: true, clientX: -500, pointerId: 1 }));
373+
expect(Number.parseFloat(optionsParent.style.width)).toBe(controls.minOptionsPanelWidthPx);
374+
375+
document.dispatchEvent(new dom.window.MouseEvent('pointermove', { bubbles: true, clientX: 9999, pointerId: 1 }));
376+
expect(Number.parseFloat(optionsParent.style.width)).toBe(controls.maxOptionsPanelWidthPx);
377+
378+
document.dispatchEvent(new dom.window.MouseEvent('pointerup', { bubbles: true, pointerId: 1 }));
379+
expect(document.body.classList.contains('is-resizing-split')).toBe(false);
380+
});
338381
});

0 commit comments

Comments
 (0)