Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 17 additions & 11 deletions packages/devextreme/js/__internal/ui/menu/m_menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -692,23 +692,29 @@ class Menu extends MenuBase {

this._actions.onSubmenuHiding(eventArgs);

if (eventArgs.cancel) {
return;
}

const { focusedElement } = this.option();
const { focusedElement: submenuFocusedElement } = submenu.option();
const submenuContainerElement = $(eventArgs.submenuContainer).get(0);
const focusedDomElement = $(focusedElement).get(0);
const isFocusedElementInsideSubmenu = focusedDomElement && submenuContainerElement
? submenuContainerElement.contains(focusedDomElement)
: false;

if (isFocusedElementInsideSubmenu) {
this.option('focusedElement', getPublicElement($menuAnchorItem));
}

const isVisibleSubmenuHiding = this._visibleSubmenu === submenu;
const isFocusedElementHiding = focusedElement === submenuFocusedElement;

if (isVisibleSubmenuHiding && isFocusedElementHiding) {
this.option('focusedElement', $menuAnchorItem);
if (isVisibleSubmenuHiding) {
this._visibleSubmenu = null;
}

if (!eventArgs.cancel) {
if (isVisibleSubmenuHiding) {
this._visibleSubmenu = null;
}
$border.hide();
$menuAnchorItem.removeClass(DX_MENU_ITEM_EXPANDED_CLASS);
}
$border.hide();
$menuAnchorItem.removeClass(DX_MENU_ITEM_EXPANDED_CLASS);
}

_submenuOnHiddenHandler($menuAnchorItem, submenu, { rootItem }) {
Expand Down
177 changes: 121 additions & 56 deletions packages/devextreme/testing/tests/DevExpress.ui.widgets/menu.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import ArrayStore from 'common/data/array_store';
import eventsEngine from 'common/core/events/core/events_engine';
import { DataSource } from 'common/data/data_source/data_source';
import * as checkStyleHelper from '../../helpers/checkStyleHelper.js';
import { shouldSkipOnMobile } from '../../helpers/device.js';

import 'generic_light.css!';
import { implementationsMap, getHeight, getWidth, getOuterHeight } from 'core/utils/size';
Expand Down Expand Up @@ -2502,6 +2503,126 @@ QUnit.module('Menu tests', {
submenu = getSubMenuInstance($rootMenuItem);
assert.ok(submenu.option('visible'), 'submenu shown');
});

QUnit.test('focusedElement should be set to main menu item after hiding submenu (T1291581)', function(assert) {
const menu = $('#menu').dxMenu({
orientation: 'horizontal',
focusStateEnabled: true,
items: [
{
text: 'Item 1',
items: [
{ text: 'Item 11', items: [ { text: 'Item 111' }, { text: 'Item 112' }, { text: 'Item 113' } ] },
{ text: 'Item 12' }
],
},
]
}).dxMenu('instance');
const keyboard = keyboardMock(menu.itemsContainer());

keyboard.press('enter')
.press('down')
.press('down');

assert.strictEqual($(menu.option('focusedElement')).text(), 'Item 12', 'focusedElement is submenu item');

keyboard.press('enter');

const mainMenuItemText = $(menu.itemElements()[0]).text();

assert.strictEqual($(menu.option('focusedElement')).text(), mainMenuItemText, 'focusedElement is main menu item');
});

QUnit.test('focusedElement should be set to main menu item after hiding nested submenu (T1291581)', function(assert) {
const menu = $('#menu').dxMenu({
orientation: 'horizontal',
focusStateEnabled: true,
items: [
{
text: 'Item 1',
items: [
{ text: 'Item 11', items: [ { text: 'Item 111' }, { text: 'Item 112' }, { text: 'Item 113' } ] },
{ text: 'Item 12' }
],
},
]
}).dxMenu('instance');
const keyboard = keyboardMock(menu.itemsContainer());

keyboard.press('enter')
.press('down')
.press('enter')
.press('right')
.press('down');

assert.strictEqual($(menu.option('focusedElement')).text(), 'Item 112', 'focusedElement is submenu item');

keyboard.press('enter');

const mainMenuItemText = $(menu.itemElements()[0]).text();

assert.strictEqual($(menu.option('focusedElement')).text(), mainMenuItemText, 'focusedElement is main menu item');
});

QUnit.test('focusedElement should not be moved if submenu hiding was cancelled (T1291581)', function(assert) {
const menu = $('#menu').dxMenu({
orientation: 'horizontal',
focusStateEnabled: true,
items: [
{
text: 'Item 1',
items: [
{ text: 'Item 11', items: [ { text: 'Item 111' }, { text: 'Item 112' }, { text: 'Item 113' } ] },
{ text: 'Item 12' }
],
},
],
onSubmenuHiding: function(e) {
e.cancel = true;
}
}).dxMenu('instance');
const keyboard = keyboardMock(menu.itemsContainer());

keyboard.press('enter')
.press('down')
.press('down');

const { focusedElement: initialFocusedElement } = menu.option();

keyboard.press('enter');

assert.strictEqual($(menu.option('focusedElement')).text(), 'Item 12', 'focusedElement is submenu item');
assert.strictEqual(menu.option('focusedElement'), initialFocusedElement, 'focusedElement not changed');
});

QUnit.test('focusedElement should not be set to main menu item after hiding nested submenu if no item was focused (T1304251)', function(assert) {
if(shouldSkipOnMobile(assert)) {
return;
}

const menu = createMenu({
items: [{ text: 'Item 1', items: [{ text: 'Item 11' }, { text: 'Item 12' }, { text: 'Item 13' }] }],
showFirstSubmenuMode: { name: 'onHover', delay: 0 },
hideSubmenuOnMouseLeave: true
});
const $rootMenuItem = $(menu.element).find(`.${DX_MENU_ITEM_CLASS}`);

$(menu.element).trigger($.Event('dxhoverstart', { target: $rootMenuItem.get(0) }));
$($rootMenuItem).trigger('dxpointermove');
this.clock.tick(0);

const submenu = getSubMenuInstance($rootMenuItem);
const $item = $(submenu._overlay.content()).find(`.${DX_MENU_ITEM_CLASS}`);

$(menu.element).trigger($.Event('dxhoverstart', { target: $item.get(1) }));
$(menu.element).trigger($.Event('dxhoverend', { target: $item.get(1) }));
$(menu.element).trigger($.Event('dxhoverstart', { target: window }));
$($(submenu._overlay.content()).find('.dx-submenu')).trigger('dxhoverend');
this.clock.tick(0);

assert.strictEqual($rootMenuItem.eq(0).hasClass(DX_STATE_FOCUSED_CLASS), false, 'root menu item has not focused class');
assert.strictEqual(menu.instance.option('focusedElement'), null, 'menu focusedElement is null');
});
});

