@vaadin/menu-bar 24.8.0-alpha2 → 24.8.0-alpha20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/menu-bar",
3
- "version": "24.8.0-alpha2",
3
+ "version": "24.8.0-alpha20",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -37,22 +37,22 @@
37
37
  "dependencies": {
38
38
  "@open-wc/dedupe-mixin": "^1.3.0",
39
39
  "@polymer/polymer": "^3.0.0",
40
- "@vaadin/a11y-base": "24.8.0-alpha2",
41
- "@vaadin/button": "24.8.0-alpha2",
42
- "@vaadin/component-base": "24.8.0-alpha2",
43
- "@vaadin/context-menu": "24.8.0-alpha2",
44
- "@vaadin/item": "24.8.0-alpha2",
45
- "@vaadin/list-box": "24.8.0-alpha2",
46
- "@vaadin/overlay": "24.8.0-alpha2",
47
- "@vaadin/vaadin-lumo-styles": "24.8.0-alpha2",
48
- "@vaadin/vaadin-material-styles": "24.8.0-alpha2",
49
- "@vaadin/vaadin-themable-mixin": "24.8.0-alpha2",
40
+ "@vaadin/a11y-base": "24.8.0-alpha20",
41
+ "@vaadin/button": "24.8.0-alpha20",
42
+ "@vaadin/component-base": "24.8.0-alpha20",
43
+ "@vaadin/context-menu": "24.8.0-alpha20",
44
+ "@vaadin/item": "24.8.0-alpha20",
45
+ "@vaadin/list-box": "24.8.0-alpha20",
46
+ "@vaadin/overlay": "24.8.0-alpha20",
47
+ "@vaadin/vaadin-lumo-styles": "24.8.0-alpha20",
48
+ "@vaadin/vaadin-material-styles": "24.8.0-alpha20",
49
+ "@vaadin/vaadin-themable-mixin": "24.8.0-alpha20",
50
50
  "lit": "^3.0.0"
51
51
  },
52
52
  "devDependencies": {
53
- "@vaadin/chai-plugins": "24.8.0-alpha2",
54
- "@vaadin/icon": "24.8.0-alpha2",
55
- "@vaadin/test-runner-commands": "24.8.0-alpha2",
53
+ "@vaadin/chai-plugins": "24.8.0-alpha20",
54
+ "@vaadin/icon": "24.8.0-alpha20",
55
+ "@vaadin/test-runner-commands": "24.8.0-alpha20",
56
56
  "@vaadin/testing-helpers": "^1.1.0",
57
57
  "sinon": "^18.0.0"
58
58
  },
@@ -60,5 +60,5 @@
60
60
  "web-types.json",
61
61
  "web-types.lit.json"
62
62
  ],
63
- "gitHead": "f48777a6e3dcf605b700305a7142145e197bb416"
63
+ "gitHead": "f091548b1f079f0c9de4be2a8ded77fb18671a2e"
64
64
  }
@@ -9,6 +9,7 @@ import type { FocusMixinClass } from '@vaadin/a11y-base/src/focus-mixin.js';
9
9
  import type { KeyboardDirectionMixinClass } from '@vaadin/a11y-base/src/keyboard-direction-mixin.js';
10
10
  import type { KeyboardMixinClass } from '@vaadin/a11y-base/src/keyboard-mixin.js';
11
11
  import type { ControllerMixinClass } from '@vaadin/component-base/src/controller-mixin.js';
12
+ import type { I18nMixinClass } from '@vaadin/component-base/src/i18n-mixin.js';
12
13
  import type { ResizeMixinClass } from '@vaadin/component-base/src/resize-mixin.js';
13
14
 
