diff --git a/packages/devextreme/js/__internal/ui/splitter/splitter.ts b/packages/devextreme/js/__internal/ui/splitter/splitter.ts index 2ca1c018760d..57c7e0aa6829 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; @@ -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,6 +239,18 @@ class Splitter extends CollectionWidgetLiveUpdate { return isElementVisible($(this.element())[0]); } + _captureInitialCollapsedItemSizes(items: InternalSplitterItem[]): void { + items.forEach((item) => { + if ( + item._initialSizeBeforeCollapse === undefined + && item.collapsed === true + && isDefined(item.size) + ) { + item._initialSizeBeforeCollapse = item.size; + } + }); + } + _resizeHandler(): void { if (this._shouldRecalculateLayout && this._isAttached() && this._isVisible()) { this._layout = this._getDefaultLayoutBasedOnSize(); @@ -249,6 +265,8 @@ class Splitter extends CollectionWidgetLiveUpdate { _renderItems(items: Item[]): void { super._renderItems(items); + this._captureInitialCollapsedItemSizes(items); + this._updateResizeHandlesResizableState(); this._updateResizeHandlesCollapsibleState(); @@ -662,6 +680,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 +924,40 @@ class Splitter extends CollectionWidgetLiveUpdate { return 0; } + _getTargetPaneSize( + paneCache: PaneCache | undefined, + direction: CollapseExpandDirection | undefined, + collapsedSize: number, + item: InternalSplitterItem, + itemIndex: number, + ): number { + if (paneCache && paneCache.direction === direction) { + return paneCache.size - collapsedSize; + } + + if (!isDefined(item._initialSizeBeforeCollapse)) { + return direction === CollapseExpandDirection.Previous + ? this._calculateExpandToLeftSize(itemIndex - 1) + : this._calculateExpandToRightSize(itemIndex + 1); + } + + const sizeRatio = convertSizeToRatio( + 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); + } + + 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 752166f71b94..b72109b720ba 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,21 @@ 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 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 +1010,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'] }, @@ -1361,6 +1363,98 @@ 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) { + + 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 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: [ + { size: '150px', collapsed: true, collapsible: true, }, + { }, + ] + }); + + this.instance.option('items[0].collapsed', false); + + const item0 = this.instance.option('items[0]'); + + assert.strictEqual(item0._initialSizeBeforeCollapse, undefined, 'items[0]._initialSizeBeforeCollapse'); + + }); + }); QUnit.module('Pane visibility', moduleConfig, () => {