@vaadin/menu-bar 25.0.0-alpha9 → 25.0.0-beta2

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.
Files changed (39) hide show
  1. package/package.json +15 -18
  2. package/src/styles/vaadin-menu-bar-base-styles.js +7 -6
  3. package/src/styles/vaadin-menu-bar-button-base-styles.js +1 -1
  4. package/src/vaadin-menu-bar-button.d.ts +19 -0
  5. package/src/vaadin-menu-bar-button.js +3 -1
  6. package/src/vaadin-menu-bar-item.js +1 -1
  7. package/src/vaadin-menu-bar-list-box.js +1 -1
  8. package/src/vaadin-menu-bar-mixin.d.ts +5 -12
  9. package/src/vaadin-menu-bar-mixin.js +54 -48
  10. package/src/vaadin-menu-bar-overlay.js +1 -1
  11. package/src/vaadin-menu-bar-submenu.d.ts +20 -0
  12. package/src/vaadin-menu-bar-submenu.js +69 -3
  13. package/src/vaadin-menu-bar.js +1 -1
  14. package/vaadin-menu-bar.js +1 -1
  15. package/web-types.json +4 -26
  16. package/web-types.lit.json +4 -11
  17. package/src/styles/vaadin-menu-bar-button-core-styles.d.ts +0 -8
  18. package/src/styles/vaadin-menu-bar-button-core-styles.js +0 -16
  19. package/src/styles/vaadin-menu-bar-core-styles.d.ts +0 -8
  20. package/src/styles/vaadin-menu-bar-core-styles.js +0 -24
  21. package/src/styles/vaadin-menu-bar-item-core-styles.d.ts +0 -8
  22. package/src/styles/vaadin-menu-bar-item-core-styles.js +0 -8
  23. package/src/styles/vaadin-menu-bar-overlay-core-styles.d.ts +0 -8
  24. package/src/styles/vaadin-menu-bar-overlay-core-styles.js +0 -9
  25. package/src/vaadin-menu-bar-submenu-mixin.js +0 -66
  26. package/theme/lumo/vaadin-menu-bar-button-styles.d.ts +0 -1
  27. package/theme/lumo/vaadin-menu-bar-button-styles.js +0 -128
  28. package/theme/lumo/vaadin-menu-bar-button.d.ts +0 -2
  29. package/theme/lumo/vaadin-menu-bar-button.js +0 -2
  30. package/theme/lumo/vaadin-menu-bar-item-styles.d.ts +0 -2
  31. package/theme/lumo/vaadin-menu-bar-item-styles.js +0 -27
  32. package/theme/lumo/vaadin-menu-bar-list-box-styles.d.ts +0 -1
  33. package/theme/lumo/vaadin-menu-bar-list-box-styles.js +0 -5
  34. package/theme/lumo/vaadin-menu-bar-overlay-styles.d.ts +0 -1
  35. package/theme/lumo/vaadin-menu-bar-overlay-styles.js +0 -13
  36. package/theme/lumo/vaadin-menu-bar-styles.d.ts +0 -1
  37. package/theme/lumo/vaadin-menu-bar-styles.js +0 -17
  38. package/theme/lumo/vaadin-menu-bar.d.ts +0 -6
  39. package/theme/lumo/vaadin-menu-bar.js +0 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/menu-bar",
3
- "version": "25.0.0-alpha9",
3
+ "version": "25.0.0-beta2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -21,9 +21,6 @@
21
21
  "type": "module",
22
22
  "files": [
23
23
  "src",
24
- "!src/styles/*-base-styles.d.ts",
25
- "!src/styles/*-base-styles.js",
26
- "theme",
27
24
  "vaadin-*.d.ts",
28
25
  "vaadin-*.js",
29
26
  "web-types.json",
@@ -37,27 +34,27 @@
37
34
  ],
38
35
  "dependencies": {
39
36
  "@open-wc/dedupe-mixin": "^1.3.0",
40
- "@vaadin/a11y-base": "25.0.0-alpha9",
41
- "@vaadin/button": "25.0.0-alpha9",
42
- "@vaadin/component-base": "25.0.0-alpha9",
43
- "@vaadin/context-menu": "25.0.0-alpha9",
44
- "@vaadin/item": "25.0.0-alpha9",
45
- "@vaadin/list-box": "25.0.0-alpha9",
46
- "@vaadin/overlay": "25.0.0-alpha9",
47
- "@vaadin/vaadin-lumo-styles": "25.0.0-alpha9",
48
- "@vaadin/vaadin-themable-mixin": "25.0.0-alpha9",
37
+ "@vaadin/a11y-base": "25.0.0-beta2",
38
+ "@vaadin/button": "25.0.0-beta2",
39
+ "@vaadin/component-base": "25.0.0-beta2",
40
+ "@vaadin/context-menu": "25.0.0-beta2",
41
+ "@vaadin/item": "25.0.0-beta2",
42
+ "@vaadin/list-box": "25.0.0-beta2",
43
+ "@vaadin/overlay": "25.0.0-beta2",
44
+ "@vaadin/vaadin-themable-mixin": "25.0.0-beta2",
49
45
  "lit": "^3.0.0"
50
46
  },
51
47
  "devDependencies": {
52
- "@vaadin/chai-plugins": "25.0.0-alpha9",
53
- "@vaadin/icon": "25.0.0-alpha9",
54
- "@vaadin/test-runner-commands": "25.0.0-alpha9",
48
+ "@vaadin/chai-plugins": "25.0.0-beta2",
49
+ "@vaadin/icon": "25.0.0-beta2",
50
+ "@vaadin/test-runner-commands": "25.0.0-beta2",
55
51
  "@vaadin/testing-helpers": "^2.0.0",
56
- "sinon": "^18.0.0"
52
+ "@vaadin/vaadin-lumo-styles": "25.0.0-beta2",
53
+ "sinon": "^21.0.0"
57
54
  },
58
55
  "web-types": [
59
56
  "web-types.json",
60
57
  "web-types.lit.json"
61
58
  ],
62
- "gitHead": "bbe4720721e0955ffc87a79b412bee38b1f0eb1e"
59
+ "gitHead": "e078f8371ae266f05c7ca1ec25686cc489c83f24"
63
60
  }
@@ -17,16 +17,15 @@ export const menuBarStyles = css`
17
17
  [part='container'] {
18
18
  display: flex;
19
19
  flex-wrap: nowrap;
20
- margin: calc((var(--vaadin-focus-ring-width) + 1px) * -1);
20
+ margin: calc((var(--vaadin-focus-ring-width) + 2px) * -1);
21
21
  overflow: hidden;
22
- padding: calc(var(--vaadin-focus-ring-width) + 1px);
22
+ padding: calc(var(--vaadin-focus-ring-width) + 2px);
23
23
  position: relative;
24
24
  width: 100%;
25
25
  --_gap: var(--vaadin-menu-bar-gap, 0px);
26
26
  --_bw: var(--vaadin-button-border-width, 1px);
27
27
  gap: var(--_gap);
28
28
  --_rad-button: var(--vaadin-button-border-radius, var(--vaadin-radius-m));
29
- --_rad: min(var(--_gap) * 1000, var(--_rad-button));
30
29
  }
31
30
 
32
31
  ::slotted(vaadin-menu-bar-button:not(:first-of-type)) {
@@ -34,16 +33,18 @@ export const menuBarStyles = css`
34
33
  }
