Skip to content

Commit 1c692a3

Browse files
committed
Menu: fix focus logic on submenu hiding (T1304251)
1 parent 4d079f5 commit 1c692a3

File tree

2 files changed

+127
-66
lines changed

2 files changed

+127
-66
lines changed

packages/devextreme/js/__internal/ui/menu/menu.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -856,23 +856,27 @@ class Menu extends MenuBase<MenuProperties> {
856856

857857
this._actions.onSubmenuHiding?.(eventArgs);
858858

859+
if (eventArgs.cancel) {
860+
return;
861+
}
862+
859863
const { focusedElement } = this.option();
860864
const { focusedElement: submenuFocusedElement } = submenu.option();
861-
862865
const isVisibleSubmenuHiding = this._visibleSubmenu === submenu;
863-
const isFocusedElementHiding = focusedElement === submenuFocusedElement;
864866

865-
if (isVisibleSubmenuHiding && isFocusedElementHiding) {
866-
this.option('focusedElement', getPublicElement($menuAnchorItem));
867-
}
867+
if (isVisibleSubmenuHiding) {
868+
const isFocusedElementHiding = !!submenuFocusedElement
869+
&& focusedElement === submenuFocusedElement;
868870

869-
if (!eventArgs.cancel) {
870-
if (isVisibleSubmenuHiding) {
871-
this._visibleSubmenu = null;
871+
if (isFocusedElementHiding) {
872+
this.option('focusedElement', getPublicElement($menuAnchorItem));
872873
}
873-
$border.hide();
874-
$menuAnchorItem.removeClass(DX_MENU_ITEM_EXPANDED_CLASS);
874+
875+
this._visibleSubmenu = null;
875876
}
877+
878+
$border.hide();
879+
$menuAnchorItem.removeClass(DX_MENU_ITEM_EXPANDED_CLASS);
876880
}
877881

878882
_submenuOnHiddenHandler(

packages/devextreme/testing/tests/DevExpress.ui.widgets/menu.tests.js

Lines changed: 113 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -2393,6 +2393,119 @@ QUnit.module('Menu tests', {
23932393
submenu = getSubMenuInstance($rootMenuItem);
23942394
assert.ok(submenu.option('visible'), 'submenu shown');
23952395
});
2396+
2397+
QUnit.test('focusedElement should be set to main menu item after hiding submenu (T1291581)', function(assert) {
2398+
const menu = $('#menu').dxMenu({
2399+
focusStateEnabled: true,
2400+
items: [
2401+
{
2402+
text: 'Item 1',
2403+
items: [
2404+
{ text: 'Item 11', items: [ { text: 'Item 111' }, { text: 'Item 112' }, { text: 'Item 113' } ] },
2405+
{ text: 'Item 12' }
2406+
],
2407+
},
2408+
]
2409+
}).dxMenu('instance');
2410+
const keyboard = keyboardMock(menu.itemsContainer());
2411+
2412+
keyboard.press('enter')
2413+
.press('down')
2414+
.press('down');
2415+
2416+
assert.strictEqual($(menu.option('focusedElement')).text(), 'Item 12', 'focusedElement is submenu item');
2417+
2418+
keyboard.press('enter');
2419+
2420+
const mainMenuItemText = $(menu.itemElements()[0]).text();
2421+
2422+
assert.strictEqual($(menu.option('focusedElement')).text(), mainMenuItemText, 'focusedElement is main menu item');
2423+
});
2424+
2425+
QUnit.test('focusedElement should be set to main menu item after hiding nested submenu (T1291581)', function(assert) {
2426+
const menu = $('#menu').dxMenu({
2427+
focusStateEnabled: true,
2428+
items: [
2429+
{
2430+
text: 'Item 1',
2431+
items: [
2432+
{ text: 'Item 11', items: [ { text: 'Item 111' }, { text: 'Item 112' }, { text: 'Item 113' } ] },
2433+
{ text: 'Item 12' }
2434+
],
2435+
},
2436+
]
2437+
}).dxMenu('instance');
2438+
const keyboard = keyboardMock(menu.itemsContainer());
2439+
2440+
keyboard.press('enter')
2441+
.press('down')
2442+
.press('enter')
2443+
.press('right')
2444+
.press('down');
2445+
2446+
assert.strictEqual($(menu.option('focusedElement')).text(), 'Item 112', 'focusedElement is submenu item');
2447+
2448+
keyboard.press('enter');
2449+
2450+
const mainMenuItemText = $(menu.itemElements()[0]).text();
2451+
2452+
assert.strictEqual($(menu.option('focusedElement')).text(), mainMenuItemText, 'focusedElement is main menu item');
2453+
});
2454+
2455+
QUnit.test('focusedElement should not be moved if submenu hiding was cancelled (T1291581)', function(assert) {
2456+
const menu = $('#menu').dxMenu({
2457+
focusStateEnabled: true,
2458+
items: [
2459+
{
2460+
text: 'Item 1',
2461+
items: [
2462+
{ text: 'Item 11', items: [ { text: 'Item 111' }, { text: 'Item 112' }, { text: 'Item 113' } ] },
2463+
{ text: 'Item 12' }
2464+
],
2465+
},
2466+
],
2467+
onSubmenuHiding: function(e) {
2468+
e.cancel = true;
2469+
}
2470+
}).dxMenu('instance');
2471+
const keyboard = keyboardMock(menu.itemsContainer());
2472+
2473+
keyboard.press('enter')
2474+
.press('down')
2475+
.press('down');
2476+
2477+
const { focusedElement: initialFocusedElement } = menu.option();
2478+
2479+
keyboard.press('enter');
2480+
2481+
assert.strictEqual($(menu.option('focusedElement')).text(), 'Item 12', 'focusedElement is submenu item');
2482+
assert.strictEqual(menu.option('focusedElement'), initialFocusedElement, 'focusedElement not changed');
2483+
});
2484+
2485+
QUnit.test('focusedElement should not be set to main menu item after hiding nested submenu if no item was focused (T1304251)', function(assert) {
2486+
const menu = createMenu({
2487+
items: [{ text: 'Item 1', items: [{ text: 'Item 11' }, { text: 'Item 12' }, { text: 'Item 13' }] }],
2488+
showFirstSubmenuMode: { name: 'onHover', delay: 0 },
2489+
hideSubmenuOnMouseLeave: true
2490+
});
2491+
const $rootMenuItem = $(menu.element).find(`.${DX_MENU_ITEM_CLASS}`);
2492+
2493+
$(menu.element).trigger($.Event('dxhoverstart', { target: $rootMenuItem.get(0) }));
2494+
$($rootMenuItem).trigger('dxpointermove');
2495+
this.clock.tick(0);
2496+
2497+
const submenu = getSubMenuInstance($rootMenuItem);
2498+
const $item = $(submenu._overlay.content()).find(`.${DX_MENU_ITEM_CLASS}`);
2499+
2500+
$(menu.element).trigger($.Event('dxhoverstart', { target: $item.get(1) }));
2501+
$(menu.element).trigger($.Event('dxhoverend', { target: $item.get(1) }));
2502+
$(menu.element).trigger($.Event('dxhoverstart', { target: window }));
2503+
$($(submenu._overlay.content()).find('.dx-submenu')).trigger('dxhoverend');
2504+
this.clock.tick(0);
2505+
2506+
assert.strictEqual($rootMenuItem.eq(0).hasClass(DX_STATE_FOCUSED_CLASS), false, 'root menu item has not focused class');
2507+
assert.strictEqual(menu.instance.option('focusedElement'), null, 'menu focusedElement is null');
2508+
});
23962509
});
23972510

23982511
QUnit.module('keyboard navigation', {
@@ -2841,62 +2954,6 @@ QUnit.module('keyboard navigation', {
28412954

28422955
assert.equal($(this.instance._visibleSubmenu.option('focusedElement')).text(), 'Item 113');
28432956
});
2844-
2845-
QUnit.test('focusedElement should be set to main menu item after hiding submenu', function(assert) {
2846-
this.instance.option({
2847-
orientation: 'horizontal',
2848-
items: [
2849-
{
2850-
text: 'Item 1',
2851-
items: [
2852-
{ text: 'Item 11', items: [ { text: 'Item 111' }, { text: 'Item 112' }, { text: 'Item 113' } ] },
2853-
{ text: 'Item 12' }
2854-
],
2855-
},
2856-
]
2857-
});
2858-
2859-
this.keyboard.press('enter')
2860-
.press('down')
2861-
.press('down');
2862-
2863-
assert.strictEqual($(this.instance.option('focusedElement')).text(), 'Item 12', 'focusedElement is submenu item');
2864-
2865-
this.keyboard.press('enter');
2866-
2867-
const mainMenuItemText = $(this.instance.itemElements()[0]).text();
2868-
2869-
assert.strictEqual($(this.instance.option('focusedElement')).text(), mainMenuItemText, 'focusedElement is main menu item');
2870-
});
2871-
2872-
QUnit.test('focusedElement should be set to main menu item after hiding nested submenu', function(assert) {
2873-
this.instance.option({
2874-
orientation: 'horizontal',
2875-
items: [
2876-
{
2877-
text: 'Item 1',
2878-
items: [
2879-
{ text: 'Item 11', items: [ { text: 'Item 111' }, { text: 'Item 112' }, { text: 'Item 113' } ] },
2880-
{ text: 'Item 12' }
2881-
],
2882-
},
2883-
]
2884-
});
2885-
2886-
this.keyboard.press('enter')
2887-
.press('down')
2888-
.press('enter')
2889-
.press('right')
2890-
.press('down');
2891-
2892-
assert.strictEqual($(this.instance.option('focusedElement')).text(), 'Item 112', 'focusedElement is submenu item');
2893-
2894-
this.keyboard.press('enter');
2895-
2896-
const mainMenuItemText = $(this.instance.itemElements()[0]).text();
2897-
2898-
assert.strictEqual($(this.instance.option('focusedElement')).text(), mainMenuItemText, 'focusedElement is main menu item');
2899-
});
29002957
});
29012958

29022959
QUnit.module('Menu with templates', {

0 commit comments

Comments
 (0)