From 138e922fcace49dba23fc180d2a72d1a343b4601 Mon Sep 17 00:00:00 2001 From: "anna.shakhova" Date: Fri, 13 Dec 2024 15:58:18 +0100 Subject: [PATCH 1/4] ContextMenu: fix menu height on async render (T1258881) --- .../__internal/ui/collection/hierarchical.ts | 4 +- .../ui/context_menu/m_context_menu.ts | 13 +++- .../m_hierarchical_collection_widget.ts | 2 +- .../contextMenu.async.tests.js | 61 +++++++++++++++++++ 4 files changed, 74 insertions(+), 6 deletions(-) diff --git a/packages/devextreme/js/__internal/ui/collection/hierarchical.ts b/packages/devextreme/js/__internal/ui/collection/hierarchical.ts index 5de8fd5a0b06..79f01834fb87 100644 --- a/packages/devextreme/js/__internal/ui/collection/hierarchical.ts +++ b/packages/devextreme/js/__internal/ui/collection/hierarchical.ts @@ -3,7 +3,7 @@ import type { ItemLike } from '@js/ui/collection/ui.collection_widget.base'; import type { HierarchicalCollectionWidgetOptions } from '@js/ui/hierarchical_collection/ui.hierarchical_collection_widget'; import HierarchicalCollectionWidget from '@js/ui/hierarchical_collection/ui.hierarchical_collection_widget'; -import CollectionWidgetEdit from './edit'; +import AsyncCollectionWidget from './async'; export interface TypedCollectionWidgetOptions< // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -22,7 +22,7 @@ declare class Hierarchical< TItem extends ItemLike = any, // eslint-disable-next-line @typescript-eslint/no-explicit-any TKey = any, -> extends CollectionWidgetEdit { +> extends AsyncCollectionWidget { // eslint-disable-next-line @typescript-eslint/no-explicit-any _dataAdapter?: any; diff --git a/packages/devextreme/js/__internal/ui/context_menu/m_context_menu.ts b/packages/devextreme/js/__internal/ui/context_menu/m_context_menu.ts index 30c11c7f6906..4ee19298ecf9 100644 --- a/packages/devextreme/js/__internal/ui/context_menu/m_context_menu.ts +++ b/packages/devextreme/js/__internal/ui/context_menu/m_context_menu.ts @@ -531,6 +531,7 @@ class ContextMenu extends MenuBase { _renderSubmenuItems(node, $itemFrame: dxElementWrapper): void { this._renderItems(this._getChildNodes(node), $itemFrame); + this._planPostRenderActions(); const $submenu = $itemFrame.children(`.${DX_SUBMENU_CLASS}`); @@ -704,7 +705,7 @@ class ContextMenu extends MenuBase { return availableHeight - SUBMENU_PADDING; } - _dimensionChanged() { + _dimensionChanged(showAnimation = true) { if (!this._shownSubmenus) { return; } @@ -715,8 +716,10 @@ class ContextMenu extends MenuBase { this._setSubMenuHeight($submenu, $item, true); this._scrollToElement($item); - const submenuPosition = this._getSubmenuPosition($item); - animationPosition.setup($submenu, submenuPosition); + if (showAnimation) { + const submenuPosition = this._getSubmenuPosition($item); + animationPosition.setup($submenu, submenuPosition); + } }); } @@ -1130,6 +1133,10 @@ class ContextMenu extends MenuBase { hide(): Promise { return this.toggle(false); } + + _postProcessRenderItems() { + this._dimensionChanged(false); + } } // @ts-expect-error diff --git a/packages/devextreme/js/__internal/ui/hierarchical_collection/m_hierarchical_collection_widget.ts b/packages/devextreme/js/__internal/ui/hierarchical_collection/m_hierarchical_collection_widget.ts index 6deddad62d6d..9d894e8ddfb5 100644 --- a/packages/devextreme/js/__internal/ui/hierarchical_collection/m_hierarchical_collection_widget.ts +++ b/packages/devextreme/js/__internal/ui/hierarchical_collection/m_hierarchical_collection_widget.ts @@ -7,7 +7,7 @@ import { extend } from '@js/core/utils/extend'; import { getImageContainer } from '@js/core/utils/icon'; import { each } from '@js/core/utils/iterator'; import { isFunction, isObject } from '@js/core/utils/type'; -import CollectionWidget from '@js/ui/collection/ui.collection_widget.edit'; +import CollectionWidget from '@js/ui/collection/ui.collection_widget.async'; import HierarchicalDataAdapter from './m_data_adapter'; diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/contextMenu.async.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/contextMenu.async.tests.js index a31b99b7b069..b88e73d65101 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/contextMenu.async.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/contextMenu.async.tests.js @@ -4,6 +4,13 @@ import ContextMenu from 'ui/context_menu'; import 'ui/button'; import 'generic_light.css!'; +const DX_SUBMENU_CLASS = 'dx-submenu'; +const DX_CONTEXT_MENU_ITEMS_CONTAINER_CLASS = 'dx-menu-items-container'; +const DX_SCROLLABLE_CONTENT_CLASS = 'dx-scrollable-container'; +const DX_MENU_ITEM_CLASS = 'dx-menu-item'; + +const ITEMS_CONTAINER_PADDING = 1; + QUnit.testStart(() => { const markup = '\
\ @@ -60,5 +67,59 @@ QUnit.module('Context menu', () => { assert.strictEqual(instance.option('items').length, 2, 'items.length'); }); + + QUnit.test('Context menu should have correct height on async render (T1258881)', function(assert) { + const done = assert.async(); + + const menuTargetSelector = '#menuTarget'; + const items = [{ + text: 'root', + items: [ + { text: 'sub 1', template: 'myTemplate' }, + { text: 'sub 2', template: 'myTemplate' }, + { text: 'sub 3', template: 'myTemplate' }, + { text: 'sub 4', template: 'myTemplate' }, + { text: 'sub 5', template: 'myTemplate' }, + { text: 'sub 6', template: 'myTemplate' }, + { text: 'sub 7', template: 'myTemplate' }, + ] + }]; + + const instance = new ContextMenu($('#simpleMenu'), { + target: menuTargetSelector, + items, + templatesRenderAsynchronously: true, + itemTemplate: 'myTemplate', + integrationOptions: { + templates: { + myTemplate: { + render({ model, container, onRendered }) { + setTimeout(() => { + container.append($('
').text(model.text)); + onRendered(); + }); + } + } + } + }, + }); + + $(menuTargetSelector).trigger($.Event('dxcontextmenu')); + + const $itemsContainer = instance.itemsContainer(); + const $rootItem = $itemsContainer.find(`.${DX_MENU_ITEM_CLASS}`).eq(0); + $($itemsContainer).trigger($.Event('dxhoverstart', { target: $rootItem.get(0) })); + + instance.option('onShown', (e) => { + const $submenus = $(`.${DX_SUBMENU_CLASS}`); + const $nestedSubmenu = $submenus.eq(1); + const $nestedSubmenuItemsContainer = $nestedSubmenu.find(`.${DX_CONTEXT_MENU_ITEMS_CONTAINER_CLASS}`); + const $scrollableContainer = $nestedSubmenu.find(`.${DX_SCROLLABLE_CONTENT_CLASS}`); + + assert.roughEqual($nestedSubmenuItemsContainer.outerHeight(), $scrollableContainer.outerHeight(), .1); + done(); + }); + + }); }); From 53c01f940c075233f2eecce335052e9c6d1c02b1 Mon Sep 17 00:00:00 2001 From: "anna.shakhova" Date: Wed, 18 Dec 2024 14:29:09 +0100 Subject: [PATCH 2/4] ContextMenu: change submenu height and visibility after render (T1258881) --- .../js/__internal/ui/collection/async.ts | 2 +- .../ui/collection/m_collection_widget.async.ts | 4 ++-- .../ui/context_menu/m_context_menu.ts | 18 ++++++++++++++---- .../contextMenu.async.tests.js | 15 ++++++++++++--- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/packages/devextreme/js/__internal/ui/collection/async.ts b/packages/devextreme/js/__internal/ui/collection/async.ts index bb3b8a54dcd0..6dd2a3ab0a33 100644 --- a/packages/devextreme/js/__internal/ui/collection/async.ts +++ b/packages/devextreme/js/__internal/ui/collection/async.ts @@ -16,7 +16,7 @@ declare class Async< args: { itemData: unknown } ): () => void; - _planPostRenderActions(): void; + _planPostRenderActions(...args: unknown[]): void; } // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/packages/devextreme/js/__internal/ui/collection/m_collection_widget.async.ts b/packages/devextreme/js/__internal/ui/collection/m_collection_widget.async.ts index 7cb167866381..7660b180bc8d 100644 --- a/packages/devextreme/js/__internal/ui/collection/m_collection_widget.async.ts +++ b/packages/devextreme/js/__internal/ui/collection/m_collection_widget.async.ts @@ -36,10 +36,10 @@ const AsyncCollectionWidget = CollectionWidgetEdit.inherit({ _postProcessRenderItems: noop, - _planPostRenderActions() { + _planPostRenderActions(...args: unknown[]) { const d = Deferred(); when.apply(this, this._asyncTemplateItems).done(() => { - this._postProcessRenderItems(); + this._postProcessRenderItems(...args); d.resolve(); }); return d.promise(); diff --git a/packages/devextreme/js/__internal/ui/context_menu/m_context_menu.ts b/packages/devextreme/js/__internal/ui/context_menu/m_context_menu.ts index 4ee19298ecf9..50f4a13ffcbd 100644 --- a/packages/devextreme/js/__internal/ui/context_menu/m_context_menu.ts +++ b/packages/devextreme/js/__internal/ui/context_menu/m_context_menu.ts @@ -531,7 +531,6 @@ class ContextMenu extends MenuBase { _renderSubmenuItems(node, $itemFrame: dxElementWrapper): void { this._renderItems(this._getChildNodes(node), $itemFrame); - this._planPostRenderActions(); const $submenu = $itemFrame.children(`.${DX_SUBMENU_CLASS}`); @@ -744,8 +743,19 @@ class ContextMenu extends MenuBase { $submenu = $item.children(`.${DX_SUBMENU_CLASS}`); } + this._planPostRenderActions($submenu); + } + + _setSubmenuVisible($submenu?: dxElementWrapper) { + if (!$submenu) { + return; + } + + const $item = $submenu?.closest(`.${DX_MENU_ITEM_CLASS}`); + this._setSubMenuHeight($submenu, $item, true); - if (!this._isSubmenuVisible($submenu)) { + + if (!this._isSubmenuVisible($submenu) && $item) { this._drawSubmenu($item); } } @@ -1134,8 +1144,8 @@ class ContextMenu extends MenuBase { return this.toggle(false); } - _postProcessRenderItems() { - this._dimensionChanged(false); + _postProcessRenderItems($submenu?: dxElementWrapper) { + this._setSubmenuVisible($submenu); } } diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/contextMenu.async.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/contextMenu.async.tests.js index b88e73d65101..63c65c9387ea 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/contextMenu.async.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/contextMenu.async.tests.js @@ -1,5 +1,6 @@ import $ from 'jquery'; import ContextMenu from 'ui/context_menu'; +import devices from '__internal/core/m_devices'; import 'ui/button'; import 'generic_light.css!'; @@ -9,7 +10,13 @@ const DX_CONTEXT_MENU_ITEMS_CONTAINER_CLASS = 'dx-menu-items-container'; const DX_SCROLLABLE_CONTENT_CLASS = 'dx-scrollable-container'; const DX_MENU_ITEM_CLASS = 'dx-menu-item'; -const ITEMS_CONTAINER_PADDING = 1; +const isDeviceDesktop = function(assert) { + if(devices.real().deviceType !== 'desktop') { + assert.ok(true, 'skip this test on mobile devices'); + return false; + } + return true; +}; QUnit.testStart(() => { const markup = '\ @@ -108,7 +115,10 @@ QUnit.module('Context menu', () => { const $itemsContainer = instance.itemsContainer(); const $rootItem = $itemsContainer.find(`.${DX_MENU_ITEM_CLASS}`).eq(0); - $($itemsContainer).trigger($.Event('dxhoverstart', { target: $rootItem.get(0) })); + + const eventName = isDeviceDesktop(assert) ? 'dxhoverstart' : 'dxclick'; + + $($itemsContainer).trigger($.Event(eventName, { target: $rootItem.get(0) })); instance.option('onShown', (e) => { const $submenus = $(`.${DX_SUBMENU_CLASS}`); @@ -119,7 +129,6 @@ QUnit.module('Context menu', () => { assert.roughEqual($nestedSubmenuItemsContainer.outerHeight(), $scrollableContainer.outerHeight(), .1); done(); }); - }); }); From 5da647296477bd85b8e14b419b60393b8d8c462a Mon Sep 17 00:00:00 2001 From: "anna.shakhova" Date: Wed, 18 Dec 2024 16:38:17 +0100 Subject: [PATCH 3/4] ContextMenu: fix test (T1258881) --- .../DevExpress.ui.widgets/contextMenu.async.tests.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/contextMenu.async.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/contextMenu.async.tests.js index 63c65c9387ea..aff5557629e8 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/contextMenu.async.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/contextMenu.async.tests.js @@ -10,14 +10,6 @@ const DX_CONTEXT_MENU_ITEMS_CONTAINER_CLASS = 'dx-menu-items-container'; const DX_SCROLLABLE_CONTENT_CLASS = 'dx-scrollable-container'; const DX_MENU_ITEM_CLASS = 'dx-menu-item'; -const isDeviceDesktop = function(assert) { - if(devices.real().deviceType !== 'desktop') { - assert.ok(true, 'skip this test on mobile devices'); - return false; - } - return true; -}; - QUnit.testStart(() => { const markup = '\
\ @@ -116,7 +108,7 @@ QUnit.module('Context menu', () => { const $itemsContainer = instance.itemsContainer(); const $rootItem = $itemsContainer.find(`.${DX_MENU_ITEM_CLASS}`).eq(0); - const eventName = isDeviceDesktop(assert) ? 'dxhoverstart' : 'dxclick'; + const eventName = devices.real().deviceType === 'desktop' ? 'dxhoverstart' : 'dxclick'; $($itemsContainer).trigger($.Event(eventName, { target: $rootItem.get(0) })); From 6da28db8bb4e6e374b6b1110ce907df43f493e3b Mon Sep 17 00:00:00 2001 From: "anna.shakhova" Date: Thu, 19 Dec 2024 08:50:42 +0100 Subject: [PATCH 4/4] ContextMenu: remove artefact (T1258881) --- .../js/__internal/ui/context_menu/m_context_menu.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/devextreme/js/__internal/ui/context_menu/m_context_menu.ts b/packages/devextreme/js/__internal/ui/context_menu/m_context_menu.ts index 50f4a13ffcbd..e05648df2371 100644 --- a/packages/devextreme/js/__internal/ui/context_menu/m_context_menu.ts +++ b/packages/devextreme/js/__internal/ui/context_menu/m_context_menu.ts @@ -704,7 +704,7 @@ class ContextMenu extends MenuBase { return availableHeight - SUBMENU_PADDING; } - _dimensionChanged(showAnimation = true) { + _dimensionChanged() { if (!this._shownSubmenus) { return; } @@ -715,10 +715,8 @@ class ContextMenu extends MenuBase { this._setSubMenuHeight($submenu, $item, true); this._scrollToElement($item); - if (showAnimation) { - const submenuPosition = this._getSubmenuPosition($item); - animationPosition.setup($submenu, submenuPosition); - } + const submenuPosition = this._getSubmenuPosition($item); + animationPosition.setup($submenu, submenuPosition); }); }