35
34
 
36
35
  ::slotted(vaadin-menu-bar-button) {
37
- border-radius: var(--_rad);
36
+ border-radius: 0;
38
37
  }
39
38
 
40
39
  ::slotted([first-visible]),
41
- :host([has-single-button]) ::slotted([slot='overflow']) {
40
+ :host([has-single-button]) ::slotted([slot='overflow']),
41
+ ::slotted(vaadin-menu-bar-button[theme~='tertiary']) {
42
42
  border-start-start-radius: var(--_rad-button);
43
43
  border-end-start-radius: var(--_rad-button);
44
44
  }
45
45
 
46
- ::slotted(:is([last-visible], [slot='overflow'])) {
46
+ ::slotted(:is([last-visible], [slot='overflow'])),
47
+ ::slotted(vaadin-menu-bar-button[theme~='tertiary']) {
47
48
  border-start-end-radius: var(--_rad-button);
48
49
  border-end-end-radius: var(--_rad-button);
49
50
  }
@@ -32,7 +32,7 @@ export const menuBarButtonStyles = css`
32
32
  background: currentColor;
33
33
  content: '';
34
34
  height: var(--vaadin-icon-size, 1lh);
35
- mask-image: var(--_vaadin-icon-chevron-down);
35
+ mask: var(--_vaadin-icon-chevron-down) 50% / var(--vaadin-icon-visual-size, 100%) no-repeat;
36
36
  width: var(--vaadin-icon-size, 1lh);
37
37
  }
38
38
 
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2019 - 2025 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ import { Button } from '@vaadin/button/src/vaadin-button.js';
7
+
8
+ /**
9
+ * An element used internally by `<vaadin-menu-bar>`. Not intended to be used separately.
10
+ */
11
+ declare class MenuBarButton extends Button {}
12
+
13
+ declare global {
14
+ interface HTMLElementTagNameMap {
15
+ 'vaadin-menu-bar-button': MenuBarButton;
16
+ }
17
+ }
18
+
19
+ export { MenuBarButton };
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { Button } from '@vaadin/button/src/vaadin-button.js';
7
7
  import { defineCustomElement } from '@vaadin/component-base/src/define.js';
8
- import { menuBarButtonStyles } from './styles/vaadin-menu-bar-button-core-styles.js';
8
+ import { menuBarButtonStyles } from './styles/vaadin-menu-bar-button-base-styles.js';
9
9
 
10
10
  /**
11
11
  * An element used internally by `<vaadin-menu-bar>`. Not intended to be used separately.
@@ -54,3 +54,5 @@ class MenuBarButton extends Button {
54
54
  }
55
55
 
56
56
  defineCustomElement(MenuBarButton);
57
+
58
+ export { MenuBarButton };
@@ -10,7 +10,7 @@ import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
10
10
  import { ItemMixin } from '@vaadin/item/src/vaadin-item-mixin.js';
11
11
  import { LumoInjectionMixin } from '@vaadin/vaadin-themable-mixin/lumo-injection-mixin.js';
12
12
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
13
- import { menuBarItemStyles } from './styles/vaadin-menu-bar-item-core-styles.js';
13
+ import { menuBarItemStyles } from './styles/vaadin-menu-bar-item-base-styles.js';
14
14
 
15
15
  /**
16
16
  * An element used internally by `<vaadin-menu-bar>`. Not intended to be used separately.
@@ -8,7 +8,7 @@ import { ListMixin } from '@vaadin/a11y-base/src/list-mixin.js';
8
8
  import { defineCustomElement } from '@vaadin/component-base/src/define.js';
9
9
  import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js';
10
10
  import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
11
- import { listBoxStyles } from '@vaadin/list-box/src/styles/vaadin-list-box-core-styles.js';
11
+ import { listBoxStyles } from '@vaadin/list-box/src/styles/vaadin-list-box-base-styles.js';
12
12
  import { LumoInjectionMixin } from '@vaadin/vaadin-themable-mixin/lumo-injection-mixin.js';
13
13
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
14
14
 
@@ -10,6 +10,7 @@ import type { KeyboardDirectionMixinClass } from '@vaadin/a11y-base/src/keyboard
10
10
  import type { KeyboardMixinClass } from '@vaadin/a11y-base/src/keyboard-mixin.js';
11
11
  import type { I18nMixinClass } from '@vaadin/component-base/src/i18n-mixin.js';
12
12
  import type { ResizeMixinClass } from '@vaadin/component-base/src/resize-mixin.js';
13
+ import type { MenuBarButton } from './vaadin-menu-bar-button.js';
13
14
 
14
15
  export type MenuBarItem<TItemData extends object = object> = {
15
16
  /**
@@ -113,7 +114,7 @@ export declare class MenuBarMixinClass<TItem extends MenuBarItem = MenuBarItem>
113
114
  * which makes disabled buttons focusable and hoverable, while still
114
115
  * preventing them from being triggered:
115
116
  *
116
- * ```
117
+ * ```js
117
118
  * // Set before any menu bar is attached to the DOM.
118
119
  * window.Vaadin.featureFlags.accessibleDisabledButtons = true;
119
120
  * ```
@@ -126,7 +127,7 @@ export declare class MenuBarMixinClass<TItem extends MenuBarItem = MenuBarItem>
126
127
  * just the individual properties you want to change.
127
128
  *
128
129
  * The object has the following JSON structure and default values:
129
- * ```
130
+ * ```js
130
131
  * {
131
132
  * moreOptions: 'More options'
132
133
  * }
@@ -134,14 +135,6 @@ export declare class MenuBarMixinClass<TItem extends MenuBarItem = MenuBarItem>
134
135
  */
135
136
  i18n: MenuBarI18n;
136
137
 
137
- /**
138
- * A space-delimited list of CSS class names
139
- * to set on each sub-menu overlay element.
140
- *
141
- * @attr {string} overlay-class
142
- */
143
- overlayClass: string;
144
-
145
138
  /**
146
139
  * If true, the submenu will open on hover (mouseover) instead of click.
147
140
  * @attr {boolean} open-on-hover
@@ -167,11 +160,11 @@ export declare class MenuBarMixinClass<TItem extends MenuBarItem = MenuBarItem>
167
160
  */
168
161
  close(): void;
169
162
 
170
- protected readonly _buttons: HTMLElement[];
163
+ protected readonly _buttons: MenuBarButton[];
171
164
 
172
165
  protected readonly _container: HTMLElement;
173
166
 
174
- protected readonly _overflow: HTMLElement;
167
+ protected readonly _overflow: MenuBarButton;
175
168
 
176
169
  protected _hasOverflow: boolean;
177
170
  }
@@ -8,7 +8,7 @@ import { Directive, directive } from 'lit/directive.js';
8
8
  import { ifDefined } from 'lit/directives/if-defined.js';
9
9
  import { DisabledMixin } from '@vaadin/a11y-base/src/disabled-mixin.js';
10
10
  import { FocusMixin } from '@vaadin/a11y-base/src/focus-mixin.js';
11
- import { isElementFocused, isKeyboardActive } from '@vaadin/a11y-base/src/focus-utils.js';
11
+ import { isElementFocused, isElementHidden, isKeyboardActive } from '@vaadin/a11y-base/src/focus-utils.js';
12
12
  import { KeyboardDirectionMixin } from '@vaadin/a11y-base/src/keyboard-direction-mixin.js';
13
13
  import { microTask } from '@vaadin/component-base/src/async.js';
14
14
  import { Debouncer } from '@vaadin/component-base/src/debounce.js';
@@ -76,10 +76,10 @@ export const MenuBarMixin = (superClass) =>
76
76
  * @property {string} text - Text to be set as the menu button component's textContent.
77
77
  * @property {string} tooltip - Text to be set as the menu button's tooltip.
78
78
  * Requires a `<vaadin-tooltip slot="tooltip">` element to be added inside the `<vaadin-menu-bar>`.
79
- * @property {union: string | object} component - The component to represent the button content.
79
+ * @property {string | HTMLElement} component - The component to represent the button content.
80
80
  * Either a tagName or an element instance. Defaults to "vaadin-menu-bar-item".
81
81
  * @property {boolean} disabled - If true, the button is disabled and cannot be activated.
82
- * @property {union: string | string[]} theme - Theme(s) to be set as the theme attribute of the button, overriding any theme set on the menu bar.
82
+ * @property {string | string[]} theme - Theme(s) to be set as the theme attribute of the button, overriding any theme set on the menu bar.
83
83
  * @property {SubMenuItem[]} children - Array of submenu items.
84
84
  */
85
85
 
@@ -87,7 +87,7 @@ export const MenuBarMixin = (superClass) =>
87
87
  * @typedef SubMenuItem
88
88
  * @type {object}
89
89
  * @property {string} text - Text to be set as the menu item component's textContent.
90
- * @property {union: string | object} component - The component to represent the item.
90
+ * @property {string | HTMLElement} component - The component to represent the item.
91
91
  * Either a tagName or an element instance. Defaults to "vaadin-menu-bar-item".
92
92
  * @property {boolean} disabled - If true, the item is disabled and cannot be selected.
93
93
  * @property {boolean} checked - If true, the item shows a checkmark next to it.
@@ -136,12 +136,7 @@ export const MenuBarMixin = (superClass) =>
136
136
  * which makes disabled buttons focusable and hoverable, while still
137
137
  * preventing them from being triggered:
138
138
  *
139
- * ```
140
- * // Set before any menu bar is attached to the DOM.
141
- * window.Vaadin.featureFlags.accessibleDisabledButtons = true;
142
- * ```
143
- *
144
- * ```
139
+ * ```js
145
140
  * // Set before any menu bar is attached to the DOM.
146
141
  * window.Vaadin.featureFlags.accessibleDisabledButtons = true;
147
142
  * ```
@@ -154,16 +149,6 @@ export const MenuBarMixin = (superClass) =>
154
149
  value: () => [],
155
150
  },
156
151
 
157
- /**
158
- * A space-delimited list of CSS class names
159
- * to set on each sub-menu overlay element.
160
- *
161
- * @attr {string} overlay-class
162
- */
163
- overlayClass: {
164
- type: String,
165
- },
166
-
167
152
  /**
168
153
  * If true, the submenu will open on hover (mouseover) instead of click.
169
154
  * @attr {boolean} open-on-hover
@@ -200,7 +185,7 @@ export const MenuBarMixin = (superClass) =>
200
185
  * just the individual properties you want to change.
201
186
  *
202
187
  * The object has the following JSON structure and default values:
203
- * ```
188
+ * ```js
204
189
  * {
205
190
  * moreOptions: 'More options'
206
191
  * }
@@ -348,10 +333,6 @@ export const MenuBarMixin = (superClass) =>
348
333
  this.__updateSubMenu();
349
334
  }
350
335
 
351
- if (props.has('overlayClass')) {
352
- this._subMenu.overlayClass = this.overlayClass;
353
- }
354
-
355
336
  if (props.has('_theme')) {
356
337
  this._themeChanged(this._theme);
357
338
  }
@@ -428,7 +409,7 @@ export const MenuBarMixin = (superClass) =>
428
409
  __updateSubMenu() {
429
410
  const subMenu = this._subMenu;
430
411
  if (subMenu && subMenu.opened) {
431
- const button = subMenu._overlayElement.positionTarget;
412
+ const button = subMenu._positionTarget;
432
413
 
433
414
  // Close sub-menu if the corresponding button is no longer in the DOM,
434
415
  // or if the item on it has been changed to no longer have children.
@@ -519,12 +500,6 @@ export const MenuBarMixin = (superClass) =>
519
500
 
520
501
  const items = buttons.filter((b) => !remaining.includes(b)).map((b) => b.item);
521
502
  this.__updateOverflow(items);
522
-
523
- // Ensure there is at least one button with tabindex set to 0
524
- // so that menu-bar is not skipped when navigating with Tab
525
- if (remaining.length && !remaining.some((btn) => btn.getAttribute('tabindex') === '0')) {
526
- this._setTabindex(remaining[remaining.length - 1], true);
527
- }
528
503
  }
529
504
  }
530
505
 
@@ -555,13 +530,23 @@ export const MenuBarMixin = (superClass) =>
555
530
  const isSingleButton = newOverflowCount === buttons.length || (newOverflowCount === 0 && buttons.length === 1);
556
531
  this.toggleAttribute('has-single-button', isSingleButton);
557
532
 
533
+ // Collect visible buttons to detect if tabindex should be updated
534
+ const visibleButtons = buttons.filter((btn) => btn.style.visibility !== 'hidden');
535
+
536
+ if (!visibleButtons.length) {
537
+ // If all buttons except overflow are hidden, set tabindex on it
538
+ this._overflow.setAttribute('tabindex', '0');
539
+ } else if (!visibleButtons.some((btn) => btn.getAttribute('tabindex') === '0')) {
540
+ // Ensure there is at least one button with tabindex set to 0
541
+ // so that menu-bar is not skipped when navigating with Tab
542
+ this._setTabindex(visibleButtons[visibleButtons.length - 1], true);
543
+ }
544
+
558
545
  // Apply first/last visible attributes to the visible buttons
559
- buttons
560
- .filter((btn) => btn.style.visibility !== 'hidden')
561
- .forEach((btn, index, visibleButtons) => {
562
- btn.toggleAttribute('first-visible', index === 0);
563
- btn.toggleAttribute('last-visible', !this._hasOverflow && index === visibleButtons.length - 1);
564
- });
546
+ visibleButtons.forEach((btn, index, visibleButtons) => {
547
+ btn.toggleAttribute('first-visible', index === 0);
548
+ btn.toggleAttribute('last-visible', !this._hasOverflow && index === visibleButtons.length - 1);
549
+ });
565
550
  }
566
551
 
567
552
  /** @private */
@@ -712,17 +697,18 @@ export const MenuBarMixin = (superClass) =>
712
697
  * and open another one for the newly focused button.
713
698
  *
714
699
  * @param {Element} item
700
+ * @param {FocusOptions=} options
715
701
  * @param {boolean} navigating
716
702
  * @protected
717
703
  * @override
718
704
  */
719
- _focusItem(item, navigating) {
705
+ _focusItem(item, options, navigating) {
720
706
  const wasExpanded = navigating && this.focused === this._expandedButton;
721
707
  if (wasExpanded) {
722
708
  this._close();
723
709
  }
724
710
 
725
- super._focusItem(item, navigating);
711
+ super._focusItem(item, options, navigating);
726
712
 
727
713
  this._buttons.forEach((btn) => {
728
714
  this._setTabindex(btn, btn === item);
@@ -779,7 +765,7 @@ export const MenuBarMixin = (superClass) =>
779
765
  */
780
766
  _setFocused(focused) {
781
767
  if (focused) {
782
- const target = this.tabNavigation ? this.querySelector('[focused]') : this.querySelector('[tabindex="0"]');
768
+ const target = this.__getFocusTarget();
783
769
  if (target) {
784
770
  this._buttons.forEach((btn) => {
785
771
  this._setTabindex(btn, btn === target);
@@ -793,6 +779,24 @@ export const MenuBarMixin = (superClass) =>
793
779
  }
794
780
  }
795
781
 
782
+ /** @private */
783
+ __getFocusTarget() {
784
+ // First, check if focus is moving to a visible button
785
+ let target = this._buttons.find((btn) => isElementFocused(btn));
786
+
787
+ if (!target) {
788
+ const selector = this.tabNavigation ? '[focused]' : '[tabindex="0"]';
789
+ // Next, check if there is a button that could be focused but is hidden
790
+ target = this.querySelector(`vaadin-menu-bar-button${selector}`);
791
+
792
+ if (isElementHidden(target)) {
793
+ target = this._buttons[this._getFocusableIndex()];
794
+ }
795
+ }
796
+
797
+ return target;
798
+ }
799
+
796
800
  /**
797
801
  * @param {!KeyboardEvent} event
798
802
  * @private
@@ -890,11 +894,11 @@ export const MenuBarMixin = (superClass) =>
890
894
  // Hide tooltip on mouseover to disabled button
891
895
  this._hideTooltip();
892
896
  } else if (button !== this._expandedButton) {
893
- const isOpened = this._subMenu.opened;
894
- if (button.item.children && (this.openOnHover || isOpened)) {
897
+ // Switch sub-menu when moving cursor over another button
898
+ // with children, regardless of whether openOnHover is set.
899
+ // If the button has no children, keep the sub-menu opened.
900
+ if (button.item.children && (this.openOnHover || this._subMenu.opened)) {
895
901
  this.__openSubMenu(button, false);
896
- } else if (isOpened) {
897
- this._close();
898
902
  }
899
903
 
900
904
  if (button === this._overflow || (this.openOnHover && button.item.children)) {
@@ -963,6 +967,7 @@ export const MenuBarMixin = (superClass) =>
963
967
 
964
968
  subMenu.items = items;
965
969
  subMenu.listenOn = button;
970
+ subMenu._positionTarget = button;
966
971
  const overlay = subMenu._overlayElement;
967
972
  overlay.noVerticalOverlap = true;
968
973
 
@@ -972,7 +977,6 @@ export const MenuBarMixin = (superClass) =>
972
977
  this._setExpanded(button, true);
973
978
 
974
979
  this.style.pointerEvents = 'auto';
975
- overlay.positionTarget = button;
976
980
 
977
981
  button.dispatchEvent(
978
982
  new CustomEvent('opensubmenu', {
@@ -990,7 +994,8 @@ export const MenuBarMixin = (superClass) =>
990
994
  }
991
995
 
992
996
  if (options.keepFocus) {
993
- this._focusItem(this._expandedButton, false);
997
+ const focusOptions = { focusVisible: isKeyboardActive() };
998
+ this._focusItem(this._expandedButton, focusOptions, false);
994
999
  }
995
1000
 
996
1001
  // Do not focus item when open not from keyboard
@@ -1034,7 +1039,8 @@ export const MenuBarMixin = (superClass) =>
1034
1039
  if (button && button.hasAttribute('expanded')) {
1035
1040
  this._setExpanded(button, false);
1036
1041
  if (restoreFocus) {
1037
- this._focusItem(button, false);
1042
+ const focusOptions = { focusVisible: isKeyboardActive() };
1043
+ this._focusItem(button, focusOptions, false);
1038
1044
  }
1039
1045
  this._expandedButton = null;
1040
1046
  }
@@ -11,7 +11,7 @@ import { MenuOverlayMixin } from '@vaadin/context-menu/src/vaadin-menu-overlay-m
11
11
  import { OverlayMixin } from '@vaadin/overlay/src/vaadin-overlay-mixin.js';
12
12
  import { LumoInjectionMixin } from '@vaadin/vaadin-themable-mixin/lumo-injection-mixin.js';
13
13
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
14
- import { menuBarOverlayStyles } from './styles/vaadin-menu-bar-overlay-core-styles.js';
14
+ import { menuBarOverlayStyles } from './styles/vaadin-menu-bar-overlay-base-styles.js';
15
15
 
16
16
  /**
17
17
  * An element used internally by `<vaadin-menu-bar>`. Not intended to be used separately.
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2019 - 2025 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ import { ContextMenuMixin } from '@vaadin/context-menu/src/vaadin-context-menu-mixin.js';
7
+ import { ThemePropertyMixin } from '@vaadin/vaadin-themable-mixin/vaadin-theme-property-mixin.js';
8
+
9
+ /**
10
+ * An element used internally by `<vaadin-menu-bar>`. Not intended to be used separately.
11
+ */
12
+ declare class MenuBarSubmenu extends ContextMenuMixin(ThemePropertyMixin(HTMLElement)) {}
13
+
14
+ declare global {
15
+ interface HTMLElementTagNameMap {
16
+ 'vaadin-menu-bar-submenu': MenuBarSubmenu;
17
+ }
18
+ }
19
+
20
+ export { MenuBarSubmenu };
@@ -10,19 +10,19 @@ import { css, html, LitElement } from 'lit';
10
10
  import { ifDefined } from 'lit/directives/if-defined.js';
11
11
  import { defineCustomElement } from '@vaadin/component-base/src/define.js';
12
12
  import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
13
+ import { ContextMenuMixin } from '@vaadin/context-menu/src/vaadin-context-menu-mixin.js';
13
14
  import { ThemePropertyMixin } from '@vaadin/vaadin-themable-mixin/vaadin-theme-property-mixin.js';
14
- import { SubMenuMixin } from './vaadin-menu-bar-submenu-mixin.js';
15
15
 
16
16
  /**
17
17
  * An element used internally by `<vaadin-menu-bar>`. Not intended to be used separately.
18
18
  *
19
19
  * @customElement
20
20
  * @extends HTMLElement
21
- * @mixes SubMenuMixin
21
+ * @mixes ContextMenuMixin
22
22
  * @mixes ThemePropertyMixin
23
23
  * @protected
24
24
  */
25
- class MenuBarSubmenu extends SubMenuMixin(ThemePropertyMixin(PolylitMixin(LitElement))) {
25
+ class MenuBarSubmenu extends ContextMenuMixin(ThemePropertyMixin(PolylitMixin(LitElement))) {
26
26
  static get is() {
27
27
  return 'vaadin-menu-bar-submenu';
28
28
  }
@@ -39,6 +39,31 @@ class MenuBarSubmenu extends SubMenuMixin(ThemePropertyMixin(PolylitMixin(LitEle
39
39
  `;
40
40
  }
