Skip to content

Commit 77054d3

Browse files
authored
Toolbar: prevent overflow menu from closing on editor component click (T1287462) (DevExpress#30020)
1 parent 41d4e28 commit 77054d3

File tree

3 files changed

+66
-47
lines changed

3 files changed

+66
-47
lines changed

packages/devextreme/js/__internal/ui/toolbar/internal/m_toolbar.menu.list.ts

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ const TOOLBAR_MENU_CUSTOM_CLASS = 'dx-toolbar-menu-custom';
1010
const TOOLBAR_MENU_LAST_SECTION_CLASS = 'dx-toolbar-menu-last-section';
1111
const SCROLLVIEW_CONTENT_CLASS = 'dx-scrollview-content';
1212
export default class ToolbarMenuList extends ListBase {
13-
// @ts-expect-error ts-error
14-
_activeStateUnit?: string;
13+
_activeStateUnit!: string;
1514

1615
_init(): void {
1716
super._init();
@@ -67,29 +66,41 @@ export default class ToolbarMenuList extends ListBase {
6766
}
6867

6968
_renderItem(index, item, itemContainer, $after) {
70-
const location = item.location ?? 'menu';
71-
const $container = this[`_$${location}Section`];
69+
const $container = this[`_$${item.location ?? 'menu'}Section`];
7270
const itemElement = super._renderItem(index, item, $container, $after);
7371

72+
const itemCssClasses = this._getItemCssClasses(item);
73+
itemElement.addClass(itemCssClasses.join(' '));
74+
75+
return itemElement;
76+
}
77+
78+
_getItemCssClasses(item): string[] {
79+
const cssClasses: string[] = [];
80+
const actionableComponents = this._getActionableComponents();
81+
7482
if (this._getItemTemplateName({ itemData: item })) {
75-
itemElement.addClass(TOOLBAR_MENU_CUSTOM_CLASS);
83+
cssClasses.push(TOOLBAR_MENU_CUSTOM_CLASS);
7684
}
7785

78-
if (location === 'menu' || item.widget === 'dxButton' || item.widget === 'dxButtonGroup' || item.isAction) {
79-
itemElement.addClass(TOOLBAR_MENU_ACTION_CLASS);
86+
if (!item.widget || actionableComponents.includes(item.widget)) {
87+
cssClasses.push(TOOLBAR_MENU_ACTION_CLASS);
8088
}
8189

8290
if (item.widget === 'dxButton') {
83-
itemElement.addClass(TOOLBAR_HIDDEN_BUTTON_CLASS);
91+
cssClasses.push(TOOLBAR_HIDDEN_BUTTON_CLASS);
8492
}
8593

8694
if (item.widget === 'dxButtonGroup') {
87-
itemElement.addClass(TOOLBAR_HIDDEN_BUTTON_GROUP_CLASS);
95+
cssClasses.push(TOOLBAR_HIDDEN_BUTTON_GROUP_CLASS);
8896
}
8997

90-
itemElement.addClass(item.cssClass);
98+
cssClasses.push(item.cssClass);
99+
return cssClasses;
100+
}
91101

92-
return itemElement;
102+
_getActionableComponents() {
103+
return ['dxButton', 'dxButtonGroup'];
93104
}
94105

95106
_getItemTemplateName(args) {

packages/devextreme/js/__internal/ui/toolbar/internal/m_toolbar.menu.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ export default class DropDownMenu extends Widget<DropDownMenuProperties> {
290290
$content.addClass(DROP_DOWN_MENU_LIST_CLASS);
291291

292292
const { itemTemplate, onItemRendered } = this.option();
293-
// @ts-expect-error ts-error
293+
294294
this._list = this._createComponent($content, ToolbarMenuList, {
295295
dataSource: this._getListDataSource(),
296296
pageLoadMode: 'scrollBottom',

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

Lines changed: 43 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'generic_light.css!';
1111

1212
import 'ui/text_box';
1313
import 'ui/drop_down_button';
14+
import 'ui/select_box';
1415
import 'ui/tabs';
1516
import 'ui/toolbar';
1617

@@ -293,15 +294,15 @@ QUnit.module('render', {
293294

294295
toolbar.option('compactMode', false);
295296

296-
assert.ok(!$toolbar.hasClass(TOOLBAR_COMPACT_CLASS), 'toolbar without compact mode hasn\'t the compact class');
297+
assert.ok(!$toolbar.hasClass(TOOLBAR_COMPACT_CLASS), 'toolbar without compact mode has not the compact class');
297298

298299
toolbar.option('compactMode', true);
299300

300301
assert.ok($toolbar.hasClass(TOOLBAR_COMPACT_CLASS), 'compact class has been added to the toolbar');
301302

302303
toolbar.option('width', 400);
303304

304-
assert.ok(!$toolbar.hasClass(TOOLBAR_COMPACT_CLASS), 'toolbar with compact mode hasn\'t the compact class if widget has a large width');
305+
assert.ok(!$toolbar.hasClass(TOOLBAR_COMPACT_CLASS), 'toolbar with compact mode has not the compact class if widget has a large width');
305306
});
306307

307308
QUnit.test('Buttons has default style in generic theme', function(assert) {
@@ -320,7 +321,7 @@ QUnit.module('render', {
320321
assert.notOk(button.hasClass('dx-button-mode-text'));
321322
});
322323

323-
QUnit.test('Toolbar provides it\'s own templates for the item widgets', function(assert) {
324+
QUnit.test('Toolbar provides its own templates for the item widgets', function(assert) {
324325
let templateUsed;
325326

326327
this.$element.dxToolbar({
@@ -530,7 +531,7 @@ QUnit.module('toolbar with menu', moduleConfig, () => {
530531
assert.strictEqual(this.instance.option('overflowMenuVisible'), false);
531532
});
532533

533-
QUnit.test('menu button click doesn\'t dispatch action', function(assert) {
534+
QUnit.test('menu button click does not dispatch action', function(assert) {
534535
const onItemClickHandler = sinon.spy();
535536

536537
this.instance.option({
@@ -545,7 +546,7 @@ QUnit.module('toolbar with menu', moduleConfig, () => {
545546
assert.strictEqual(onItemClickHandler.callCount, 0, 'onItemClick was not executed');
546547
});
547548

548-
QUnit.test('windowResize should not show/hide menu that doesn\'t created', function(assert) {
549+
QUnit.test('windowResize should not show/hide menu that was not created', function(assert) {
549550
this.instance.option('items', []);
550551

551552
resizeCallbacks.fire();
@@ -576,6 +577,32 @@ QUnit.module('toolbar with menu', moduleConfig, () => {
576577

577578
assert.strictEqual($('.dx-state-disabled').length, 0, 'disabled state changed');
578579
});
580+
581+
[
582+
{
583+
widget: 'dxSelectBox',
584+
options: { items: ['item'] },
585+
},
586+
{
587+
widget: 'dxDropDownButton',
588+
options: { items: ['item'] },
589+
},
590+
].forEach(({ widget, options }) => {
591+
QUnit.test(`click on editor component (${widget}) inside the toolbar menu should not close it (T1287462)`, function(assert) {
592+
this.instance.option('items', [{
593+
locateInMenu: 'always',
594+
widget,
595+
options,
596+
}]);
597+
598+
this.overflowMenu.click();
599+
600+
const $menuItem = $(`.${TOOLBAR_MENU_SECTION_CLASS} .${LIST_ITEM_CLASS}`).eq(0);
601+
$menuItem.trigger('dxclick');
602+
603+
assert.strictEqual(this.instance.option('overflowMenuVisible'), true, `overflow menu remains visible after clicking ${widget} in it`);
604+
});
605+
});
579606
});
580607

581608

@@ -618,7 +645,7 @@ QUnit.module('widget sizing render', () => {
618645
assert.strictEqual($element.outerWidth(), customWidth, 'outer width of the element must be equal to custom width');
619646
});
620647

621-
QUnit.test('text should crop in the label inside the toolbar on toolbar\'s width changing', function(assert) {
648+
QUnit.test('text should crop in the label inside the toolbar on toolbar width changing', function(assert) {
622649
const $element = $('#widget').dxToolbar({
623650
items: [
624651
{ location: 'before', text: 'Before long text label' },
@@ -636,7 +663,7 @@ QUnit.module('widget sizing render', () => {
636663
assert.roughEqual($before.width(), 100 - $after.width() - afterPadding, 1.001, 'width of before element should be changed');
637664
});
638665

639-
QUnit.test('text should crop in the label inside the toolbar on window\'s width changing', function(assert) {
666+
QUnit.test('text should crop in the label inside the toolbar on window width changing', function(assert) {
640667
const $element = $('#widget').width(300).dxToolbar({
641668
items: [
642669
{ location: 'before', text: 'Before long text label' },
@@ -1061,7 +1088,7 @@ QUnit.module('adaptivity', moduleConfig, () => {
10611088
assert.strictEqual(this.overflowMenu.$element().length, 1);
10621089
});
10631090

1064-
QUnit.test('menu shouldn\'t be closed during resize with open menu if menu has items', function(assert) {
1091+
QUnit.test('menu should not be closed during resize with open menu if menu has items', function(assert) {
10651092
this.instance.option({
10661093
items: [
10671094
{ location: 'before', template: () => $('<div>').width(100) },
@@ -1171,7 +1198,7 @@ QUnit.module('adaptivity', moduleConfig, () => {
11711198
this.overflowMenu.click();
11721199
});
11731200

1174-
QUnit.test('items with locateInMenu == \'always\' should be rendered in menu if there is free space for them', function(assert) {
1201+
QUnit.test('items with locateInMenu === always should be rendered in menu if there is free space for them', function(assert) {
11751202
const $item = $('<div>').width(100);
11761203
this.instance.option({
11771204
items: [
@@ -1236,23 +1263,6 @@ QUnit.module('adaptivity', moduleConfig, () => {
12361263
assert.ok($sections.eq(2).hasClass('dx-toolbar-menu-last-section'), 'border for last section is removed');
12371264
});
12381265

1239-
QUnit.test('menu shouldn\'t be closed after click on editors', function(assert) {
1240-
const $beforeItem = $('<div>').width(150);
1241-
1242-
this.instance.option({
1243-
items: [
1244-
{ location: 'before', locateInMenu: 'auto', template: () => $beforeItem },
1245-
],
1246-
width: 100
1247-
});
1248-
1249-
this.overflowMenu.click();
1250-
1251-
$($beforeItem).trigger('dxclick');
1252-
1253-
assert.ok(this.overflowMenu.instance().option('opened'), 'dropdown isn\'t closed');
1254-
});
1255-
12561266
QUnit.test('menu should be closed after click on button or menu items', function(assert) {
12571267
this.instance.option({
12581268
items: [
@@ -1343,7 +1353,7 @@ QUnit.module('adaptivity', moduleConfig, () => {
13431353
});
13441354

13451355
assert.ok($toolbarTemplate.is(':visible'), 'toolbar template was rendered');
1346-
assert.ok($menuTemplate.is(':hidden'), 'menu template won\'t rendered');
1356+
assert.ok($menuTemplate.is(':hidden'), 'menu template was not rendered');
13471357

13481358
this.instance.option('width', 400);
13491359

@@ -1518,7 +1528,7 @@ QUnit.module('adaptivity', moduleConfig, () => {
15181528

15191529
this.overflowMenu.click();
15201530

1521-
assert.strictEqual(this.$element.find(`.${DROP_DOWN_MENU_POPUP_WRAPPER_CLASS}`).length, 0, 'Toolbar\'s container isn\'t contains a dropDown list');
1531+
assert.strictEqual(this.$element.find(`.${DROP_DOWN_MENU_POPUP_WRAPPER_CLASS}`).length, 0, 'Toolbar container does not contain a dropDown list');
15221532
});
15231533

15241534
QUnit.test('init Toolbar with new menuContainer', function(assert) {
@@ -1535,7 +1545,7 @@ QUnit.module('adaptivity', moduleConfig, () => {
15351545

15361546
this.overflowMenu.click();
15371547

1538-
assert.strictEqual(this.$element.find(`.${DROP_DOWN_MENU_POPUP_WRAPPER_CLASS}`).length, 1, 'Toolbar\'s container contains a dropDown list');
1548+
assert.strictEqual(this.$element.find(`.${DROP_DOWN_MENU_POPUP_WRAPPER_CLASS}`).length, 1, 'Toolbar container contains a dropDown list');
15391549
});
15401550

15411551
QUnit.test('change Toolbar menuContainer', function(assert) {
@@ -1553,7 +1563,7 @@ QUnit.module('adaptivity', moduleConfig, () => {
15531563

15541564
this.overflowMenu.click();
15551565

1556-
assert.strictEqual(this.$element.find(`.${DROP_DOWN_MENU_POPUP_WRAPPER_CLASS}`).length, 1, 'Toolbar\'s container contains a dropDown list');
1566+
assert.strictEqual(this.$element.find(`.${DROP_DOWN_MENU_POPUP_WRAPPER_CLASS}`).length, 1, 'Toolbar container contains a dropDown list');
15571567
});
15581568
});
15591569

@@ -1567,7 +1577,6 @@ QUnit.module('default template', moduleConfig, () => {
15671577
location: 'center',
15681578
text: '123',
15691579
locateInMenu: 'always',
1570-
isAction: true,
15711580
onClick: onClickActionStub
15721581
}
15731582
],
@@ -1576,11 +1585,10 @@ QUnit.module('default template', moduleConfig, () => {
15761585

15771586
this.overflowMenu.click();
15781587

1579-
const $items = this.overflowMenu.instance()._popup.$content().find('.dx-list-item');
1588+
const $menuItem = $(`.${TOOLBAR_MENU_SECTION_CLASS} .${LIST_ITEM_CLASS}`).eq(0);
1589+
$menuItem.trigger('dxclick');
15801590

1581-
$($items.eq(0)).trigger('dxclick');
1582-
1583-
assert.ok(!this.overflowMenu.instance().option('opened'), 'dropdown is closed');
1591+
assert.strictEqual(this.instance.option('overflowMenuVisible'), false, 'dropdown is closed');
15841592
assert.strictEqual(onClickActionStub.callCount, 1, 'onClick was fired');
15851593
});
15861594

0 commit comments

Comments
 (0)