14
15
  export type MenuBarItem<TItemData extends object = object> = {
@@ -56,7 +57,7 @@ export type SubMenuItem<TItemData extends object = object> = {
56
57
  } & TItemData;
57
58
 
58
59
  export interface MenuBarI18n {
59
- moreOptions: string;
60
+ moreOptions?: string;
60
61
  }
61
62
 
62
63
  export declare function MenuBarMixin<T extends Constructor<HTMLElement>, TItem extends MenuBarItem = MenuBarItem>(
@@ -64,6 +65,7 @@ export declare function MenuBarMixin<T extends Constructor<HTMLElement>, TItem e
64
65
  ): Constructor<ControllerMixinClass> &
65
66
  Constructor<DisabledMixinClass> &
66
67
  Constructor<FocusMixinClass> &
68
+ Constructor<I18nMixinClass<MenuBarI18n>> &
67
69
  Constructor<KeyboardDirectionMixinClass> &
68
70
  Constructor<KeyboardMixinClass> &
69
71
  Constructor<MenuBarMixinClass<TItem>> &
@@ -121,17 +123,9 @@ export declare class MenuBarMixinClass<TItem extends MenuBarItem = MenuBarItem>
121
123
  items: TItem[];
122
124
 
123
125
  /**
124
- * The object used to localize this component.
125
- * To change the default localization, replace the entire
126
- * `i18n` object with a custom one.
127
- *
128
- * To update individual properties, extend the existing i18n object like so:
129
- * ```
130
- * menuBar.i18n = {
131
- * ...menuBar.i18n,
132
- * moreOptions: 'More options'
133
- * }
134
- * ```
126
+ * The object used to localize this component. To change the default
127
+ * localization, replace this with an object that provides all properties, or
128
+ * just the individual properties you want to change.
135
129
  *
136
130
  * The object has the following JSON structure and default values:
137
131
  * ```
@@ -3,25 +3,70 @@
3
3
  * Copyright (c) 2019 - 2025 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
+ import { html, noChange, nothing, render } from 'lit';
7
+ import { Directive, directive } from 'lit/directive.js';
8
+ import { ifDefined } from 'lit/directives/if-defined.js';
6
9
  import { DisabledMixin } from '@vaadin/a11y-base/src/disabled-mixin.js';
7
10
  import { FocusMixin } from '@vaadin/a11y-base/src/focus-mixin.js';
8
- import { isElementFocused, isElementHidden, isKeyboardActive } from '@vaadin/a11y-base/src/focus-utils.js';
11
+ import { isElementFocused, isKeyboardActive } from '@vaadin/a11y-base/src/focus-utils.js';
9
12
  import { KeyboardDirectionMixin } from '@vaadin/a11y-base/src/keyboard-direction-mixin.js';
10
13
  import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
14
+ import { I18nMixin } from '@vaadin/component-base/src/i18n-mixin.js';
11
15
  import { ResizeMixin } from '@vaadin/component-base/src/resize-mixin.js';
12
16
  import { SlotController } from '@vaadin/component-base/src/slot-controller.js';
13
17
 
18
+ /**
19
+ * Custom Lit directive for rendering item components
20
+ * inspired by the `flowComponentDirective` logic.
21
+ */
22
+ class ItemComponentDirective extends Directive {
23
+ update(part, [{ component, text }]) {
24
+ const { parentNode, startNode } = part;
25
+
26
+ const newNode = component || (text ? document.createTextNode(text) : null);
27
+ const oldNode = this.getOldNode(part);
28
+
29
+ if (oldNode === newNode) {
30
+ return noChange;
31
+ } else if (oldNode && newNode) {
32
+ parentNode.replaceChild(newNode, oldNode);
33
+ } else if (oldNode) {
34
+ parentNode.removeChild(oldNode);
35
+ } else if (newNode) {
36
+ startNode.after(newNode);
37
+ }
38
+
39
+ return noChange;
40
+ }
41
+
42
+ getOldNode(part) {
43
+ const { startNode, endNode } = part;
44
+ if (startNode.nextSibling === endNode) {
45
+ return null;
46
+ }
47
+ return startNode.nextSibling;
48
+ }
49
+ }
50
+
51
+ const componentDirective = directive(ItemComponentDirective);
52
+
53
+ const DEFAULT_I18N = {
54
+ moreOptions: 'More options',
55
+ };
56
+
14
57
  /**
15
58
  * @polymerMixin
16
- * @mixes DisabledMixin
17
59
  * @mixes ControllerMixin
60
+ * @mixes DisabledMixin
18
61
  * @mixes FocusMixin
62
+ * @mixes I18nMixin
19
63
  * @mixes KeyboardDirectionMixin
20
64
  * @mixes ResizeMixin
21
65
  */
22
66
  export const MenuBarMixin = (superClass) =>
23
- class MenuBarMixinClass extends KeyboardDirectionMixin(
24
- ResizeMixin(FocusMixin(DisabledMixin(ControllerMixin(superClass)))),
67
+ class MenuBarMixinClass extends I18nMixin(
68
+ DEFAULT_I18N,
69
+ KeyboardDirectionMixin(ResizeMixin(FocusMixin(DisabledMixin(ControllerMixin(superClass))))),
25
70
  ) {
26
71
  static get properties() {
27
72
  return {
@@ -105,41 +150,10 @@ export const MenuBarMixin = (superClass) =>
105
150
  */
106
151
  items: {
107
152
  type: Array,
153
+ sync: true,
108
154
  value: () => [],
109
155
  },
110
156
 
111
- /**
112
- * The object used to localize this component.
113
- * To change the default localization, replace the entire
114
- * `i18n` object with a custom one.
115
- *
116
- * To update individual properties, extend the existing i18n object like so:
117
- * ```
118
- * menuBar.i18n = {
119
- * ...menuBar.i18n,
120
- * moreOptions: 'More options'
121
- * }
122
- * ```
123
- *
124
- * The object has the following JSON structure and default values:
125
- * ```
126
- * {
127
- * moreOptions: 'More options'
128
- * }
129
- * ```
130
- *
131
- * @type {!MenuBarI18n}
132
- * @default {English/US}
133
- */
134
- i18n: {
135
- type: Object,
136
- value: () => {
137
- return {
138
- moreOptions: 'More options',
139
- };
140
- },
141
- },
142
-
143
157
  /**
144
158
  * A space-delimited list of CSS class names
145
159
  * to set on each sub-menu overlay element.
@@ -165,6 +179,7 @@ export const MenuBarMixin = (superClass) =>
165
179
  */
166
180
  reverseCollapse: {
167
181
  type: Boolean,
182
+ sync: true,
168
183
  },
169
184
 
170
185
  /**
@@ -174,6 +189,7 @@ export const MenuBarMixin = (superClass) =>
174
189
  */
175
190
  tabNavigation: {
176
191
  type: Boolean,
192
+ sync: true,
177
193
  },
178
194
 
179
195
  /**
@@ -194,6 +210,7 @@ export const MenuBarMixin = (superClass) =>
194
210
  /** @protected */
195
211
  _container: {
196
212
  type: Object,
213
+ sync: true,
197
214
  },
198
215
  };
199
216
  }
@@ -202,13 +219,34 @@ export const MenuBarMixin = (superClass) =>
202
219
  return [
203
220
  '_themeChanged(_theme, _overflow, _container)',
204
221
  '__hasOverflowChanged(_hasOverflow, _overflow)',
205
- '__i18nChanged(i18n, _overflow)',
206
- '_menuItemsChanged(items, _overflow, _container)',
222
+ '__i18nChanged(__effectiveI18n, _overflow)',
223
+ '__updateButtons(items, disabled, _overflow, _container)',
207
224
  '_reverseCollapseChanged(reverseCollapse, _overflow, _container)',
208
225
  '_tabNavigationChanged(tabNavigation, _overflow, _container)',
209
226
  ];
210
227
  }
211
228
 
229
+ /**
230
+ * The object used to localize this component. To change the default
231
+ * localization, replace this with an object that provides all properties, or
232
+ * just the individual properties you want to change.
233
+ *
234
+ * The object has the following JSON structure and default values:
235
+ * ```
236
+ * {
237
+ * moreOptions: 'More options'
238
+ * }
239
+ * ```
240
+ * @return {!MenuBarI18n}
241
+ */
242
+ get i18n() {
243
+ return super.i18n;
244
+ }
245
+
246
+ set i18n(value) {
247
+ super.i18n = value;
248
+ }
249
+
212
250
  constructor() {
213
251
  super();
214
252
  this.__boundOnContextMenuKeydown = this.__onContextMenuKeydown.bind(this);
@@ -239,6 +277,17 @@ export const MenuBarMixin = (superClass) =>
239
277
  return false;
240
278
  }
241
279
 
280
+ /**
281
+ * Override getter from `KeyboardDirectionMixin`.
282
+ *
283
+ * @return {boolean}
284
+ * @protected
285
+ * @override
286
+ */
287
+ get _tabNavigation() {
288
+ return this.tabNavigation;
289
+ }
290
+
242
291
  /**
243
292
  * Override getter from `ResizeMixin` to observe parent.
244
293
  *
@@ -277,8 +326,11 @@ export const MenuBarMixin = (superClass) =>
277
326
  dots.innerHTML = '&centerdot;'.repeat(3);
278
327
  btn.appendChild(dots);
279
328
 
329
+ btn.setAttribute('aria-haspopup', 'true');
330
+ btn.setAttribute('aria-expanded', 'false');
331
+ btn.setAttribute('role', this.tabNavigation ? 'button' : 'menuitem');
332
+
280
333
  this._overflow = btn;
281
- this._initButtonAttrs(btn);
282
334
  },
283
335
  });
284
336
  this.addController(this._overflowController);
@@ -333,23 +385,6 @@ export const MenuBarMixin = (superClass) =>
333
385
  this.__detectOverflow();
334
386
  }
335
387
 
336
- /**
337
- * Override method inherited from `DisabledMixin`
338
- * to update the `disabled` property for the buttons
339
- * whenever the property changes on the menu bar.
340
- *
341
- * @param {boolean} newValue the new disabled value
342
- * @param {boolean} oldValue the previous disabled value
343
- * @override
344
- * @protected
345
- */
346
- _disabledChanged(newValue, oldValue) {
347
- super._disabledChanged(newValue, oldValue);
348
- if (oldValue !== newValue) {
349
- this.__updateButtonsDisabled(newValue);
350
- }
351
- }
352
-
353
388
  /**
354
389
  * A callback for the `_theme` property observer.
355
390
  * It propagates the host theme to the buttons and the sub menu.
@@ -359,14 +394,16 @@ export const MenuBarMixin = (superClass) =>
359
394
  */
360
395
  _themeChanged(theme, overflow, container) {
361
396
  if (overflow && container) {
362
- this._buttons.forEach((btn) => this._setButtonTheme(btn, theme));
397
+ this.__renderButtons(this.items);
363
398
  this.__detectOverflow();
364
- }
365
399
 
366
- if (theme) {
367
- this._subMenu.setAttribute('theme', theme);
368
- } else {
369
- this._subMenu.removeAttribute('theme');
400
+ if (theme) {
401
+ overflow.setAttribute('theme', theme);
402
+ this._subMenu.setAttribute('theme', theme);
403
+ } else {
404
+ overflow.removeAttribute('theme');
405
+ this._subMenu.removeAttribute('theme');
406
+ }
370
407
  }
371
408
  }
372
409
 
@@ -406,7 +443,7 @@ export const MenuBarMixin = (superClass) =>
406
443
  }
407
444
 
408
445
  /** @private */
409
- _menuItemsChanged(items, overflow, container) {
446
+ __updateButtons(items, disabled, overflow, container) {
410
447
  if (!overflow || !container) {
411
448
  return;
412
449
  }
@@ -414,19 +451,32 @@ export const MenuBarMixin = (superClass) =>
414
451
  if (items !== this._oldItems) {
415
452
  this._oldItems = items;
416
453
  this.__renderButtons(items);
454
+ this.__detectOverflow();
455
+ }
456
+
457
+ if (disabled !== this._oldDisabled) {
458
+ this._oldDisabled = disabled;
459
+ this.__renderButtons(items);
460
+ overflow.toggleAttribute('disabled', disabled);
417
461
  }
418
462
 
419
463
  const subMenu = this._subMenu;
420
464
  if (subMenu && subMenu.opened) {
421
- subMenu.close();
465
+ const button = subMenu._overlayElement.positionTarget;
466
+
467
+ // Close sub-menu if the corresponding button is no longer in the DOM,
468
+ // or if the item on it has been changed to no longer have children.
469
+ if (!button.isConnected || !Array.isArray(button.item.children) || button.item.children.length === 0) {
470
+ subMenu.close();
471
+ }
422
472
  }
423
473
  }
424
474
 
425
475
  /** @private */
426
- __i18nChanged(i18n, overflow) {
427
- if (overflow && i18n && i18n.moreOptions !== undefined) {
428
- if (i18n.moreOptions) {
429
- overflow.setAttribute('aria-label', i18n.moreOptions);
476
+ __i18nChanged(effectiveI18n, overflow) {
477
+ if (overflow && effectiveI18n && effectiveI18n.moreOptions !== undefined) {
478
+ if (effectiveI18n.moreOptions) {
479
+ overflow.setAttribute('aria-label', effectiveI18n.moreOptions);
430
480
  } else {
431
481
  overflow.removeAttribute('aria-label');
432
482
  }
@@ -442,9 +492,9 @@ export const MenuBarMixin = (superClass) =>
442
492
  /** @private */
443
493
  __restoreButtons(buttons) {
444
494
  buttons.forEach((button) => {
445
- button.disabled = (button.item && button.item.disabled) || this.disabled;
446
495
  button.style.visibility = '';
447
496
  button.style.position = '';
497
+ button.style.width = '';
448
498
 
449
499
  // Teleport item component back from "overflow" sub-menu
450
500
  const item = button.item && button.item.component;
@@ -464,14 +514,6 @@ export const MenuBarMixin = (superClass) =>
464
514
  item.removeAttribute('tabindex');
465
515
  }
466
516
 
467
- /** @private */
468
- __updateButtonsDisabled(disabled) {
469
- this._buttons.forEach((btn) => {
470
- // Disable the button if the entire menu-bar is disabled or the item alone is disabled
471
- btn.disabled = disabled || (btn.item && btn.item.disabled);
472
- });
473
- }
474
-
475
517
  /** @private */
476
518
  __updateOverflow(items) {
477
519
  this._overflow.item = { children: items };
@@ -505,7 +547,6 @@ export const MenuBarMixin = (superClass) =>
505
547
 
506
548
  // Save width for buttons with component
507
549
  btn.style.width = getComputedStyle(btn).width;
508
- btn.disabled = true;
509
550
  btn.style.visibility = 'hidden';
510
551
  btn.style.position = 'absolute';
511
552
  }
@@ -554,66 +595,17 @@ export const MenuBarMixin = (superClass) =>
554
595
  });
555
596
  }
556
597
 
557
- /** @protected */
558
- _removeButtons() {
559
- this._buttons.forEach((button) => {
560
- if (button !== this._overflow) {
561
- this.removeChild(button);
562
- }
563
- });
564
- }
565
-
566
- /** @protected */
567
- _initButton(item) {
568
- const button = document.createElement('vaadin-menu-bar-button');
569
-
570
- const itemCopy = { ...item };
571
- button.item = itemCopy;
572
-
573
- if (item.component) {
574
- const component = this.__getComponent(itemCopy);
575
- itemCopy.component = component;
576
- // Save item for overflow menu
577
- component.item = itemCopy;
578
- button.appendChild(component);
579
- } else if (item.text) {
580
- button.textContent = item.text;
581
- }
582
-
583
- if (item.className) {
584
- button.className = item.className;
585
- }
586
-
587
- button.disabled = item.disabled;
588
-
589
- return button;
590
- }
591
-
592
- /** @protected */
593
- _initButtonAttrs(button) {
594
- button.setAttribute('role', this.tabNavigation ? 'button' : 'menuitem');
595
-
596
- if (button === this._overflow || (button.item && button.item.children)) {
597
- button.setAttribute('aria-haspopup', 'true');
598
- button.setAttribute('aria-expanded', 'false');
599
- }
600
- }
601
-
602
- /** @protected */
603
- _setButtonTheme(btn, hostTheme) {
598
+ /** @private */
599
+ __getButtonTheme(item, hostTheme) {
604
600
  let theme = hostTheme;
605
601
 
606
602
  // Item theme takes precedence over host theme even if it's empty, as long as it's not undefined or null
607
- const itemTheme = btn.item && btn.item.theme;
603
+ const itemTheme = item && item.theme;
608
604
  if (itemTheme != null) {
609
605
  theme = Array.isArray(itemTheme) ? itemTheme.join(' ') : itemTheme;
610
606
  }
611
607
 
612
- if (theme) {
613
- btn.setAttribute('theme', theme);
614
- } else {
615
- btn.removeAttribute('theme');
616
- }
608
+ return theme;
617
609
  }
618
610
 
619
611
  /** @private */
@@ -638,21 +630,47 @@ export const MenuBarMixin = (superClass) =>
638
630
 
639
631
  /** @private */
640
632
  __renderButtons(items = []) {
641
- this._removeButtons();
642
-
643
- /* Empty array, do nothing */
644
- if (items.length === 0) {
645
- return;
646
- }
633
+ render(
634
+ html`
635
+ ${items.map((item) => {
636
+ const itemCopy = { ...item };
637
+ const hasChildren = Boolean(item && item.children);
638
+
639
+ if (itemCopy.component) {
640
+ const component = this.__getComponent(itemCopy);
641
+ itemCopy.component = component;
642
+ component.item = itemCopy;
643
+ }
647
644
 
648
- items.forEach((item) => {
649
- const button = this._initButton(item);
650
- this.insertBefore(button, this._overflow);
651
- this._initButtonAttrs(button);
652
- this._setButtonTheme(button, this._theme);
653
- });
645
+ return html`
646
+ <vaadin-menu-bar-button
647
+ .item="${itemCopy}"
648
+ .disabled="${this.disabled || item.disabled}"
649
+ role="${this.tabNavigation ? 'button' : 'menuitem'}"
650
+ aria-haspopup="${ifDefined(hasChildren ? 'true' : nothing)}"
651
+ aria-expanded="${ifDefined(hasChildren ? 'false' : nothing)}"
652
+ class="${ifDefined(item.className || nothing)}"
653
+ theme="${ifDefined(this.__getButtonTheme(item, this._theme) || nothing)}"
654
+ @click="${this.__onRootButtonClick}"
655
+ >${componentDirective(itemCopy)}</vaadin-menu-bar-button
656
+ >
657
+ `;
658
+ })}
659
+ `,
660
+ this,
661
+ { renderBefore: this._overflow },
662
+ );
663
+ }
654
664
 
655
- this.__detectOverflow();
665
+ /** @private */
666
+ __onRootButtonClick(event) {
667
+ const button = event.target;
668
+ // Propagate click event from button to the item component if it was outside
669
+ // it e.g. by calling `click()` on the button (used by the Flow counterpart).
670
+ if (button.item && button.item.component && !event.composedPath().includes(button.item.component)) {
671
+ event.stopPropagation();
672
+ button.item.component.click();
673
+ }
656
674
  }
657
675
 
658
676
  /**
@@ -763,12 +781,7 @@ export const MenuBarMixin = (superClass) =>
763
781
  */
764
782
  _setFocused(focused) {
765
783
  if (focused) {
766
- let target = this.querySelector('[tabindex="0"]');
767
- if (this.tabNavigation) {
768
- // Switch submenu on menu button Tab / Shift Tab
769
- target = this.querySelector('[focused]');
770
- this.__switchSubMenu(target);
771
- }
784
+ const target = this.tabNavigation ? this.querySelector('[focused]') : this.querySelector('[tabindex="0"]');
772
785
  if (target) {
773
786
  this._buttons.forEach((btn) => {
774
787
  this._setTabindex(btn, btn === target);
@@ -887,30 +900,15 @@ export const MenuBarMixin = (superClass) =>
887
900
  if (e.keyCode === 38 && item === list.items[0]) {
888
901
  this._close(true);
889
902
  }
890
- // ArrowLeft, or ArrowRight on non-parent submenu item
903
+ // ArrowLeft, or ArrowRight on non-parent submenu item,
891
904
  if (e.keyCode === 37 || (e.keyCode === 39 && !item._item.children)) {
892
905
  // Prevent ArrowLeft from being handled in context-menu
893
906
  e.stopImmediatePropagation();
894
907
  this._onKeyDown(e);
895
- } else if (e.keyCode === 9 && this.tabNavigation) {
896
- // Switch opened submenu on submenu item Tab / Shift Tab
897
- const items = this._getItems() || [];
898
- const currentIdx = items.indexOf(this.focused);
899
- const increment = e.shiftKey ? -1 : 1;
900
- let idx = currentIdx + increment;
901
- idx = this._getAvailableIndex(items, idx, increment, (item) => !isElementHidden(item));
902
- this.__switchSubMenu(items[idx]);
903
908
  }
904
- }
905
- }
906
909
 
907
- /** @private */
908
- __switchSubMenu(target) {
909
- const wasExpanded = this._expandedButton != null && this._expandedButton !== target;
910
- if (wasExpanded) {
911
- this._close();
912
- if (target.item && target.item.children) {
913
- this.__openSubMenu(target, true, { keepFocus: true });
910
+ if (e.key === 'Tab' && this.tabNavigation) {
911
+ this._onKeyDown(e);
914
912
  }
915
913
  }
916
914
  }
@@ -955,31 +953,21 @@ export const MenuBarMixin = (superClass) =>
955
953
  const overlay = subMenu._overlayElement;
956
954
  overlay.noVerticalOverlap = true;
957
955
 
958
- this._expandedButton = button;
959
-
960
- requestAnimationFrame(() => {
961
- // After changing items, buttons are recreated so the old button is
962
- // no longer in the DOM. Reset position target to null to prevent
963
- // overlay from closing due to target width / height equal to 0.
964
- if (overlay.positionTarget && !overlay.positionTarget.isConnected) {
965
- overlay.positionTarget = null;
966
- }
967
-
968
- button.dispatchEvent(
969
- new CustomEvent('opensubmenu', {
970
- detail: {
971
- children: items,
972
- },
973
- }),
974
- );
975
- this._hideTooltip(true);
976
-
977
- this._setExpanded(button, true);
956
+ this._hideTooltip(true);
978
957
 
979
- overlay.positionTarget = button;
980
- });
958
+ this._expandedButton = button;
959
+ this._setExpanded(button, true);
981
960
 
982
961
  this.style.pointerEvents = 'auto';
962
+ overlay.positionTarget = button;
963
+
964
+ button.dispatchEvent(
965
+ new CustomEvent('opensubmenu', {
966
+ detail: {
967
+ children: items,
968
+ },
969
+ }),
970
+ );
983
971
 
984
972
  overlay.addEventListener(
985
973
  'vaadin-overlay-open',
package/web-types.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/web-types",
3
3
  "name": "@vaadin/menu-bar",
4
- "version": "24.8.0-alpha2",
4
+ "version": "24.8.0-alpha20",
5
5
  "description-markup": "markdown",
6
6
  "contributions": {
7
7
  "html": {
8
8
  "elements": [
9
9
  {
10
10
  "name": "vaadin-menu-bar",
11
- "description": "`<vaadin-menu-bar>` is a Web Component providing a set of horizontally stacked buttons offering\nthe user quick access to a consistent set of commands. Each button can toggle a submenu with\nsupport for additional levels of nested menus.\n\nTo create the menu bar, first add the component to the page:\n\n```\n<vaadin-menu-bar></vaadin-menu-bar>\n```\n\nAnd then use [`items`](https://cdn.vaadin.com/vaadin-web-components/24.8.0-alpha2/#/elements/vaadin-menu-bar#property-items) property to initialize the structure:\n\n```\ndocument.querySelector('vaadin-menu-bar').items = [{text: 'File'}, {text: 'Edit'}];\n```\n\n### Styling\n\nThe following shadow DOM parts are exposed for styling:\n\nPart name | Description\n------------------|----------------\n`container` | The container wrapping menu bar buttons.\n\nThe following state attributes are available for styling:\n\nAttribute | Description\n--------------------|----------------------------------\n`disabled` | Set when the menu bar is disabled\n`has-single-button` | Set when there is only one button visible\n\nSee [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.\n\n### Internal components\n\nIn addition to `<vaadin-menu-bar>` itself, the following internal\ncomponents are themable:\n\n- `<vaadin-menu-bar-button>` - has the same API as [`<vaadin-button>`](https://cdn.vaadin.com/vaadin-web-components/24.8.0-alpha2/#/elements/vaadin-button).\n- `<vaadin-menu-bar-item>` - has the same API as [`<vaadin-item>`](https://cdn.vaadin.com/vaadin-web-components/24.8.0-alpha2/#/elements/vaadin-item).\n- `<vaadin-menu-bar-list-box>` - has the same API as [`<vaadin-list-box>`](https://cdn.vaadin.com/vaadin-web-components/24.8.0-alpha2/#/elements/vaadin-list-box).\n- `<vaadin-menu-bar-overlay>` - has the same API as [`<vaadin-overlay>`](https://cdn.vaadin.com/vaadin-web-components/24.8.0-alpha2/#/elements/vaadin-overlay).\n\nThe `<vaadin-menu-bar-item>` sub-menu elements have the following additional state attributes\non top of the built-in `<vaadin-item>` state attributes:\n\nAttribute | Description\n---------- |-------------\n`expanded` | Expanded parent item.\n\nNote: the `theme` attribute value set on `<vaadin-menu-bar>` is\npropagated to the internal components listed above.",
11
+ "description": "`<vaadin-menu-bar>` is a Web Component providing a set of horizontally stacked buttons offering\nthe user quick access to a consistent set of commands. Each button can toggle a submenu with\nsupport for additional levels of nested menus.\n\nTo create the menu bar, first add the component to the page:\n\n```\n<vaadin-menu-bar></vaadin-menu-bar>\n```\n\nAnd then use [`items`](https://cdn.vaadin.com/vaadin-web-components/24.8.0-alpha20/#/elements/vaadin-menu-bar#property-items) property to initialize the structure:\n\n```\ndocument.querySelector('vaadin-menu-bar').items = [{text: 'File'}, {text: 'Edit'}];\n```\n\n### Styling\n\nThe following shadow DOM parts are exposed for styling:\n\nPart name | Description\n------------------|----------------\n`container` | The container wrapping menu bar buttons.\n\nThe following state attributes are available for styling:\n\nAttribute | Description\n--------------------|----------------------------------\n`disabled` | Set when the menu bar is disabled\n`has-single-button` | Set when there is only one button visible\n\nSee [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.\n\n### Internal components\n\nIn addition to `<vaadin-menu-bar>` itself, the following internal\ncomponents are themable:\n\n- `<vaadin-menu-bar-button>` - has the same API as [`<vaadin-button>`](https://cdn.vaadin.com/vaadin-web-components/24.8.0-alpha20/#/elements/vaadin-button).\n- `<vaadin-menu-bar-item>` - has the same API as [`<vaadin-item>`](https://cdn.vaadin.com/vaadin-web-components/24.8.0-alpha20/#/elements/vaadin-item).\n- `<vaadin-menu-bar-list-box>` - has the same API as [`<vaadin-list-box>`](https://cdn.vaadin.com/vaadin-web-components/24.8.0-alpha20/#/elements/vaadin-list-box).\n- `<vaadin-menu-bar-overlay>` - has the same API as [`<vaadin-overlay>`](https://cdn.vaadin.com/vaadin-web-components/24.8.0-alpha20/#/elements/vaadin-overlay).\n\nThe `<vaadin-menu-bar-item>` sub-menu elements have the following additional state attributes\non top of the built-in `<vaadin-item>` state attributes:\n\nAttribute | Description\n---------- |-------------\n`expanded` | Expanded parent item.\n\nNote: the `theme` attribute value set on `<vaadin-menu-bar>` is\npropagated to the internal components listed above.",
12
12
  "attributes": [
13
13
  {
14
14
  "name": "disabled",
@@ -21,6 +21,15 @@
21
21
  ]
22
22
  }
23
23
  },
24
+ {
25
+ "name": "i18n",
26
+ "description": "The object used to localize this component. To change the default\nlocalization, replace this with an object that provides all properties, or\njust the individual properties you want to change.\n\nShould be overridden by subclasses to provide a custom JSDoc with the\ndefault I18N properties.",
27
+ "value": {
28
+ "type": [
29
+ "Object"
30
+ ]
31
+ }
32
+ },
24
33
  {
25
34
  "name": "overlay-class",
26
35
  "description": "A space-delimited list of CSS class names\nto set on each sub-menu overlay element.",
@@ -91,20 +100,20 @@
91
100
  }
92
101
  },
93
102
  {
94
- "name": "items",
95
- "description": "Defines a hierarchical structure, where root level items represent menu bar buttons,\nand `children` property configures a submenu with items to be opened below\nthe button on click, Enter, Space, Up and Down arrow keys.\n\n#### Example\n\n```js\nmenubar.items = [\n {\n text: 'File',\n className: 'file',\n children: [\n {text: 'Open', className: 'file open'}\n {text: 'Auto Save', checked: true},\n ]\n },\n {component: 'hr'},\n {\n text: 'Edit',\n children: [\n {text: 'Undo', disabled: true},\n {text: 'Redo'}\n ]\n },\n {text: 'Help'}\n];\n```\n\n#### Disabled buttons\n\nWhen disabled, menu bar buttons (root-level items) are rendered\nas \"dimmed\" and prevent all user interactions (mouse and keyboard).\n\nSince disabled buttons are not focusable and cannot react to hover\nevents by default, it can cause accessibility issues by making them\nentirely invisible to assistive technologies, and prevents the use\nof Tooltips to explain why the action is not available. This can be\naddressed by enabling the feature flag `accessibleDisabledButtons`,\nwhich makes disabled buttons focusable and hoverable, while still\npreventing them from being triggered:\n\n```\n// Set before any menu bar is attached to the DOM.\nwindow.Vaadin.featureFlags.accessibleDisabledButtons = true;\n```\n```\n// Set before any menu bar is attached to the DOM.\nwindow.Vaadin.featureFlags.accessibleDisabledButtons = true;\n```",
103
+ "name": "i18n",
104
+ "description": "The object used to localize this component. To change the default\nlocalization, replace this with an object that provides all properties, or\njust the individual properties you want to change.\n\nThe object has the following JSON structure and default values:\n```\n{\n moreOptions: 'More options'\n}\n```",
96
105
  "value": {
97
106
  "type": [
98
- "Array.<MenuBarItem>"
107
+ "MenuBarI18n"
99
108
  ]
100
109
  }
101
110
  },
102
111
  {
103
- "name": "i18n",
104
- "description": "The object used to localize this component.\nTo change the default localization, replace the entire\n`i18n` object with a custom one.\n\nTo update individual properties, extend the existing i18n object like so:\n```\nmenuBar.i18n = {\n ...menuBar.i18n,\n moreOptions: 'More options'\n}\n```\n\nThe object has the following JSON structure and default values:\n```\n{\n moreOptions: 'More options'\n}\n```",
112
+ "name": "items",
113
+ "description": "Defines a hierarchical structure, where root level items represent menu bar buttons,\nand `children` property configures a submenu with items to be opened below\nthe button on click, Enter, Space, Up and Down arrow keys.\n\n#### Example\n\n```js\nmenubar.items = [\n {\n text: 'File',\n className: 'file',\n children: [\n {text: 'Open', className: 'file open'}\n {text: 'Auto Save', checked: true},\n ]\n },\n {component: 'hr'},\n {\n text: 'Edit',\n children: [\n {text: 'Undo', disabled: true},\n {text: 'Redo'}\n ]\n },\n {text: 'Help'}\n];\n```\n\n#### Disabled buttons\n\nWhen disabled, menu bar buttons (root-level items) are rendered\nas \"dimmed\" and prevent all user interactions (mouse and keyboard).\n\nSince disabled buttons are not focusable and cannot react to hover\nevents by default, it can cause accessibility issues by making them\nentirely invisible to assistive technologies, and prevents the use\nof Tooltips to explain why the action is not available. This can be\naddressed by enabling the feature flag `accessibleDisabledButtons`,\nwhich makes disabled buttons focusable and hoverable, while still\npreventing them from being triggered:\n\n```\n// Set before any menu bar is attached to the DOM.\nwindow.Vaadin.featureFlags.accessibleDisabledButtons = true;\n```\n```\n// Set before any menu bar is attached to the DOM.\nwindow.Vaadin.featureFlags.accessibleDisabledButtons = true;\n```",
105
114
  "value": {
106
115
  "type": [
107
- "MenuBarI18n"
116
+ "Array.<MenuBarItem>"
108
117
  ]
109
118
  }
110
119
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/web-types",
3
3
  "name": "@vaadin/menu-bar",
4
- "version": "24.8.0-alpha2",
4
+ "version": "24.8.0-alpha20",
5
5
  "description-markup": "markdown",
6
6
  "framework": "lit",
7
7
  "framework-config": {
@@ -16,7 +16,7 @@
16
16
  "elements": [
17
17
  {
18
18
  "name": "vaadin-menu-bar",
19
- "description": "`<vaadin-menu-bar>` is a Web Component providing a set of horizontally stacked buttons offering\nthe user quick access to a consistent set of commands. Each button can toggle a submenu with\nsupport for additional levels of nested menus.\n\nTo create the menu bar, first add the component to the page:\n\n```\n<vaadin-menu-bar></vaadin-menu-bar>\n```\n\nAnd then use [`items`](https://cdn.vaadin.com/vaadin-web-components/24.8.0-alpha2/#/elements/vaadin-menu-bar#property-items) property to initialize the structure:\n\n```\ndocument.querySelector('vaadin-menu-bar').items = [{text: 'File'}, {text: 'Edit'}];\n```\n\n### Styling\n\nThe following shadow DOM parts are exposed for styling:\n\nPart name | Description\n------------------|----------------\n`container` | The container wrapping menu bar buttons.\n\nThe following state attributes are available for styling:\n\nAttribute | Description\n--------------------|----------------------------------\n`disabled` | Set when the menu bar is disabled\n`has-single-button` | Set when there is only one button visible\n\nSee [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.\n\n### Internal components\n\nIn addition to `<vaadin-menu-bar>` itself, the following internal\ncomponents are themable:\n\n- `<vaadin-menu-bar-button>` - has the same API as [`<vaadin-button>`](https://cdn.vaadin.com/vaadin-web-components/24.8.0-alpha2/#/elements/vaadin-button).\n- `<vaadin-menu-bar-item>` - has the same API as [`<vaadin-item>`](https://cdn.vaadin.com/vaadin-web-components/24.8.0-alpha2/#/elements/vaadin-item).\n- `<vaadin-menu-bar-list-box>` - has the same API as [`<vaadin-list-box>`](https://cdn.vaadin.com/vaadin-web-components/24.8.0-alpha2/#/elements/vaadin-list-box).\n- `<vaadin-menu-bar-overlay>` - has the same API as [`<vaadin-overlay>`](https://cdn.vaadin.com/vaadin-web-components/24.8.0-alpha2/#/elements/vaadin-overlay).\n\nThe `<vaadin-menu-bar-item>` sub-menu elements have the following additional state attributes\non top of the built-in `<vaadin-item>` state attributes:\n\nAttribute | Description\n---------- |-------------\n`expanded` | Expanded parent item.\n\nNote: the `theme` attribute value set on `<vaadin-menu-bar>` is\npropagated to the internal components listed above.",
19
+ "description": "`<vaadin-menu-bar>` is a Web Component providing a set of horizontally stacked buttons offering\nthe user quick access to a consistent set of commands. Each button can toggle a submenu with\nsupport for additional levels of nested menus.\n\nTo create the menu bar, first add the component to the page:\n\n```\n<vaadin-menu-bar></vaadin-menu-bar>\n```\n\nAnd then use [`items`](https://cdn.vaadin.com/vaadin-web-components/24.8.0-alpha20/#/elements/vaadin-menu-bar#property-items) property to initialize the structure:\n\n```\ndocument.querySelector('vaadin-menu-bar').items = [{text: 'File'}, {text: 'Edit'}];\n```\n\n### Styling\n\nThe following shadow DOM parts are exposed for styling:\n\nPart name | Description\n------------------|----------------\n`container` | The container wrapping menu bar buttons.\n\nThe following state attributes are available for styling:\n\nAttribute | Description\n--------------------|----------------------------------\n`disabled` | Set when the menu bar is disabled\n`has-single-button` | Set when there is only one button visible\n\nSee [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.\n\n### Internal components\n\nIn addition to `<vaadin-menu-bar>` itself, the following internal\ncomponents are themable:\n\n- `<vaadin-menu-bar-button>` - has the same API as [`<vaadin-button>`](https://cdn.vaadin.com/vaadin-web-components/24.8.0-alpha20/#/elements/vaadin-button).\n- `<vaadin-menu-bar-item>` - has the same API as [`<vaadin-item>`](https://cdn.vaadin.com/vaadin-web-components/24.8.0-alpha20/#/elements/vaadin-item).\n- `<vaadin-menu-bar-list-box>` - has the same API as [`<vaadin-list-box>`](https://cdn.vaadin.com/vaadin-web-components/24.8.0-alpha20/#/elements/vaadin-list-box).\n- `<vaadin-menu-bar-overlay>` - has the same API as [`<vaadin-overlay>`](https://cdn.vaadin.com/vaadin-web-components/24.8.0-alpha20/#/elements/vaadin-overlay).\n\nThe `<vaadin-menu-bar-item>` sub-menu elements have the following additional state attributes\non top of the built-in `<vaadin-item>` state attributes:\n\nAttribute | Description\n---------- |-------------\n`expanded` | Expanded parent item.\n\nNote: the `theme` attribute value set on `<vaadin-menu-bar>` is\npropagated to the internal components listed above.",
20
20
  "extension": true,
21
21
  "attributes": [
22
22
  {
@@ -48,15 +48,15 @@
48
48
  }
49
49
  },
50
50
  {
51
- "name": ".items",
52
- "description": "Defines a hierarchical structure, where root level items represent menu bar buttons,\nand `children` property configures a submenu with items to be opened below\nthe button on click, Enter, Space, Up and Down arrow keys.\n\n#### Example\n\n```js\nmenubar.items = [\n {\n text: 'File',\n className: 'file',\n children: [\n {text: 'Open', className: 'file open'}\n {text: 'Auto Save', checked: true},\n ]\n },\n {component: 'hr'},\n {\n text: 'Edit',\n children: [\n {text: 'Undo', disabled: true},\n {text: 'Redo'}\n ]\n },\n {text: 'Help'}\n];\n```\n\n#### Disabled buttons\n\nWhen disabled, menu bar buttons (root-level items) are rendered\nas \"dimmed\" and prevent all user interactions (mouse and keyboard).\n\nSince disabled buttons are not focusable and cannot react to hover\nevents by default, it can cause accessibility issues by making them\nentirely invisible to assistive technologies, and prevents the use\nof Tooltips to explain why the action is not available. This can be\naddressed by enabling the feature flag `accessibleDisabledButtons`,\nwhich makes disabled buttons focusable and hoverable, while still\npreventing them from being triggered:\n\n```\n// Set before any menu bar is attached to the DOM.\nwindow.Vaadin.featureFlags.accessibleDisabledButtons = true;\n```\n```\n// Set before any menu bar is attached to the DOM.\nwindow.Vaadin.featureFlags.accessibleDisabledButtons = true;\n```",
51
+ "name": ".i18n",
52
+ "description": "The object used to localize this component. To change the default\nlocalization, replace this with an object that provides all properties, or\njust the individual properties you want to change.\n\nThe object has the following JSON structure and default values:\n```\n{\n moreOptions: 'More options'\n}\n```",
53
53
  "value": {
54
54
  "kind": "expression"
55
55
  }
56
56
  },
57
57
  {
58
- "name": ".i18n",
59
- "description": "The object used to localize this component.\nTo change the default localization, replace the entire\n`i18n` object with a custom one.\n\nTo update individual properties, extend the existing i18n object like so:\n```\nmenuBar.i18n = {\n ...menuBar.i18n,\n moreOptions: 'More options'\n}\n```\n\nThe object has the following JSON structure and default values:\n```\n{\n moreOptions: 'More options'\n}\n```",
58
+ "name": ".items",
59
+ "description": "Defines a hierarchical structure, where root level items represent menu bar buttons,\nand `children` property configures a submenu with items to be opened below\nthe button on click, Enter, Space, Up and Down arrow keys.\n\n#### Example\n\n```js\nmenubar.items = [\n {\n text: 'File',\n className: 'file',\n children: [\n {text: 'Open', className: 'file open'}\n {text: 'Auto Save', checked: true},\n ]\n },\n {component: 'hr'},\n {\n text: 'Edit',\n children: [\n {text: 'Undo', disabled: true},\n {text: 'Redo'}\n ]\n },\n {text: 'Help'}\n];\n```\n\n#### Disabled buttons\n\nWhen disabled, menu bar buttons (root-level items) are rendered\nas \"dimmed\" and prevent all user interactions (mouse and keyboard).\n\nSince disabled buttons are not focusable and cannot react to hover\nevents by default, it can cause accessibility issues by making them\nentirely invisible to assistive technologies, and prevents the use\nof Tooltips to explain why the action is not available. This can be\naddressed by enabling the feature flag `accessibleDisabledButtons`,\nwhich makes disabled buttons focusable and hoverable, while still\npreventing them from being triggered:\n\n```\n// Set before any menu bar is attached to the DOM.\nwindow.Vaadin.featureFlags.accessibleDisabledButtons = true;\n```\n```\n// Set before any menu bar is attached to the DOM.\nwindow.Vaadin.featureFlags.accessibleDisabledButtons = true;\n```",
60
60
  "value": {
61
61
  "kind": "expression"
62
62
  }