41
41
 
42
+ static get properties() {
43
+ return {
44
+ isRoot: {
45
+ type: Boolean,
46
+ reflectToAttribute: true,
47
+ sync: true,
48
+ },
49
+ };
50
+ }
51
+
52
+ constructor() {
53
+ super();
54
+
55
+ this.openOn = 'opensubmenu';
56
+ }
57
+
58
+ /**
59
+ * Tag name prefix used by overlay, list-box and items.
60
+ * @protected
61
+ * @return {string}
62
+ */
63
+ get _tagNamePrefix() {
64
+ return 'vaadin-menu-bar';
65
+ }
66
+
42
67
  /** @protected */
43
68
  render() {
44
69
  return html`
@@ -50,6 +75,8 @@ class MenuBarSubmenu extends SubMenuMixin(ThemePropertyMixin(PolylitMixin(LitEle
50
75
  .modeless="${this._modeless}"
51
76
  .renderer="${this.__itemsRenderer}"
52
77
  .withBackdrop="${this._phone}"
78
+ .positionTarget="${this._positionTarget}"
79
+ ?no-horizontal-overlap="${!this.isRoot}"
53
80
  ?phone="${this._phone}"
54
81
  theme="${ifDefined(this._theme)}"
55
82
  exportparts="backdrop, overlay, content"
@@ -61,6 +88,45 @@ class MenuBarSubmenu extends SubMenuMixin(ThemePropertyMixin(PolylitMixin(LitEle
61
88
  </vaadin-menu-bar-overlay>
62
89
  `;
63
90
  }
