Skip to content

Commit 4816422

Browse files
Menu: fix focus logic on submenu hiding (T1304251) (24_2) (#32015)
1 parent 9d098f9 commit 4816422

File tree

2 files changed

+138
-67
lines changed

2 files changed

+138
-67
lines changed

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: 121 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import ArrayStore from 'common/data/array_store';
1515
import eventsEngine from 'common/core/events/core/events_engine';
1616
import { DataSource } from 'common/data/data_source/data_source';
1717
import * as checkStyleHelper from '../../helpers/checkStyleHelper.js';
18+
import { shouldSkipOnMobile } from '../../helpers/device.js';
1819

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

25072628
QUnit.module('keyboard navigation', {
@@ -2952,62 +3073,6 @@ QUnit.module('keyboard navigation', {
29523073

29533074
assert.equal($(this.instance._visibleSubmenu.option('focusedElement')).text(), 'Item 113');
29543075
});
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-
});
30113076
});
30123077

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

0 commit comments

Comments
 (0)