@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.
- package/package.json +13 -14
- package/src/styles/vaadin-context-menu-item-base-styles.d.ts +8 -0
- package/src/styles/vaadin-context-menu-item-base-styles.js +34 -0
- package/src/styles/vaadin-context-menu-overlay-base-styles.d.ts +8 -0
- package/src/styles/vaadin-context-menu-overlay-base-styles.js +36 -0
- package/src/{vaadin-menu-overlay-styles.d.ts → styles/vaadin-menu-overlay-base-styles.d.ts} +1 -1
- package/src/{vaadin-menu-overlay-styles.js → styles/vaadin-menu-overlay-base-styles.js} +7 -8
- package/src/vaadin-context-menu-item.js +5 -11
- package/src/vaadin-context-menu-list-box.js +5 -18
- package/src/vaadin-context-menu-mixin.js +118 -75
- package/src/vaadin-context-menu-overlay.js +59 -4
- package/src/vaadin-context-menu.d.ts +48 -11
- package/src/vaadin-context-menu.js +98 -23
- package/src/vaadin-contextmenu-items-mixin.js +61 -41
- package/src/vaadin-menu-overlay-mixin.d.ts +0 -5
- package/src/vaadin-menu-overlay-mixin.js +23 -28
- package/vaadin-context-menu.js +1 -1
- package/web-types.json +10 -6
- package/web-types.lit.json +11 -4
- package/theme/lumo/vaadin-context-menu-item-styles.d.ts +0 -6
- package/theme/lumo/vaadin-context-menu-item-styles.js +0 -45
- package/theme/lumo/vaadin-context-menu-list-box-styles.d.ts +0 -5
- package/theme/lumo/vaadin-context-menu-list-box-styles.js +0 -47
- package/theme/lumo/vaadin-context-menu-overlay-styles.d.ts +0 -4
- package/theme/lumo/vaadin-context-menu-overlay-styles.js +0 -35
- package/theme/lumo/vaadin-context-menu.d.ts +0 -4
- 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
|
-
*
|
|
212
|
-
* themable component as the actual visible context menu overlay.
|
|
231
|
+
* The following shadow DOM parts are available for styling:
|
|
213
232
|
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
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
|
|
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
|
-
*
|
|
179
|
-
* themable component as the actual visible context menu overlay.
|
|
178
|
+
* The following shadow DOM parts are available for styling:
|
|
180
179
|
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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 {
|
|
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 {
|
|
26
|
-
* @property {
|
|
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.
|
|
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
|
|
135
|
-
|
|
136
|
-
subMenu
|
|
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
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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.
|
|
381
|
+
const child = subMenu._overlayElement._contentRoot.firstElementChild;
|
|
369
382
|
const isSubmenuFocused = child && child.focused;
|
|
370
383
|
|
|
371
|
-
|
|
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
|
|
422
|
+
__itemsRenderer(root, menu) {
|
|
401
423
|
this.__initMenu(root, menu);
|
|
402
424
|
|
|
403
|
-
|
|
404
|
-
|
|
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
|
-
|
|
428
|
+
menu.items.forEach((item) => {
|
|
410
429
|
const component = this.__createComponent(item);
|
|
411
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
153
|
-
*
|
|
154
|
-
*
|
|
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
|
-
|
|
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
|
};
|
package/vaadin-context-menu.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import './
|
|
1
|
+
import './src/vaadin-context-menu.js';
|
|
2
2
|
export * from './src/vaadin-context-menu.js';
|