From 68209b1cf21b629dd55070be547591a913ed6b3a Mon Sep 17 00:00:00 2001 From: dmlvr Date: Thu, 19 Mar 2026 10:11:25 +0200 Subject: [PATCH 1/3] add test --- .../DevExpress.ui.widgets/splitter.tests.js | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/splitter.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/splitter.tests.js index 752166f71b94..1a45dc70b41b 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/splitter.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/splitter.tests.js @@ -1361,6 +1361,31 @@ QUnit.module('Pane sizing', moduleConfig, () => { resizeObserverSingleton.unobserve.restore(); }); + + + QUnit.test('initial collapsed pane should restore size from configuration', function(assert) { + + const EXPECTED_SIZE = 200; + + this.reinit({ + width: 600, + height: 600, + items: [ + { text: 'first pane' }, + { + text: 'second pane', + size: `${EXPECTED_SIZE}px`, + collapsed: true, + collapsible: true, + } + ] + }); + + this.instance.option('items[1].collapsed', false); + + assert.strictEqual(this.instance.option('items[1].size'), EXPECTED_SIZE, 'items[1].size'); + + }); }); QUnit.module('Pane visibility', moduleConfig, () => { From 302df62c5c176d7655fa5d8c800a69c27ce3a080 Mon Sep 17 00:00:00 2001 From: dmlvr Date: Thu, 19 Mar 2026 15:47:18 +0200 Subject: [PATCH 2/3] fix --- .../js/__internal/ui/splitter/splitter.ts | 82 +++++++++++--- .../DevExpress.ui.widgets/splitter.tests.js | 107 +++++++++++++++--- 2 files changed, 159 insertions(+), 30 deletions(-) diff --git a/packages/devextreme/js/__internal/ui/splitter/splitter.ts b/packages/devextreme/js/__internal/ui/splitter/splitter.ts index 2ca1c018760d..98241170d004 100644 --- a/packages/devextreme/js/__internal/ui/splitter/splitter.ts +++ b/packages/devextreme/js/__internal/ui/splitter/splitter.ts @@ -69,13 +69,13 @@ import { type RenderQueueItem, } from './utils/types'; -const SPLITTER_CLASS = 'dx-splitter'; -const SPLITTER_ITEM_CLASS = 'dx-splitter-item'; -const SPLITTER_ITEM_HIDDEN_CONTENT_CLASS = 'dx-splitter-item-hidden-content'; +export const SPLITTER_CLASS = 'dx-splitter'; +export const SPLITTER_ITEM_CLASS = 'dx-splitter-item'; +export const SPLITTER_ITEM_HIDDEN_CONTENT_CLASS = 'dx-splitter-item-hidden-content'; +export const INVISIBLE_STATE_CLASS = 'dx-state-invisible'; const SPLITTER_ITEM_DATA_KEY = 'dxSplitterItemData'; const HORIZONTAL_ORIENTATION_CLASS = 'dx-splitter-horizontal'; const VERTICAL_ORIENTATION_CLASS = 'dx-splitter-vertical'; -const INVISIBLE_STATE_CLASS = 'dx-state-invisible'; const DEFAULT_RESIZE_HANDLE_SIZE = 8; @@ -235,6 +235,20 @@ class Splitter extends CollectionWidgetLiveUpdate { return isElementVisible($(this.element())[0]); } + _captureInitialCollapsedItemSizes(items: Item[]): void { + items.forEach((item) => { + if ( + // @ts-expect-error + item._initialSizeBeforeCollapse === undefined + && item.collapsed === true + && isDefined(item.size) + ) { + // @ts-expect-error + item._initialSizeBeforeCollapse = item.size; + } + }); + } + _resizeHandler(): void { if (this._shouldRecalculateLayout && this._isAttached() && this._isVisible()) { this._layout = this._getDefaultLayoutBasedOnSize(); @@ -249,6 +263,8 @@ class Splitter extends CollectionWidgetLiveUpdate { _renderItems(items: Item[]): void { super._renderItems(items); + this._captureInitialCollapsedItemSizes(items); + this._updateResizeHandlesResizableState(); this._updateResizeHandlesCollapsibleState(); @@ -662,6 +678,12 @@ class Splitter extends CollectionWidgetLiveUpdate { ): void { switch (property) { case 'size': + + if (item.collapsed) { + // @ts-expect-error + item._initialSizeBeforeCollapse = value; + } + this._layout = this._getDefaultLayoutBasedOnSize(item); this._applyStylesFromLayout(this.getLayout()); @@ -900,6 +922,42 @@ class Splitter extends CollectionWidgetLiveUpdate { return 0; } + _getTargetPaneSize( + paneCache: PaneCache | undefined, + direction: CollapseExpandDirection | undefined, + collapsedSize: number, + item: Item, + itemIndex: number, + ): number { + if (paneCache && paneCache.direction === direction) { + return paneCache.size - collapsedSize; + } + + // @ts-expect-error + if (!isDefined(item._initialSizeBeforeCollapse)) { + return direction === CollapseExpandDirection.Previous + ? this._calculateExpandToLeftSize(itemIndex - 1) + : this._calculateExpandToRightSize(itemIndex + 1); + } + + const sizeRatio = convertSizeToRatio( + // @ts-expect-error + item._initialSizeBeforeCollapse, + getElementSize($(this.element()), this.option().orientation), + this._getResizeHandlesSize(), + ); + + if (!isDefined(sizeRatio)) { + return direction === CollapseExpandDirection.Previous + ? this._calculateExpandToLeftSize(itemIndex - 1) + : this._calculateExpandToRightSize(itemIndex + 1); + } + + // @ts-expect-error + item._initialSizeBeforeCollapse = undefined; + return sizeRatio - collapsedSize; + } + _getCollapseDelta( item: Item, newCollapsedState: boolean | undefined, @@ -934,15 +992,13 @@ class Splitter extends CollectionWidgetLiveUpdate { const paneCache = panesCacheSize[itemIndex]; panesCacheSize[itemIndex] = undefined; - let targetPaneSize = 0; - - if (paneCache && paneCache.direction === direction) { - targetPaneSize = paneCache.size - collapsedSize; - } else { - targetPaneSize = direction === CollapseExpandDirection.Previous - ? this._calculateExpandToLeftSize(itemIndex - 1) - : this._calculateExpandToRightSize(itemIndex + 1); - } + const targetPaneSize = this._getTargetPaneSize( + paneCache, + direction, + collapsedSize, + item, + itemIndex, + ); let adjustedSize = compareNumbersWithPrecision(targetPaneSize, minSize) < 0 ? minSize diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/splitter.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/splitter.tests.js index 1a45dc70b41b..89b1b6736abc 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/splitter.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/splitter.tests.js @@ -9,19 +9,25 @@ import { createEvent } from 'common/core/events/utils/index'; import { name as DOUBLE_CLICK_EVENT } from 'common/core/events/double_click'; import { name as CLICK_EVENT } from 'common/core/events/click'; import resizeObserverSingleton from 'core/resize_observer'; +import { + SPLITTER_CLASS, + SPLITTER_ITEM_CLASS, + SPLITTER_ITEM_HIDDEN_CONTENT_CLASS, + INVISIBLE_STATE_CLASS as STATE_INVISIBLE_CLASS +} from '__internal/ui/splitter/splitter'; import 'fluent_blue_light.css!'; -const SPLITTER_ITEM_CLASS = 'dx-splitter-item'; -const SPLITTER_ITEM_HIDDEN_CONTENT_CLASS = 'dx-splitter-item-hidden-content'; +// const SPLITTER_CLASS = 'dx-splitter'; +// const SPLITTER_ITEM_CLASS = 'dx-splitter-item'; +// const SPLITTER_ITEM_HIDDEN_CONTENT_CLASS = 'dx-splitter-item-hidden-content'; +// const STATE_INVISIBLE_CLASS = 'dx-state-invisible'; const RESIZE_HANDLE_CLASS = 'dx-resize-handle'; const RESIZE_HANDLE_ICON_CLASS = 'dx-resize-handle-icon'; const RESIZE_HANDLE_COLLAPSE_PREV_PANE_CLASS = 'dx-resize-handle-collapse-prev-pane'; const RESIZE_HANDLE_COLLAPSE_NEXT_PANE_CLASS = 'dx-resize-handle-collapse-next-pane'; -const STATE_INVISIBLE_CLASS = 'dx-state-invisible'; const STATE_ACTIVE_CLASS = 'dx-state-active'; const STATE_FOCUSED_CLASS = 'dx-state-focused'; -const SPLITTER_CLASS = 'dx-splitter'; QUnit.testStart(() => { const markup = @@ -1008,14 +1014,14 @@ QUnit.module('Pane sizing', moduleConfig, () => { ] }, { - items: [{ collapsed: true, size: '150px', collapsible: true }, { collapsible: true }, { collapsed: true, collapsible: true }, { }], + items: [{ collapsed: true, collapsible: true }, { collapsible: true }, { collapsed: true, collapsible: true }, { }], scenarios: [ { newCollapsedValue: false, paneIndex: 0, expectedLayout: ['25', '25', '0', '50'] }, { newCollapsedValue: false, paneIndex: 2, expectedLayout: ['25', '25', '25', '25'] }, ] }, { - items: [{ collapsed: false, collapsible: true }, { collapsed: true, collapsible: true }, { collapsible: true }, { collapsed: true, size: '150px', collapsible: true }], + items: [{ collapsed: false, collapsible: true }, { collapsed: true, collapsible: true }, { collapsible: true }, { collapsed: true, collapsible: true }], scenarios: [ { newCollapsedValue: false, paneIndex: 3, expectedLayout: ['50', '0', '25', '25'] }, { newCollapsedValue: false, paneIndex: 1, expectedLayout: ['50', '12.5', '12.5', '25'] }, @@ -1362,30 +1368,97 @@ QUnit.module('Pane sizing', moduleConfig, () => { resizeObserverSingleton.unobserve.restore(); }); + [150, 200, 250].forEach(expectedSize => { + QUnit.test(`initial collapsed pane should restore size from configuration (left pane) size ${expectedSize}`, function(assert) { - QUnit.test('initial collapsed pane should restore size from configuration', function(assert) { + this.reinit({ + width: 600, + height: 600, + items: [ + { size: `${expectedSize}px`, collapsed: true, collapsible: true, }, + { } + ] + }); + + this.instance.option('items[0].collapsed', false); + + assert.strictEqual(this.instance.option('items[0].size'), expectedSize, 'items[0].size'); + + }); + + QUnit.test(`initial collapsed pane should restore size from configuration (right pane) size ${expectedSize}`, function(assert) { + + this.reinit({ + width: 600, + height: 600, + items: [ + { }, + { size: `${expectedSize}px`, collapsed: true, collapsible: true, } + ] + }); + + this.instance.option('items[1].collapsed', false); + + assert.strictEqual(this.instance.option('items[1].size'), expectedSize, 'items[1].size'); + + }); + + QUnit.test(`initial collapsed pane should restore size from configuration (right and left pane) size ${expectedSize}`, function(assert) { + + this.reinit({ + width: 600, + height: 600, + items: [ + { size: `${expectedSize}px`, collapsed: true, collapsible: true, }, + { }, + { size: `${expectedSize}px`, collapsed: true, collapsible: true, } + ] + }); + + this.instance.option('items[0].collapsed', false); + this.instance.option('items[2].collapsed', false); + + assert.strictEqual(this.instance.option('items[0].size'), expectedSize, 'items[0].size'); + assert.strictEqual(this.instance.option('items[2].size'), expectedSize, 'items[2].size'); + + }); + + QUnit.test(`_initialSizeBeforeCollapse should create when item has collapsed:true and size = ${expectedSize}`, function(assert) { + this.reinit({ + width: 600, + height: 600, + items: [ + { size: `${expectedSize}px`, collapsed: true, collapsible: true, }, + { }, + ] + }); - const EXPECTED_SIZE = 200; + const items = this.instance.option('items'); + assert.strictEqual(items[0]._initialSizeBeforeCollapse, `${expectedSize}px`, 'items[0]._initialSizeBeforeCollapse'); + assert.strictEqual(items[1]._initialSizeBeforeCollapse, undefined, 'items[1]._initialSizeBeforeCollapse'); + + }); + }); + + QUnit.test('_initialSizeBeforeCollapse should be undefined after first expand', function(assert) { this.reinit({ width: 600, height: 600, items: [ - { text: 'first pane' }, - { - text: 'second pane', - size: `${EXPECTED_SIZE}px`, - collapsed: true, - collapsible: true, - } + { size: '150px', collapsed: true, collapsible: true, }, + { }, ] }); - this.instance.option('items[1].collapsed', false); + this.instance.option('items[0].collapsed', false); + + const item0 = this.instance.option('items[0]'); - assert.strictEqual(this.instance.option('items[1].size'), EXPECTED_SIZE, 'items[1].size'); + assert.strictEqual(item0._initialSizeBeforeCollapse, undefined, 'items[0]._initialSizeBeforeCollapse'); }); + }); QUnit.module('Pane visibility', moduleConfig, () => { From e2f74e1c03e44620baa1a913660e90befc3073b5 Mon Sep 17 00:00:00 2001 From: dmlvr Date: Thu, 19 Mar 2026 16:14:18 +0200 Subject: [PATCH 3/3] fix by copilot review --- .../js/__internal/ui/splitter/splitter.ts | 16 ++++++++-------- .../DevExpress.ui.widgets/splitter.tests.js | 4 ---- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/packages/devextreme/js/__internal/ui/splitter/splitter.ts b/packages/devextreme/js/__internal/ui/splitter/splitter.ts index 98241170d004..57c7e0aa6829 100644 --- a/packages/devextreme/js/__internal/ui/splitter/splitter.ts +++ b/packages/devextreme/js/__internal/ui/splitter/splitter.ts @@ -93,6 +93,10 @@ const ORIENTATION: Record = { vertical: 'vertical', }; +type InternalSplitterItem = Item & { + _initialSizeBeforeCollapse?: Item['size']; +}; + // eslint-disable-next-line @typescript-eslint/no-explicit-any type ItemLike = string | Item | any; @@ -235,15 +239,13 @@ class Splitter extends CollectionWidgetLiveUpdate { return isElementVisible($(this.element())[0]); } - _captureInitialCollapsedItemSizes(items: Item[]): void { + _captureInitialCollapsedItemSizes(items: InternalSplitterItem[]): void { items.forEach((item) => { if ( - // @ts-expect-error item._initialSizeBeforeCollapse === undefined && item.collapsed === true && isDefined(item.size) ) { - // @ts-expect-error item._initialSizeBeforeCollapse = item.size; } }); @@ -926,14 +928,13 @@ class Splitter extends CollectionWidgetLiveUpdate { paneCache: PaneCache | undefined, direction: CollapseExpandDirection | undefined, collapsedSize: number, - item: Item, + item: InternalSplitterItem, itemIndex: number, ): number { if (paneCache && paneCache.direction === direction) { return paneCache.size - collapsedSize; } - // @ts-expect-error if (!isDefined(item._initialSizeBeforeCollapse)) { return direction === CollapseExpandDirection.Previous ? this._calculateExpandToLeftSize(itemIndex - 1) @@ -941,20 +942,19 @@ class Splitter extends CollectionWidgetLiveUpdate { } const sizeRatio = convertSizeToRatio( - // @ts-expect-error item._initialSizeBeforeCollapse, getElementSize($(this.element()), this.option().orientation), this._getResizeHandlesSize(), ); + item._initialSizeBeforeCollapse = undefined; + if (!isDefined(sizeRatio)) { return direction === CollapseExpandDirection.Previous ? this._calculateExpandToLeftSize(itemIndex - 1) : this._calculateExpandToRightSize(itemIndex + 1); } - // @ts-expect-error - item._initialSizeBeforeCollapse = undefined; return sizeRatio - collapsedSize; } diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/splitter.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/splitter.tests.js index 89b1b6736abc..b72109b720ba 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/splitter.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/splitter.tests.js @@ -18,10 +18,6 @@ import { import 'fluent_blue_light.css!'; -// const SPLITTER_CLASS = 'dx-splitter'; -// const SPLITTER_ITEM_CLASS = 'dx-splitter-item'; -// const SPLITTER_ITEM_HIDDEN_CONTENT_CLASS = 'dx-splitter-item-hidden-content'; -// const STATE_INVISIBLE_CLASS = 'dx-state-invisible'; const RESIZE_HANDLE_CLASS = 'dx-resize-handle'; const RESIZE_HANDLE_ICON_CLASS = 'dx-resize-handle-icon'; const RESIZE_HANDLE_COLLAPSE_PREV_PANE_CLASS = 'dx-resize-handle-collapse-prev-pane';