@vaadin/context-menu 25.0.0-alpha2 → 25.0.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.
Files changed (27) hide show
  1. package/package.json +13 -14
  2. package/src/styles/vaadin-context-menu-item-base-styles.d.ts +8 -0
  3. package/src/styles/vaadin-context-menu-item-base-styles.js +34 -0
  4. package/src/styles/vaadin-context-menu-overlay-base-styles.d.ts +8 -0
  5. package/src/styles/vaadin-context-menu-overlay-base-styles.js +36 -0
  6. package/src/{vaadin-menu-overlay-styles.d.ts → styles/vaadin-menu-overlay-base-styles.d.ts} +1 -1
  7. package/src/{vaadin-menu-overlay-styles.js → styles/vaadin-menu-overlay-base-styles.js} +7 -8
  8. package/src/vaadin-context-menu-item.js +5 -11
  9. package/src/vaadin-context-menu-list-box.js +5 -18
  10. package/src/vaadin-context-menu-mixin.js +118 -75
  11. package/src/vaadin-context-menu-overlay.js +59 -4
  12. package/src/vaadin-context-menu.d.ts +48 -11
  13. package/src/vaadin-context-menu.js +98 -23
  14. package/src/vaadin-contextmenu-items-mixin.js +61 -41
  15. package/src/vaadin-menu-overlay-mixin.d.ts +0 -5
  16. package/src/vaadin-menu-overlay-mixin.js +23 -28
  17. package/vaadin-context-menu.js +1 -1
  18. package/web-types.json +10 -6
  19. package/web-types.lit.json +11 -4
  20. package/theme/lumo/vaadin-context-menu-item-styles.d.ts +0 -6
  21. package/theme/lumo/vaadin-context-menu-item-styles.js +0 -45
  22. package/theme/lumo/vaadin-context-menu-list-box-styles.d.ts +0 -5
  23. package/theme/lumo/vaadin-context-menu-list-box-styles.js +0 -47
  24. package/theme/lumo/vaadin-context-menu-overlay-styles.d.ts +0 -4
  25. package/theme/lumo/vaadin-context-menu-overlay-styles.js +0 -35
  26. package/theme/lumo/vaadin-context-menu.d.ts +0 -4
  27. package/theme/lumo/vaadin-context-menu.js +0 -4
@@ -4,13 +4,26 @@
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
6
  import type { ElementMixinClass } from '@vaadin/component-base/src/element-mixin.js';
7
- import type { OverlayClassMixinClass } from '@vaadin/component-base/src/overlay-class-mixin.js';
8
7
  import type { ThemePropertyMixinClass } from '@vaadin/vaadin-themable-mixin/vaadin-theme-property-mixin.js';
9
8
  import type { ContextMenuMixinClass } from './vaadin-context-menu-mixin.js';
10
9
  import type { ContextMenuItem } from './vaadin-contextmenu-items-mixin.js';
11
10
 
12
11
  export { ContextMenuItem };
13
12
 
13
+ export type ContextMenuPosition =
14
+ | 'bottom-end'
15
+ | 'bottom-start'
16
+ | 'bottom'
17
+ | 'end-bottom'
18
+ | 'end-top'
19
+ | 'end'
20
+ | 'start-bottom'
21
+ | 'start-top'
22
+ | 'start'
23
+ | 'top-end'
24
+ | 'top-start'
25
+ | 'top';
26
+
14
27
  export interface ContextMenuRendererContext {
15
28
  target: HTMLElement;
16
29
  detail?: { sourceEvent: Event };
@@ -34,6 +47,11 @@ export type ContextMenuItemSelectedEvent<TItem extends ContextMenuItem = Context
34
47
  value: TItem;
35
48
  }>;
36
49
 
50
+ /**
51
+ * Fired when the context menu is closed.
52
+ */
53
+ export type ContextMenuClosedEvent = CustomEvent;
54
+
37
55
  export interface ContextMenuCustomEventMap<TItem extends ContextMenuItem = ContextMenuItem> {
38
56
  'opened-changed': ContextMenuOpenedChangedEvent;
39
57
 
@@ -42,6 +60,8 @@ export interface ContextMenuCustomEventMap<TItem extends ContextMenuItem = Conte
42
60
  'close-all-menus': Event;
43
61
 
44
62
  'items-outside-click': Event;
63
+
64
+ closed: ContextMenuClosedEvent;
45
65
  }