91
+
92
+ /**
93
+ * Overriding the observer to not add global "contextmenu" listener.
94
+ * @override
95
+ */
96
+ _openedChanged() {
97
+ // Do nothing
98
+ }
99
+
100
+ /**
101
+ * Overriding the public method to reset expanded button state.
102
+ */
103
+ close() {
104
+ super.close();
105
+
106
+ // Only handle 1st level submenu
107
+ if (this.hasAttribute('is-root')) {
108
+ this.parentElement._close();
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Override method from `ContextMenuMixin` to prevent closing
114
+ * sub-menu on the same click event that was used to open it.
115
+ *
116
+ * @param {Event} event
117
+ * @return {boolean}
118
+ * @protected
119
+ * @override
120
+ */
121
+ _shouldCloseOnOutsideClick(event) {
122
+ if (this.hasAttribute('is-root') && event.composedPath().includes(this.listenOn)) {
123
+ return false;
124
+ }
125
+
126
+ return super._shouldCloseOnOutsideClick(event);
127
+ }
64
128
  }
65
129
 
66
130
  defineCustomElement(MenuBarSubmenu);
131
+
132
+ export { MenuBarSubmenu };
@@ -12,7 +12,7 @@ import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
12
12
  import { TooltipController } from '@vaadin/component-base/src/tooltip-controller.js';
13
13
  import { LumoInjectionMixin } from '@vaadin/vaadin-themable-mixin/lumo-injection-mixin.js';
14
14
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
15
- import { menuBarStyles } from './styles/vaadin-menu-bar-core-styles.js';
15
+ import { menuBarStyles } from './styles/vaadin-menu-bar-base-styles.js';
16
16
  import { MenuBarMixin } from './vaadin-menu-bar-mixin.js';
17
17
 
18
18
  /**
@@ -1,2 +1,2 @@
1
- import './theme/lumo/vaadin-menu-bar.js';
1
+ import './src/vaadin-menu-bar.js';
2
2
  export * from './src/vaadin-menu-bar.js';
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": "25.0.0-alpha9",
4
+ "version": "25.0.0-beta2",
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```html\n<vaadin-menu-bar></vaadin-menu-bar>\n```\n\nAnd then use [`items`](https://cdn.vaadin.com/vaadin-web-components/25.0.0-alpha9/#/elements/vaadin-menu-bar#property-items) property to initialize the structure:\n\n```js\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/25.0.0-alpha9/#/elements/vaadin-button).\n- `<vaadin-menu-bar-item>` - has the same API as [`<vaadin-item>`](https://cdn.vaadin.com/vaadin-web-components/25.0.0-alpha9/#/elements/vaadin-item).\n- `<vaadin-menu-bar-list-box>` - has the same API as [`<vaadin-list-box>`](https://cdn.vaadin.com/vaadin-web-components/25.0.0-alpha9/#/elements/vaadin-list-box).\n- `<vaadin-menu-bar-submenu>` - has the same API as [`<vaadin-context-menu>`](https://cdn.vaadin.com/vaadin-web-components/25.0.0-alpha9/#/elements/vaadin-context-menu).\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.",
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```html\n<vaadin-menu-bar></vaadin-menu-bar>\n```\n\nAnd then use [`items`](https://cdn.vaadin.com/vaadin-web-components/25.0.0-beta2/#/elements/vaadin-menu-bar#property-items) property to initialize the structure:\n\n```js\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/25.0.0-beta2/#/elements/vaadin-button).\n- `<vaadin-menu-bar-item>` - has the same API as [`<vaadin-item>`](https://cdn.vaadin.com/vaadin-web-components/25.0.0-beta2/#/elements/vaadin-item).\n- `<vaadin-menu-bar-list-box>` - has the same API as [`<vaadin-list-box>`](https://cdn.vaadin.com/vaadin-web-components/25.0.0-beta2/#/elements/vaadin-list-box).\n- `<vaadin-menu-bar-submenu>` - has the same API as [`<vaadin-context-menu>`](https://cdn.vaadin.com/vaadin-web-components/25.0.0-beta2/#/elements/vaadin-context-menu).\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.",
12
12
  "attributes": [
13
13
  {
14
14
  "name": "disabled",
@@ -30,17 +30,6 @@
30
30
  ]
31
31
  }
32
32
  },
33
- {
34
- "name": "overlay-class",
35
- "description": "A space-delimited list of CSS class names\nto set on each sub-menu overlay element.",
36
- "value": {
37
- "type": [
38
- "string",
39
- "null",
40
- "undefined"
41
- ]
42
- }
43
- },
44
33
  {
45
34
  "name": "open-on-hover",
46
35
  "description": "If true, the submenu will open on hover (mouseover) instead of click.",
@@ -101,7 +90,7 @@
101
90
  },
102
91
  {
103
92
  "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```",
93
+ "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```js\n{\n moreOptions: 'More options'\n}\n```",
105
94
  "value": {
106
95
  "type": [
107
96
  "MenuBarI18n"
@@ -110,24 +99,13 @@
110
99
  },
111
100
  {
112
101
  "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```",
102
+ "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```js\n// Set before any menu bar is attached to the DOM.\nwindow.Vaadin.featureFlags.accessibleDisabledButtons = true;\n```",
114
103
  "value": {
115
104
  "type": [
116
105
  "Array.<MenuBarItem>"
117
106
  ]
118
107
  }
119
108
  },
120
- {
121
- "name": "overlayClass",
122
- "description": "A space-delimited list of CSS class names\nto set on each sub-menu overlay element.",
123
- "value": {
124
- "type": [
125
- "string",
126
- "null",
127
- "undefined"
128
- ]
129
- }
130
- },
131
109
  {
132
110
  "name": "openOnHover",
133
111
  "description": "If true, the submenu will open on hover (mouseover) instead of click.",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/web-types",
3
3
  "name": "@vaadin/menu-bar",
4
- "version": "25.0.0-alpha9",
4
+ "version": "25.0.0-beta2",
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```html\n<vaadin-menu-bar></vaadin-menu-bar>\n```\n\nAnd then use [`items`](https://cdn.vaadin.com/vaadin-web-components/25.0.0-alpha9/#/elements/vaadin-menu-bar#property-items) property to initialize the structure:\n\n```js\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/25.0.0-alpha9/#/elements/vaadin-button).\n- `<vaadin-menu-bar-item>` - has the same API as [`<vaadin-item>`](https://cdn.vaadin.com/vaadin-web-components/25.0.0-alpha9/#/elements/vaadin-item).\n- `<vaadin-menu-bar-list-box>` - has the same API as [`<vaadin-list-box>`](https://cdn.vaadin.com/vaadin-web-components/25.0.0-alpha9/#/elements/vaadin-list-box).\n- `<vaadin-menu-bar-submenu>` - has the same API as [`<vaadin-context-menu>`](https://cdn.vaadin.com/vaadin-web-components/25.0.0-alpha9/#/elements/vaadin-context-menu).\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.",
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```html\n<vaadin-menu-bar></vaadin-menu-bar>\n```\n\nAnd then use [`items`](https://cdn.vaadin.com/vaadin-web-components/25.0.0-beta2/#/elements/vaadin-menu-bar#property-items) property to initialize the structure:\n\n```js\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/25.0.0-beta2/#/elements/vaadin-button).\n- `<vaadin-menu-bar-item>` - has the same API as [`<vaadin-item>`](https://cdn.vaadin.com/vaadin-web-components/25.0.0-beta2/#/elements/vaadin-item).\n- `<vaadin-menu-bar-list-box>` - has the same API as [`<vaadin-list-box>`](https://cdn.vaadin.com/vaadin-web-components/25.0.0-beta2/#/elements/vaadin-list-box).\n- `<vaadin-menu-bar-submenu>` - has the same API as [`<vaadin-context-menu>`](https://cdn.vaadin.com/vaadin-web-components/25.0.0-beta2/#/elements/vaadin-context-menu).\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.",
20
20
  "extension": true,
21
21
  "attributes": [
22
22
  {
@@ -49,21 +49,14 @@
49
49
  },
50
50
  {
51
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```",
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```js\n{\n moreOptions: 'More options'\n}\n```",
53
53
  "value": {
54
54
  "kind": "expression"
55
55
  }
56
56
  },
57
57
  {
58
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
- "value": {
61
- "kind": "expression"
62
- }
63
- },
64
- {
65
- "name": ".overlayClass",
66
- "description": "A space-delimited list of CSS class names\nto set on each sub-menu overlay element.",
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```js\n// Set before any menu bar is attached to the DOM.\nwindow.Vaadin.featureFlags.accessibleDisabledButtons = true;\n```",
67
60
  "value": {
68
61
  "kind": "expression"
69
62
  }
@@ -1,8 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright (c) 2019 - 2025 Vaadin Ltd.
4
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
- */
6
- import type { CSSResult } from 'lit';
7
-
8
- export const menuBarButtonStyles: CSSResult;
@@ -1,16 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright (c) 2019 - 2025 Vaadin Ltd.
4
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
- */
6
- import { css } from 'lit';
7
-
8
- export const menuBarButtonStyles = css`
9
- :host {
10
- flex-shrink: 0;
11
- }
12
-
13
- :host([slot='overflow']) {
14
- margin-inline-end: 0;
15
- }
16
- `;
@@ -1,8 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright (c) 2019 - 2025 Vaadin Ltd.
4
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
- */
6
- import type { CSSResult } from 'lit';
7
-
8
- export const menuBarStyles: CSSResult;
@@ -1,24 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright (c) 2019 - 2025 Vaadin Ltd.
4
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
- */
6
- import { css } from 'lit';
7
-
8
- export const menuBarStyles = css`
9
- :host {
10
- display: block;
11
- }
12
-
13
- :host([hidden]) {
14
- display: none !important;
15
- }
16
-
17
- [part='container'] {
18
- position: relative;
19
- display: flex;
20
- width: 100%;
21
- flex-wrap: nowrap;
22
- overflow: hidden;
23
- }
24
- `;
@@ -1,8 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright (c) 2019 - 2025 Vaadin Ltd.
4
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
- */
6
- import type { CSSResult } from 'lit';
7
-
8
- export const menuBarItemStyles: CSSResult;
@@ -1,8 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright (c) 2019 - 2025 Vaadin Ltd.
4
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
- */
6
- import { itemStyles } from '@vaadin/item/src/styles/vaadin-item-core-styles.js';
7
-
8
- export const menuBarItemStyles = [itemStyles];
@@ -1,8 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright (c) 2019 - 2025 Vaadin Ltd.
4
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
- */
6
- import type { CSSResult } from 'lit';
7
-
8
- export const menuBarOverlayStyles: CSSResult;
@@ -1,9 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright (c) 2019 - 2025 Vaadin Ltd.
4
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
- */
6
- import { menuOverlayStyles } from '@vaadin/context-menu/src/styles/vaadin-menu-overlay-core-styles.js';
7
- import { overlayStyles } from '@vaadin/overlay/src/styles/vaadin-overlay-core-styles.js';
8
-
9
- export const menuBarOverlayStyles = [overlayStyles, menuOverlayStyles];
@@ -1,66 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright (c) 2019 - 2025 Vaadin Ltd.
4
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
- */
6
- import { OverlayClassMixin } from '@vaadin/component-base/src/overlay-class-mixin.js';
7
- import { ContextMenuMixin } from '@vaadin/context-menu/src/vaadin-context-menu-mixin.js';
8
-
9
- /**
10
- * @polymerMixin
11
- * @mixes ContextMenuMixin
12
- * @mixes OverlayClassMixin
13
- */
14
- export const SubMenuMixin = (superClass) =>
15
- class SubMenuMixinClass extends ContextMenuMixin(OverlayClassMixin(superClass)) {
16
- constructor() {
17
- super();
18
-
19
- this.openOn = 'opensubmenu';
20
- }
21
-
22
- /**
23
- * Tag name prefix used by overlay, list-box and items.
24
- * @protected
25
- * @return {string}
26
- */
27
- get _tagNamePrefix() {
28
- return 'vaadin-menu-bar';
29
- }
30
-
31
- /**
32
- * Overriding the observer to not add global "contextmenu" listener.
33
- */
34
- _openedChanged() {
35
- // Do nothing
36
- }
37
-
38
- /**
39
- * Overriding the public method to reset expanded button state.
40
- */
41
- close() {
42
- super.close();
43
-
44
- // Only handle 1st level submenu
45
- if (this.hasAttribute('is-root')) {
46
- this.parentElement._close();
47
- }
48
- }
49
-
50
- /**
51
- * Override method from `ContextMenuMixin` to prevent closing
52
- * sub-menu on the same click event that was used to open it.
53
- *
54
- * @param {Event} event
55
- * @return {boolean}
56
- * @protected
57
- * @override
58
- */
59
- _shouldCloseOnOutsideClick(event) {
60
- if (this.hasAttribute('is-root') && event.composedPath().includes(this.listenOn)) {
61
- return false;
62
- }
63
-
64
- return super._shouldCloseOnOutsideClick(event);
65
- }
66
- };
@@ -1 +0,0 @@
1
- export {};
@@ -1,128 +0,0 @@
1
- import { button } from '@vaadin/button/theme/lumo/vaadin-button-styles.js';
2
- import { css, registerStyles } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
3
-
4
- const menuBarButton = css`
5
- :host {
6
- margin: calc(var(--lumo-space-xs) / 2);
7
- margin-left: 0;
8
- border-radius: 0;
9
- }
10
-
11
- [part='label'] {
12
- width: 100%;
13
- }
14
-
15
- /* NOTE(web-padawan): avoid using shorthand padding property for IE11 */
16
- [part='label'] ::slotted(vaadin-menu-bar-item) {
17
- justify-content: center;
18
- background-color: transparent;
19
- height: var(--lumo-button-size);
20
- margin: 0 calc((var(--lumo-size-m) / 3 + var(--lumo-border-radius-m) / 2) * -1);
21
- padding-left: calc(var(--lumo-size-m) / 3 + var(--lumo-border-radius-m) / 2);
22
- padding-right: calc(var(--lumo-size-m) / 3 + var(--lumo-border-radius-m) / 2);
23
- }
24
-
25
- :host([theme~='small']) [part='label'] ::slotted(vaadin-menu-bar-item) {
26
- min-height: var(--lumo-size-s);
27
- margin: 0 calc((var(--lumo-size-s) / 3 + var(--lumo-border-radius-m) / 2) * -1);
28
- padding-left: calc(var(--lumo-size-s) / 3 + var(--lumo-border-radius-m) / 2);
29
- padding-right: calc(var(--lumo-size-s) / 3 + var(--lumo-border-radius-m) / 2);
30
- }
31
-
32
- :host([theme~='tertiary']) [part='label'] ::slotted(vaadin-menu-bar-item) {
33
- margin: 0 calc((var(--lumo-button-size) / 6) * -1);
34
- padding-left: calc(var(--lumo-button-size) / 6);
35
- padding-right: calc(var(--lumo-button-size) / 6);
36
- }
37
-
38
- :host([theme~='tertiary-inline']) {
39
- margin-top: calc(var(--lumo-space-xs) / 2);
40
- margin-bottom: calc(var(--lumo-space-xs) / 2);
41
- margin-right: calc(var(--lumo-space-xs) / 2);
42
- }
43
-
44
- :host([theme~='tertiary-inline']) [part='label'] ::slotted(vaadin-menu-bar-item) {
45
- margin: 0;
46
- padding: 0;
47
- }
48
-
49
- :host([first-visible]) {
50
- border-radius: var(--lumo-border-radius-m) 0 0 var(--lumo-border-radius-m);
51
-
52
- /* Needed to retain the focus-ring with border-radius */
53
- margin-left: calc(var(--lumo-space-xs) / 2);
54
- }
55
-
56
- :host([last-visible]),
57
- :host([slot='overflow']) {
58
- border-radius: 0 var(--lumo-border-radius-m) var(--lumo-border-radius-m) 0;
59
- }
60
-
61
- :host([theme~='tertiary']),
62
- :host([theme~='tertiary-inline']) {
63
- border-radius: var(--lumo-border-radius-m);
64
- }
65
-
66
- :host([slot='overflow']) {
67
- min-width: var(--lumo-button-size);
68
- padding-left: calc(var(--lumo-button-size) / 4);
69
- padding-right: calc(var(--lumo-button-size) / 4);
70
- }
71
-
72
- :host([slot='overflow']) ::slotted(*) {
73
- font-size: var(--lumo-font-size-xl);
74
- }
75
-
76
- :host([slot='overflow']) [part='prefix'],
77
- :host([slot='overflow']) [part='suffix'] {
78
- margin-left: 0;
79
- margin-right: 0;
80
- }
81
-
82
- :host([theme~='dropdown-indicators']:not([slot='overflow']):not([theme~='icon'])[aria-haspopup]) [part='suffix'] {
83
- margin-inline-start: 0;
84
- width: 1em;
85
- height: 1em;
86
- line-height: 1;
87
- font-size: var(--lumo-icon-size-s);
88
- position: relative;
89
- inset-inline-start: 0.15em;
90
- }
91
-
92
- /* prettier-ignore */
93
- :host([theme~='dropdown-indicators']:not([slot='overflow']):not([theme~='icon'])[aria-haspopup]) [part='suffix']::after {
94
- font-family: lumo-icons;
95
- content: var(--lumo-icons-dropdown);
96
- }
97
-
98
- /* prettier-ignore */
99
- :host([theme~='dropdown-indicators']:not([slot='overflow']):not([theme~='icon'])[theme~='tertiary'][aria-haspopup]) [part='suffix'] {
100
- inset-inline-start: 0.05em;
101
- }
102
-
103
- /* prettier-ignore */
104
- :host([theme~='dropdown-indicators']:not([slot='overflow']):not([theme~='icon'])[theme~='tertiary-inline'][aria-haspopup]) [part='suffix'] {
105
- inset-inline-start: 0;
106
- }
107
-
108
- /* RTL styles */
109
- :host([dir='rtl']) {
110
- margin-left: calc(var(--lumo-space-xs) / 2);
111
- margin-right: 0;
112
- border-radius: 0;
113
- }
114
-
115
- :host([dir='rtl'][first-visible]) {
116
- border-radius: 0 var(--lumo-border-radius-m) var(--lumo-border-radius-m) 0;
117
- margin-right: calc(var(--lumo-space-xs) / 2);
118
- }
119
-
120
- :host([dir='rtl'][last-visible]),
121
- :host([dir='rtl'][slot='overflow']) {
122
- border-radius: var(--lumo-border-radius-m) 0 0 var(--lumo-border-radius-m);
123
- }
124
- `;
125
-
126
- registerStyles('vaadin-menu-bar-button', [button, menuBarButton], {
127
- moduleId: 'lumo-menu-bar-button',
128
- });
@@ -1,2 +0,0 @@
1
- import './vaadin-menu-bar-button-styles.js';
2
- import '../../src/vaadin-menu-bar-button.js';
@@ -1,2 +0,0 @@
1
- import './vaadin-menu-bar-button-styles.js';
2
- import '../../src/vaadin-menu-bar-button.js';
@@ -1,2 +0,0 @@
1
- import '@vaadin/vaadin-lumo-styles/sizing.js';
2
- import '@vaadin/vaadin-lumo-styles/spacing.js';
@@ -1,27 +0,0 @@
1
- import '@vaadin/vaadin-lumo-styles/sizing.js';
2
- import '@vaadin/vaadin-lumo-styles/spacing.js';
3
- import { contextMenuItem } from '@vaadin/context-menu/theme/lumo/vaadin-context-menu-item-styles.js';
4
- import { item } from '@vaadin/item/theme/lumo/vaadin-item-styles.js';
5
- import { css, registerStyles } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
6
-
7
- const menuBarItem = css`
8
- [part='content'] {
9
- display: flex;
10
- /* tweak to inherit centering from menu bar button */
11
- align-items: inherit;
12
- justify-content: inherit;
13
- }
14
-
15
- [part='content'] ::slotted(vaadin-icon) {
16
- display: inline-block;
17
- width: var(--lumo-icon-size-m);
18
- height: var(--lumo-icon-size-m);
19
- }
20
-
21
- [part='content'] ::slotted(vaadin-icon[icon^='vaadin:']) {
22
- padding: var(--lumo-space-xs);
23
- box-sizing: border-box !important;
24
- }
25
- `;
26
-
27
- registerStyles('vaadin-menu-bar-item', [item, contextMenuItem, menuBarItem], { moduleId: 'lumo-menu-bar-item' });
@@ -1 +0,0 @@
1
- export {};
@@ -1,5 +0,0 @@
1
- import { contextMenuListBox } from '@vaadin/context-menu/theme/lumo/vaadin-context-menu-list-box-styles.js';
2
- import { listBox } from '@vaadin/list-box/theme/lumo/vaadin-list-box-styles.js';
3
- import { registerStyles } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
4
-
5
- registerStyles('vaadin-menu-bar-list-box', [listBox, contextMenuListBox], { moduleId: 'lumo-menu-bar-list-box' });
@@ -1 +0,0 @@
1
- export {};
@@ -1,13 +0,0 @@
1
- import { contextMenuOverlay } from '@vaadin/context-menu/theme/lumo/vaadin-context-menu-overlay-styles.js';
2
- import { menuOverlay } from '@vaadin/vaadin-lumo-styles/mixins/menu-overlay.js';
3
- import { css, registerStyles } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
4
-
5
- const menuBarOverlay = css`
6
- :host(:first-of-type) {
7
- padding-top: var(--lumo-space-xs);
8
- }
9
- `;
10
-
11
- registerStyles('vaadin-menu-bar-overlay', [menuOverlay, contextMenuOverlay, menuBarOverlay], {
12
- moduleId: 'lumo-menu-bar-overlay',
13
- });
@@ -1 +0,0 @@
1
- import '@vaadin/vaadin-lumo-styles/style.js';
@@ -1,17 +0,0 @@
1
- import '@vaadin/vaadin-lumo-styles/style.js';
2
- import { css, registerStyles } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
3
-
4
- registerStyles(
5
- 'vaadin-menu-bar',
6
- css`
7
- :host([has-single-button]) ::slotted(vaadin-menu-bar-button) {
8
- border-radius: var(--lumo-border-radius-m);
9
- }
10
-
11
- :host([theme~='end-aligned']) ::slotted(vaadin-menu-bar-button[first-visible]),
12
- :host([theme~='end-aligned'][has-single-button]) ::slotted(vaadin-menu-bar-button) {
13
- margin-inline-start: auto;
14
- }
15
- `,
16
- { moduleId: 'lumo-menu-bar' },
17
- );
@@ -1,6 +0,0 @@
1
- import './vaadin-menu-bar-button.js';
2
- import './vaadin-menu-bar-item-styles.js';
3
- import './vaadin-menu-bar-list-box-styles.js';
4
- import './vaadin-menu-bar-overlay-styles.js';
5
- import './vaadin-menu-bar-styles.js';
6
- import '../../src/vaadin-menu-bar.js';
@@ -1,6 +0,0 @@
1
- import './vaadin-menu-bar-button.js';
2
- import './vaadin-menu-bar-item-styles.js';
3
- import './vaadin-menu-bar-list-box-styles.js';
4
- import './vaadin-menu-bar-overlay-styles.js';
5
- import './vaadin-menu-bar-styles.js';
6
- import '../../src/vaadin-menu-bar.js';