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/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/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 30c11c7f6906..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 @@ -741,8 +741,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); } } @@ -1130,6 +1141,10 @@ class ContextMenu extends MenuBase { hide(): Promise { return this.toggle(false); } + + _postProcessRenderItems($submenu?: dxElementWrapper) { + this._setSubmenuVisible($submenu); + } } // @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..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 @@ -1,9 +1,15 @@ import $ from 'jquery'; import ContextMenu from 'ui/context_menu'; +import devices from '__internal/core/m_devices'; 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'; + QUnit.testStart(() => { const markup = '\
\ @@ -60,5 +66,61 @@ 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); + + const eventName = devices.real().deviceType === 'desktop' ? 'dxhoverstart' : 'dxclick'; + + $($itemsContainer).trigger($.Event(eventName, { 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(); + }); + }); });