46
66
 
47
67
  export interface ContextMenuEventMap<TItem extends ContextMenuItem = ContextMenuItem>
@@ -208,18 +228,30 @@ export interface ContextMenuEventMap<TItem extends ContextMenuItem = ContextMenu
208
228
  *
209
229
  * ### Styling
210
230
  *
211
- * `<vaadin-context-menu>` uses `<vaadin-context-menu-overlay>` internal
212
- * themable component as the actual visible context menu overlay.
231
+ * The following shadow DOM parts are available for styling:
213
232
  *
214
- * See [`<vaadin-overlay>`](#/elements/vaadin-overlay)
215
- * documentation for `<vaadin-context-menu-overlay>` stylable parts.
233
+ * Part name | Description
234
+ * -----------------|-------------------------------------------
235
+ * `backdrop` | Backdrop of the overlay
236
+ * `overlay` | The overlay container
237
+ * `content` | The overlay content
238
+ *
239
+ * ### Custom CSS Properties
240
+ *
241
+ * The following custom CSS properties are available for styling:
242
+ *
243
+ * Custom CSS property | Description
244
+ * --------------------------------------|-------------
245
+ * `--vaadin-context-menu-offset-top` | Used as an offset when using `position` and the context menu is aligned vertically below the target
246
+ * `--vaadin-context-menu-offset-bottom` | Used as an offset when using `position` and the context menu is aligned vertically above the target
247
+ * `--vaadin-context-menu-offset-start` | Used as an offset when using `position` and the context menu is aligned horizontally after the target
248
+ * `--vaadin-context-menu-offset-end` | Used as an offset when using `position` and the context menu is aligned horizontally before the target
216
249
  *
217
250
  * See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
218
251
  *
219
252
  * ### Internal components
220
253
  *
221
- * When using `items` API, in addition `<vaadin-context-menu-overlay>`, the following
222
- * internal components are themable:
254
+ * When using `items` API the following internal components are themable:
223
255
  *
224
256
  * - `<vaadin-context-menu-item>` - has the same API as [`<vaadin-item>`](#/elements/vaadin-item).
225
257
  * - `<vaadin-context-menu-list-box>` - has the same API as [`<vaadin-list-box>`](#/elements/vaadin-list-box).
@@ -231,13 +263,19 @@ export interface ContextMenuEventMap<TItem extends ContextMenuItem = ContextMenu
231
263
  * ---------- |-------------
232
264
  * `expanded` | Expanded parent item.
233
265
  *
234
- * Note: the `theme` attribute value set on `<vaadin-context-menu>` is
235
- * propagated to the internal components listed above.
236
- *
237
266
  * @fires {CustomEvent} opened-changed - Fired when the `opened` property changes.
238
267
  * @fires {CustomEvent} item-selected - Fired when an item is selected when the context menu is populated using the `items` API.
268
+ * @fires {CustomEvent} closed - Fired when the context menu is closed.
239
269
  */
240
270
  declare class ContextMenu<TItem extends ContextMenuItem = ContextMenuItem> extends HTMLElement {
271
+ /**
272
+ * Position of the overlay with respect to the target.
273
+ * Supported values: null, `top-start`, `top`, `top-end`,
274
+ * `bottom-start`, `bottom`, `bottom-end`, `start-top`,
275
+ * `start`, `start-bottom`, `end-top`, `end`, `end-bottom`.
276
+ */
277
+ position: ContextMenuPosition | null | undefined;
278
+
241
279
  addEventListener<K extends keyof ContextMenuEventMap>(
242
280
  type: K,
243
281
  listener: (this: ContextMenu<TItem>, ev: ContextMenuEventMap<TItem>[K]) => void,
@@ -253,7 +291,6 @@ declare class ContextMenu<TItem extends ContextMenuItem = ContextMenuItem> exten
253
291
 
254
292
  interface ContextMenu<TItem extends ContextMenuItem = ContextMenuItem>
255
293
  extends ContextMenuMixinClass<TItem>,
256
- OverlayClassMixinClass,
257
294
  ElementMixinClass,
258
295
  ThemePropertyMixinClass {}
259
296
 
@@ -8,9 +8,9 @@ import './vaadin-context-menu-item.js';
8
8
  import './vaadin-context-menu-list-box.js';
9
9
  import './vaadin-context-menu-overlay.js';
10
10
  import { css, html, LitElement } from 'lit';
11
+ import { ifDefined } from 'lit/directives/if-defined.js';
11
12
  import { defineCustomElement } from '@vaadin/component-base/src/define.js';
12
13
  import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
13
- import { OverlayClassMixin } from '@vaadin/component-base/src/overlay-class-mixin.js';
14
14
  import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
15
15
  import { ThemePropertyMixin } from '@vaadin/vaadin-themable-mixin/vaadin-theme-property-mixin.js';
16
16
  import { ContextMenuMixin } from './vaadin-context-menu-mixin.js';
@@ -175,18 +175,30 @@ import { ContextMenuMixin } from './vaadin-context-menu-mixin.js';
175
175
  *
176
176
  * ### Styling
177
177
  *
178
- * `<vaadin-context-menu>` uses `<vaadin-context-menu-overlay>` internal
179
- * themable component as the actual visible context menu overlay.
178
+ * The following shadow DOM parts are available for styling:
180
179
  *
181
- * See [`<vaadin-overlay>`](#/elements/vaadin-overlay)
182
- * documentation for `<vaadin-context-menu-overlay>` stylable parts.
180
+ * Part name | Description
181
+ * -----------------|-------------------------------------------
182
+ * `backdrop` | Backdrop of the overlay
183
+ * `overlay` | The overlay container
184
+ * `content` | The overlay content
185
+ *
186
+ * ### Custom CSS Properties
187
+ *
188
+ * The following custom CSS properties are available for styling:
189
+ *
190
+ * Custom CSS property | Description
191
+ * --------------------------------------|-------------
192
+ * `--vaadin-context-menu-offset-top` | Used as an offset when using `position` and the context menu is aligned vertically below the target
193
+ * `--vaadin-context-menu-offset-bottom` | Used as an offset when using `position` and the context menu is aligned vertically above the target
194
+ * `--vaadin-context-menu-offset-start` | Used as an offset when using `position` and the context menu is aligned horizontally after the target
195
+ * `--vaadin-context-menu-offset-end` | Used as an offset when using `position` and the context menu is aligned horizontally before the target
183
196
  *
184
197
  * See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
185
198
  *
186
199
  * ### Internal components
187
200
  *
188
- * When using `items` API, in addition `<vaadin-context-menu-overlay>`, the following
189
- * internal components are themable:
201
+ * When using `items` API the following internal components are themable:
190
202
  *
191
203
  * - `<vaadin-context-menu-item>` - has the same API as [`<vaadin-item>`](#/elements/vaadin-item).
192
204
  * - `<vaadin-context-menu-list-box>` - has the same API as [`<vaadin-list-box>`](#/elements/vaadin-list-box).
@@ -198,22 +210,17 @@ import { ContextMenuMixin } from './vaadin-context-menu-mixin.js';
198
210
  * ---------- |-------------
199
211
  * `expanded` | Expanded parent item.
200
212
  *
201
- * Note: the `theme` attribute value set on `<vaadin-context-menu>` is
202
- * propagated to the internal components listed above.
203
- *
204
213
  * @fires {CustomEvent} opened-changed - Fired when the `opened` property changes.
205
214
  * @fires {CustomEvent} item-selected - Fired when an item is selected when the context menu is populated using the `items` API.
215
+ * @fires {CustomEvent} closed - Fired when the context menu is closed.
206
216
  *
207
217
  * @customElement
208
218
  * @extends HTMLElement
209
219
  * @mixes ElementMixin
210
220
  * @mixes ContextMenuMixin
211
- * @mixes OverlayClassMixin
212
221
  * @mixes ThemePropertyMixin
213
222
  */
214
- class ContextMenu extends ContextMenuMixin(
215
- OverlayClassMixin(ElementMixin(ThemePropertyMixin(PolylitMixin(LitElement)))),
216
- ) {
223
+ class ContextMenu extends ContextMenuMixin(ElementMixin(ThemePropertyMixin(PolylitMixin(LitElement)))) {
217
224
  static get is() {
218
225
  return 'vaadin-context-menu';
219
226
  }
@@ -230,19 +237,87 @@ class ContextMenu extends ContextMenuMixin(
230
237
  `;
231
238
  }
232
239
 
240
+ static get properties() {
241
+ return {
242
+ /**
243
+ * Position of the overlay with respect to the target.
244
+ * Supported values: null, `top-start`, `top`, `top-end`,
245
+ * `bottom-start`, `bottom`, `bottom-end`, `start-top`,
246
+ * `start`, `start-bottom`, `end-top`, `end`, `end-bottom`.
247
+ */
248
+ position: {
249
+ type: String,
250
+ },
251
+ };
252
+ }
253
+
233
254
  /** @protected */
234
255
  render() {
235
- return html`<slot id="slot"></slot>`;
256
+ const { _context: context, position } = this;
257
+
258
+ return html`
259
+ <slot id="slot"></slot>
260
+ <vaadin-context-menu-overlay
261
+ id="overlay"
262
+ .owner="${this}"
263
+ .opened="${this.opened}"
264
+ .model="${context}"
265
+ .modeless="${this._modeless}"
266
+ .renderer="${this.items ? this.__itemsRenderer : this.renderer}"
267
+ .position="${position}"
268
+ .positionTarget="${position ? context && context.target : this._positionTarget}"
269
+ .horizontalAlign="${this.__computeHorizontalAlign(position)}"
270
+ .verticalAlign="${this.__computeVerticalAlign(position)}"
271
+ ?no-horizontal-overlap="${this.__computeNoHorizontalOverlap(position)}"
272
+ ?no-vertical-overlap="${this.__computeNoVerticalOverlap(position)}"
273
+ .withBackdrop="${this._phone}"
274
+ ?phone="${this._phone}"
275
+ theme="${ifDefined(this._theme)}"
276
+ exportparts="backdrop, overlay, content"
277
+ @opened-changed="${this._onOverlayOpened}"
278
+ @vaadin-overlay-open="${this._onVaadinOverlayOpen}"
279
+ @vaadin-overlay-closed="${this._onVaadinOverlayClosed}"
280
+ >
281
+ <slot name="overlay"></slot>
282
+ <slot name="submenu" slot="submenu"></slot>
283
+ </vaadin-context-menu-overlay>
284
+ `;
236
285
  }
237
286
 
238
- /**
239
- * @protected
240
- * @override
241
- */
242
- createRenderRoot() {
243
- const root = super.createRenderRoot();
244
- root.appendChild(this._overlayElement);
245
- return root;
287
+ /** @private */
288
+ __computeHorizontalAlign(position) {
289
+ if (!position) {
290
+ return 'start';
291
+ }
292
+
293
+ return ['top-end', 'bottom-end', 'start-top', 'start', 'start-bottom'].includes(position) ? 'end' : 'start';
294
+ }
295
+
296
+ /** @private */
297
+ __computeNoHorizontalOverlap(position) {
298
+ if (!position) {
299
+ return !!this._positionTarget;
300
+ }
301
+
302
+ return ['start-top', 'start', 'start-bottom', 'end-top', 'end', 'end-bottom'].includes(position);
303
+ }
304
+
305
+ /** @private */
306
+ __computeNoVerticalOverlap(position) {
307
+ if (!position) {
308
+ return false;
309
+ }
310
+
311
+ return ['top-start', 'top-end', 'top', 'bottom-start', 'bottom', 'bottom-end'].includes(position);
312
+ }
313
+
314
+ /** @private */
315
+ __computeVerticalAlign(position) {
316
+ if (!position) {
317
+ return 'top';
318
+ }
319
+
320
+ return ['top-start', 'top-end', 'top', 'start-bottom', 'end-bottom'].includes(position) ? 'bottom' : 'top';
246
321
  }
247
322
 
248
323
  /**
@@ -16,14 +16,14 @@ export const ItemsMixin = (superClass) =>
16
16
  * @typedef ContextMenuItem
17
17
  * @type {object}
18
18
  * @property {string} text - Text to be set as the menu item component's textContent
19
- * @property {union: string | object} component - The component to represent the item.
19
+ * @property {string | HTMLElement} component - The component to represent the item.
20
20
  * Either a tagName or an element instance. Defaults to "vaadin-context-menu-item".
21
21
  * @property {boolean} disabled - If true, the item is disabled and cannot be selected
22
22
  * @property {boolean} checked - If true, the item shows a checkmark next to it
23
23
  * @property {boolean} keepOpen - If true, the menu will not be closed on item selection
24
24
  * @property {string} className - A space-delimited list of CSS class names to be set on the menu item component.
25
- * @property {union: string | string[]} theme - If set, sets the given theme(s) as an attribute to the menu item component, overriding any theme set on the context menu.
26
- * @property {MenuItem[]} children - Array of child menu items
25
+ * @property {string | string[]} theme - If set, sets the given theme(s) as an attribute to the menu item component, overriding any theme set on the context menu.
26
+ * @property {ContextMenuItem[]} children - Array of child menu items
27
27
  */
28
28
 
29
29
  /**
@@ -60,6 +60,12 @@ export const ItemsMixin = (superClass) =>
60
60
  type: Array,
61
61
  sync: true,
62
62
  },
63
+
64
+ /** @protected */
65
+ _positionTarget: {
66
+ type: Object,
67
+ sync: true,
68
+ },
63
69
  };
64
70
  }
65
71
 
@@ -116,10 +122,10 @@ export const ItemsMixin = (superClass) =>
116
122
  /** @protected */
117
123
  __forwardFocus() {
118
124
  const overlay = this._overlayElement;
119
- const child = overlay.getFirstChild();
125
+ const child = overlay._contentRoot.firstElementChild;
120
126
  // If parent item is not focused, do not focus submenu
121
127
  if (overlay.parentOverlay) {
122
- const parent = overlay.parentOverlay.querySelector('[expanded]');
128
+ const parent = overlay.parentOverlay._contentRoot.querySelector('[expanded]');
123
129
  if (parent && parent.hasAttribute('focused') && child) {
124
130
  child.focus();
125
131
  } else {
@@ -131,16 +137,12 @@ export const ItemsMixin = (superClass) =>
131
137
  }
132
138
 
133
139
  /** @private */
134
- __openSubMenu(subMenu, itemElement, overlayClass) {
135
- subMenu.items = itemElement._item.children;
136
- subMenu.listenOn = itemElement;
137
- subMenu.overlayClass = overlayClass;
140
+ __openSubMenu(subMenu, itemElement) {
141
+ // Update sub-menu items and position target
142
+ this.__updateSubMenuForItem(subMenu, itemElement);
138
143
 
139
144
  const parent = this._overlayElement;
140
-
141
145
  const subMenuOverlay = subMenu._overlayElement;
142
- subMenuOverlay.positionTarget = itemElement;
143
- subMenuOverlay.noHorizontalOverlap = true;
144
146
  // Store the reference parent overlay
145
147
  subMenuOverlay._setParentOverlay(parent);
146
148
 
@@ -163,6 +165,14 @@ export const ItemsMixin = (superClass) =>
163
165
  );
164
166
  }
165
167
 
168
+ /** @private */
169
+ __updateSubMenuForItem(subMenu, itemElement) {
170
+ subMenu.items = itemElement._item.children;
171
+ subMenu.listenOn = itemElement;
172
+ subMenu._positionTarget = itemElement;
173
+ subMenu._overlayElement.requestContentUpdate();
174
+ }
175
+
166
176
  /**
167
177
  * @param {!ContextMenuItem} item
168
178
  * @return {HTMLElement}
@@ -248,10 +258,20 @@ export const ItemsMixin = (superClass) =>
248
258
  // Open a submenu on click event when a touch device is used.
249
259
  // On desktop, a submenu opens on hover.
250
260
  overlay.addEventListener(isTouch ? 'click' : 'mouseover', (event) => {
261
+ // Ignore events from the submenus
262
+ if (event.composedPath().includes(this._subMenu)) {
263
+ return;
264
+ }
265
+
251
266
  this.__showSubMenu(event);
252
267
  });
253
268
 
254
269
  overlay.addEventListener('keydown', (event) => {
270
+ // Ignore events from the submenus
271
+ if (event.composedPath().includes(this._subMenu)) {
272
+ return;
273
+ }
274
+
255
275
  const { key } = event;
256
276
  const isRTL = this.__isRTL;
257
277
 
@@ -283,10 +303,6 @@ export const ItemsMixin = (superClass) =>
283
303
  subMenu._modeless = true;
284
304
  subMenu.openOn = 'opensubmenu';
285
305
 
286
- // Sub-menu doesn't have a target to wrap,
287
- // so there is no need to keep it visible.
288
- subMenu.setAttribute('hidden', '');
289
-
290
306
  // Close sub-menu when the parent menu closes.
291
307
  this.addEventListener('opened-changed', (event) => {
292
308
  if (!event.detail.value) {
@@ -317,14 +333,10 @@ export const ItemsMixin = (superClass) =>
317
333
  const menu = e.target;
318
334
  const selectedItem = e.detail.value;
319
335
  const index = menu.items.indexOf(selectedItem);
320
- if (!!selectedItem.keepOpen && index > -1) {
321
- menu._overlayElement.requestContentUpdate();
322
-
323
- // Initialize items synchronously
324
- menu._listBox._observer.flush();
325
-
326
- const newItem = menu._listBox.children[index];
327
- newItem.focus();
336
+ // Menu can be no longer opened if parent menu items changed
337
+ if (!!selectedItem.keepOpen && index > -1 && menu.opened) {
338
+ menu.__selectedIndex = index;
339
+ menu.requestContentUpdate();
328
340
  } else if (!selectedItem.keepOpen) {
329
341
  this.close();
330
342
  }
@@ -360,27 +372,33 @@ export const ItemsMixin = (superClass) =>
360
372
  }
361
373
 
362
374
  const subMenu = this._subMenu;
375
+ const expandedItem = this._listBox.querySelector('[expanded]');
363
376
 
364
- if (item) {
377
+ if (item && item !== expandedItem) {
365
378
  const { children } = item._item;
366
379
 
367
380
  // Check if the sub-menu was focused before closing it.
368
- const child = subMenu._overlayElement.getFirstChild();
381
+ const child = subMenu._overlayElement._contentRoot.firstElementChild;
369
382
  const isSubmenuFocused = child && child.focused;
370
383
 
371
- if (subMenu.items !== children) {
384
+ // Mark previously expanded item as collapsed
385
+ if (expandedItem) {
386
+ this.__updateExpanded(expandedItem, false);
387
+ }
388
+
389
+ // Close sub-menu if there are no children for the new item
390
+ if (!children || !children.length) {
372
391
  subMenu.close();
373
392
  }
393
+
374
394
  if (!this.opened) {
375
395
  return;
376
396
  }
377
397
 
378
398
  if (children && children.length) {
399
+ // Open or update the submenu if the new item has children
379
400
  this.__updateExpanded(item, true);
380
-
381
- // Forward parent overlay class
382
- const { overlayClass } = this;
383
- this.__openSubMenu(subMenu, item, overlayClass);
401
+ this.__openSubMenu(subMenu, item);
384
402
  } else if (isSubmenuFocused) {
385
403
  // If the sub-menu item was focused, focus its parent item.
386
404
  subMenu.listenOn.focus();
@@ -391,24 +409,25 @@ export const ItemsMixin = (superClass) =>
391
409
  }
392
410
  }
393
411
 
412
+ /** @protected */
413
+ __getListBox() {
414
+ return this._overlayElement._contentRoot.querySelector(`${this._tagNamePrefix}-list-box`);
415
+ }
416
+
394
417
  /**
395
418
  * @param {!HTMLElement} root
396
419
  * @param {!ContextMenu} menu
397
- * @param {!ContextMenuRendererContext} context
398
420
  * @protected
399
421
  */
400
- __itemsRenderer(root, menu, { detail }) {
422
+ __itemsRenderer(root, menu) {
401
423
  this.__initMenu(root, menu);
402
424
 
403
- const subMenu = root.querySelector(this.constructor.is);
404
- subMenu.closeOn = menu.closeOn;
405
-
406
- const listBox = root.querySelector(`${this._tagNamePrefix}-list-box`);
407
- listBox.innerHTML = '';
425
+ this._subMenu.closeOn = menu.closeOn;
426
+ this._listBox.innerHTML = '';
408
427
 
409
- [...(detail.children || menu.items)].forEach((item) => {
428
+ menu.items.forEach((item) => {
410
429
  const component = this.__createComponent(item);
411
- listBox.appendChild(component);
430
+ this._listBox.appendChild(component);
412
431
  });
413
432
  }
414
433
 
@@ -449,8 +468,9 @@ export const ItemsMixin = (superClass) =>
449
468
  root.appendChild(listBox);
450
469
 
451
470
  const subMenu = this.__initSubMenu();
471
+ subMenu.slot = 'submenu';
452
472
  this._subMenu = subMenu;
453
- root.appendChild(subMenu);
473
+ this.appendChild(subMenu);
454
474
 
455
475
  requestAnimationFrame(() => {
456
476
  this.__openListenerActive = true;
@@ -18,9 +18,4 @@ export declare class MenuOverlayMixinClass {
18
18
  * Returns the adjusted boundaries of the overlay.
19
19
  */
20
20
  getBoundaries(): { xMax: number; xMin: number; yMax: number };
21
-
22
- /**
23
- * Returns the first element in the overlay content.
24
- */
25
- getFirstChild(): HTMLElement;
26
21
  }
@@ -3,7 +3,6 @@
3
3
  * Copyright (c) 2016 - 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 { getClosestElement } from '@vaadin/component-base/src/dom-utils.js';
7
6
  import { OverlayFocusMixin } from '@vaadin/overlay/src/vaadin-overlay-focus-mixin.js';
8
7
  import { PositionMixin } from '@vaadin/overlay/src/vaadin-overlay-position-mixin.js';
9
8
 
@@ -37,6 +36,23 @@ export const MenuOverlayMixin = (superClass) =>
37
36
  return ['_themeChanged(_theme)'];
38
37
  }
39
38
 
39
+ /**
40
+ * Override method from OverlayFocusMixin to use slotted div as the content root.
41
+ * @protected
42
+ * @override
43
+ */
44
+ get _contentRoot() {
45
+ if (!this.__savedRoot) {
46
+ const root = document.createElement('div');
47
+ root.setAttribute('slot', 'overlay');
48
+ root.style.display = 'contents';
49
+ this.owner.appendChild(root);
50
+ this.__savedRoot = root;
51
+ }
52
+
53
+ return this.__savedRoot;
54
+ }
55
+
40
56
  /** @protected */
41
57
  ready() {
42
58
  super.ready();
@@ -45,7 +61,7 @@ export const MenuOverlayMixin = (superClass) =>
45
61
 
46
62
  this.addEventListener('keydown', (e) => {
47
63
  if (!e.defaultPrevented && e.composedPath()[0] === this.$.overlay && [38, 40].indexOf(e.keyCode) > -1) {
48
- const child = this.getFirstChild();
64
+ const child = this._contentRoot.firstElementChild;
49
65
  if (child && Array.isArray(child.items) && child.items.length) {
50
66
  e.preventDefault();
51
67
  if (e.keyCode === 38) {
@@ -58,15 +74,6 @@ export const MenuOverlayMixin = (superClass) =>
58
74
  });
59
75
  }
60
76
 
61
- /**
62
- * Returns the first element in the overlay content.
63
- *
64
- * @returns {HTMLElement}
65
- */
66
- getFirstChild() {
67
- return this.querySelector(':not(style):not(slot)');
68
- }
69
-
70
77
  /** @private */
71
78
  _themeChanged() {
72
79
  this.close();
@@ -106,7 +113,7 @@ export const MenuOverlayMixin = (superClass) =>
106
113
  _updatePosition() {
107
114
  super._updatePosition();
108
115
 
109
- if (this.positionTarget && this.parentOverlay) {
116
+ if (this.positionTarget && this.parentOverlay && this.opened) {
110
117
  // This overlay is positioned by a parent menu item,
111
118
  // adjust the position by the overlay content paddings
112
119
  const content = this.$.content;
@@ -149,9 +156,9 @@ export const MenuOverlayMixin = (superClass) =>
149
156
  }
150
157
 
151
158
  /**
152
- * Override method inherited from `OverlayFocusMixin` to return
153
- * true if the overlay contains the given node, including
154
- * those within descendant menu overlays.
159
+ * Override method inherited from `OverlayFocusMixin` to check if the
160
+ * node is contained within the overlay's owner element (the menu),
161
+ * where all content (overlay content, sub-menus, etc.) is slotted.
155
162
  *
156
163
  * @protected
157
164
  * @override
@@ -159,18 +166,6 @@ export const MenuOverlayMixin = (superClass) =>
159
166
  * @return {boolean}
160
167
  */
161
168
  _deepContains(node) {
162
- // Find the closest menu overlay for the given node.
163
- let overlay = getClosestElement(this.localName, node);
164
- while (overlay) {
165
- if (overlay === this) {
166
- // The node is inside a descendant menu overlay.
167
- return true;
168
- }
169
-
170
- // Traverse the overlay hierarchy to check parent overlays.
171
- overlay = overlay.parentOverlay;
172
- }
173
-
174
- return false;
169
+ return this.owner.contains(node);
175
170
  }
176
171
  };
@@ -1,2 +1,2 @@
1
- import './theme/lumo/vaadin-context-menu.js';
1
+ import './src/vaadin-context-menu.js';
2
2
  export * from './src/vaadin-context-menu.js';