Skip to content

Commit 1b9e22a

Browse files
committed
Menu: fix focus logic on submenu hiding (T1304251) (24_2)
1 parent ce65168 commit 1b9e22a

2 files changed

Lines changed: 134 additions & 67 deletions

File tree

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

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -692,23 +692,29 @@ class Menu extends MenuBase {
692692

693693
this._actions.onSubmenuHiding(eventArgs);
694694

695+
if (eventArgs.cancel) {
696+
return;
697+
}
698+
695699
const { focusedElement } = this.option();
696-
const { focusedElement: submenuFocusedElement } = submenu.option();
700+
const submenuContainerElement = $(eventArgs.submenuContainer).get(0);
701+
const focusedDomElement = $(focusedElement).get(0);
702+
const isFocusedElementInsideSubmenu = focusedDomElement && submenuContainerElement
703+
? submenuContainerElement.contains(focusedDomElement)
704+
: false;
705+
706+
if (isFocusedElementInsideSubmenu) {
707+
this.option('focusedElement', getPublicElement($menuAnchorItem));
708+
}
697709

698710
const isVisibleSubmenuHiding = this._visibleSubmenu === submenu;
699-
const isFocusedElementHiding = focusedElement === submenuFocusedElement;
700711

701-
if (isVisibleSubmenuHiding && isFocusedElementHiding) {
702-
this.option('focusedElement', $menuAnchorItem);
712+
if (isVisibleSubmenuHiding) {
713+
this._visibleSubmenu = null;
703714
}
704715

705-
if (!eventArgs.cancel) {
706-
if (isVisibleSubmenuHiding) {
707-
this._visibleSubmenu = null;
708-
}
709-
$border.hide();
710-
$menuAnchorItem.removeClass(DX_MENU_ITEM_EXPANDED_CLASS);
711-
}
716+
$border.hide();
717+
$menuAnchorItem.removeClass(DX_MENU_ITEM_EXPANDED_CLASS);
712718
}
713719

714720
_submenuOnHiddenHandler($menuAnchorItem, submenu, { rootItem }) {

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

Lines changed: 117 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -2502,6 +2502,123 @@ QUnit.module('Menu tests', {
25022502
submenu = getSubMenuInstance($rootMenuItem);
25032503
assert.ok(submenu.option('visible'), 'submenu shown');
25042504
});
2505+
2506+
QUnit.test('focusedElement should be set to main menu item after hiding submenu (T1291581)', function(assert) {
2507+
const menu = $('#menu').dxMenu({
2508+
orientation: 'horizontal',
2509+
focusStateEnabled: true,
2510+
items: [
2511+
{
2512+
text: 'Item 1',
2513+
items: [
2514+
{ text: 'Item 11', items: [ { text: 'Item 111' }, { text: 'Item 112' }, { text: 'Item 113' } ] },
2515+
{ text: 'Item 12' }
2516+
],
2517+
},
2518+
]
2519+
}).dxMenu('instance');
2520+
const keyboard = keyboardMock(menu.itemsContainer());
2521+
2522+
keyboard.press('enter')
2523+
.press('down')
2524+
.press('down');
2525+
2526+
assert.strictEqual($(menu.option('focusedElement')).text(), 'Item 12', 'focusedElement is submenu item');
2527+
2528+
keyboard.press('enter');
2529+
2530+
const mainMenuItemText = $(menu.itemElements()[0]).text();
2531+
2532+
assert.strictEqual($(menu.option('focusedElement')).text(), mainMenuItemText, 'focusedElement is main menu item');
2533+
});
2534+
2535+
QUnit.test('focusedElement should be set to main menu item after hiding nested submenu (T1291581)', function(assert) {
2536+
const menu = $('#menu').dxMenu({
2537+
orientation: 'horizontal',
2538+
focusStateEnabled: true,
2539+
items: [
2540+
{
2541+
text: 'Item 1',
2542+
items: [
2543+
{ text: 'Item 11', items: [ { text: 'Item 111' }, { text: 'Item 112' }, { text: 'Item 113' } ] },
2544+
{ text: 'Item 12' }
2545+
],
2546+
},
2547+
]
2548+
}).dxMenu('instance');
2549+
const keyboard = keyboardMock(menu.itemsContainer());
2550+
2551+
keyboard.press('enter')
2552+
.press('down')
2553+
.press('enter')
2554+
.press('right')
2555+
.press('down');
2556+
2557+
assert.strictEqual($(menu.option('focusedElement')).text(), 'Item 112', 'focusedElement is submenu item');
2558+
2559+
keyboard.press('enter');
2560+
2561+
const mainMenuItemText = $(menu.itemElements()[0]).text();
2562+
2563+
assert.strictEqual($(menu.option('focusedElement')).text(), mainMenuItemText, 'focusedElement is main menu item');
2564+
});
2565+
2566+
QUnit.test('focusedElement should not be moved if submenu hiding was cancelled (T1291581)', function(assert) {
2567+
const menu = $('#menu').dxMenu({
2568+
orientation: 'horizontal',
2569+
focusStateEnabled: true,
2570+
items: [
2571+
{
2572+
text: 'Item 1',
2573+
items: [
2574+
{ text: 'Item 11', items: [ { text: 'Item 111' }, { text: 'Item 112' }, { text: 'Item 113' } ] },
2575+
{ text: 'Item 12' }
2576+
],
2577+
},
2578+
],
2579+
onSubmenuHiding: function(e) {
2580+
e.cancel = true;
2581+
}
2582+
}).dxMenu('instance');
2583+
const keyboard = keyboardMock(menu.itemsContainer());
2584+
2585+
keyboard.press('enter')
2586+
.press('down')
2587+
.press('down');
2588+
2589+
const { focusedElement: initialFocusedElement } = menu.option();
2590+
2591+
keyboard.press('enter');
2592+
2593+
assert.strictEqual($(menu.option('focusedElement')).text(), 'Item 12', 'focusedElement is submenu item');
2594+
assert.strictEqual(menu.option('focusedElement'), initialFocusedElement, 'focusedElement not changed');
2595+
});
2596+
2597+
QUnit.test('focusedElement should not be set to main menu item after hiding nested submenu if no item was focused (T1304251)', function(assert) {
2598+
const menu = createMenu({
2599+
focusStateEnabled: true,
2600+
items: [{ text: 'Item 1', items: [{ text: 'Item 11' }, { text: 'Item 12' }, { text: 'Item 13' }] }],
2601+
showFirstSubmenuMode: { name: 'onHover', delay: 0 },
2602+
hideSubmenuOnMouseLeave: true
2603+
});
2604+
const $rootMenuItem = $(menu.element).find(`.${DX_MENU_ITEM_CLASS}`);
2605+
2606+
$(menu.element).trigger($.Event('dxhoverstart', { target: $rootMenuItem.get(0) }));
2607+
$($rootMenuItem).trigger('dxpointermove');
2608+
this.clock.tick(0);
2609+
2610+
const submenu = getSubMenuInstance($rootMenuItem);
2611+
const $item = $(submenu._overlay.content()).find(`.${DX_MENU_ITEM_CLASS}`);
2612+
2613+
$(menu.element).trigger($.Event('dxhoverstart', { target: $item.get(1) }));
2614+
$(menu.element).trigger($.Event('dxhoverend', { target: $item.get(1) }));
2615+
$(menu.element).trigger($.Event('dxhoverstart', { target: window }));
2616+
$($(submenu._overlay.content()).find('.dx-submenu')).trigger('dxhoverend');
2617+
this.clock.tick(0);
2618+
2619+
assert.strictEqual($rootMenuItem.eq(0).hasClass(DX_STATE_FOCUSED_CLASS), false, 'root menu item has not focused class');
2620+
assert.strictEqual(menu.instance.option('focusedElement'), null, 'menu focusedElement is null');
2621+
});
25052622
});
25062623