QUnit.module('keyboard navigation', {
Expand Down Expand Up @@ -2952,62 +3073,6 @@ QUnit.module('keyboard navigation', {

assert.equal($(this.instance._visibleSubmenu.option('focusedElement')).text(), 'Item 113');
});

QUnit.test('focusedElement should be set to main menu item after hiding submenu', function(assert) {
this.instance.option({
orientation: 'horizontal',
items: [
{
text: 'Item 1',
items: [
{ text: 'Item 11', items: [ { text: 'Item 111' }, { text: 'Item 112' }, { text: 'Item 113' } ] },
{ text: 'Item 12' }
],
},
]
});

this.keyboard.press('enter')
.press('down')
.press('down');

assert.strictEqual($(this.instance.option('focusedElement')).text(), 'Item 12', 'focusedElement is submenu item');

this.keyboard.press('enter');

const mainMenuItemText = $(this.instance.itemElements()[0]).text();

assert.strictEqual($(this.instance.option('focusedElement')).text(), mainMenuItemText, 'focusedElement is main menu item');
});

QUnit.test('focusedElement should be set to main menu item after hiding nested submenu', function(assert) {
this.instance.option({
orientation: 'horizontal',
items: [
{
text: 'Item 1',
items: [
{ text: 'Item 11', items: [ { text: 'Item 111' }, { text: 'Item 112' }, { text: 'Item 113' } ] },
{ text: 'Item 12' }
],
},
]
});

this.keyboard.press('enter')
.press('down')
.press('enter')
.press('right')
.press('down');

assert.strictEqual($(this.instance.option('focusedElement')).text(), 'Item 112', 'focusedElement is submenu item');

this.keyboard.press('enter');

const mainMenuItemText = $(this.instance.itemElements()[0]).text();

assert.strictEqual($(this.instance.option('focusedElement')).text(), mainMenuItemText, 'focusedElement is main menu item');
});
});

QUnit.module('Menu with templates', {
Expand Down
Loading