diff --git a/packages/devextreme/js/__internal/ui/splitter/splitter.ts b/packages/devextreme/js/__internal/ui/splitter/splitter.ts index 2ca1c018760d..7136c0dfa39a 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; @@ -304,12 +308,24 @@ class Splitter extends CollectionWidgetLiveUpdate { return index === findLastIndexOfVisibleItem(items); } + _captureInitialCollapsedItemSize(item: InternalSplitterItem): void { + if ( + item._initialSizeBeforeCollapse === undefined + && item.collapsed === true + && isDefined(item.size) + ) { + item._initialSizeBeforeCollapse = item.size; + } + } + _renderItem( index: number, itemData: Item, $container: dxElementWrapper, $itemToReplace: dxElementWrapper, ): dxElementWrapper { + this._captureInitialCollapsedItemSize(itemData); + const $itemFrame = super._renderItem(index, itemData, $container, $itemToReplace); const itemElement = $itemFrame.get(0) as HTMLElement; @@ -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,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 +990,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..0b34ceb0f18a 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,83 @@ 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 even size was updated in real time ${expectedSize} + 20`, function(assert) { + + this.reinit({ + width: 600, + height: 600, + items: [ + { size: `${expectedSize}px`, collapsed: true, collapsible: true, }, + { } + ] + }); + + this.instance.option('items[0].size', `${expectedSize + 20}px`); + this.instance.option('items[0].collapsed', false); + + assert.strictEqual(this.instance.option('items[0].size'), expectedSize + 20, '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.module('Pane visibility', moduleConfig, () => {