25072624
QUnit.module('keyboard navigation', {
@@ -2952,62 +3069,6 @@ QUnit.module('keyboard navigation', {
29523069

29533070
assert.equal($(this.instance._visibleSubmenu.option('focusedElement')).text(), 'Item 113');
29543071
});
2955-
2956-
QUnit.test('focusedElement should be set to main menu item after hiding submenu', function(assert) {
2957-
this.instance.option({
2958-
orientation: 'horizontal',
2959-
items: [
2960-
{
2961-
text: 'Item 1',
2962-
items: [
2963-
{ text: 'Item 11', items: [ { text: 'Item 111' }, { text: 'Item 112' }, { text: 'Item 113' } ] },
2964-
{ text: 'Item 12' }
2965-
],
2966-
},
2967-
]
2968-
});
2969-
2970-
this.keyboard.press('enter')
2971-
.press('down')
2972-
.press('down');
2973-
2974-
assert.strictEqual($(this.instance.option('focusedElement')).text(), 'Item 12', 'focusedElement is submenu item');
2975-
2976-
this.keyboard.press('enter');
2977-
2978-
const mainMenuItemText = $(this.instance.itemElements()[0]).text();
2979-
2980-
assert.strictEqual($(this.instance.option('focusedElement')).text(), mainMenuItemText, 'focusedElement is main menu item');
2981-
});
2982-
2983-
QUnit.test('focusedElement should be set to main menu item after hiding nested submenu', function(assert) {
2984-
this.instance.option({
2985-
orientation: 'horizontal',
2986-
items: [
2987-
{
2988-
text: 'Item 1',
2989-
items: [
2990-
{ text: 'Item 11', items: [ { text: 'Item 111' }, { text: 'Item 112' }, { text: 'Item 113' } ] },
2991-
{ text: 'Item 12' }
2992-
],
2993-
},
2994-
]
2995-
});
2996-
2997-
this.keyboard.press('enter')
2998-
.press('down')
2999-
.press('enter')
3000-
.press('right')
3001-
.press('down');
3002-
3003-
assert.strictEqual($(this.instance.option('focusedElement')).text(), 'Item 112', 'focusedElement is submenu item');
3004-
3005-
this.keyboard.press('enter');
3006-
3007-
const mainMenuItemText = $(this.instance.itemElements()[0]).text();
3008-
3009-
assert.strictEqual($(this.instance.option('focusedElement')).text(), mainMenuItemText, 'focusedElement is main menu item');
3010-
});
30113072
});
30123073

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

0 commit comments

Comments
 (0)