@m3e/menu 1.0.0-rc.1 → 1.0.0-rc.2

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 (40) hide show
  1. package/README.md +1 -2
  2. package/dist/custom-elements.json +3270 -34
  3. package/dist/html-custom-data.json +12 -6
  4. package/dist/index.js +8 -8
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.min.js +7 -7
  7. package/dist/index.min.js.map +1 -1
  8. package/package.json +4 -4
  9. package/cem.config.mjs +0 -16
  10. package/demo/index.html +0 -112
  11. package/dist/src/MenuElement.d.ts +0 -143
  12. package/dist/src/MenuElement.d.ts.map +0 -1
  13. package/dist/src/MenuItemCheckboxElement.d.ts +0 -76
  14. package/dist/src/MenuItemCheckboxElement.d.ts.map +0 -1
  15. package/dist/src/MenuItemElement.d.ts +0 -113
  16. package/dist/src/MenuItemElement.d.ts.map +0 -1
  17. package/dist/src/MenuItemElementBase.d.ts +0 -21
  18. package/dist/src/MenuItemElementBase.d.ts.map +0 -1
  19. package/dist/src/MenuItemGroupElement.d.ts +0 -28
  20. package/dist/src/MenuItemGroupElement.d.ts.map +0 -1
  21. package/dist/src/MenuItemRadioElement.d.ts +0 -77
  22. package/dist/src/MenuItemRadioElement.d.ts.map +0 -1
  23. package/dist/src/MenuPosition.d.ts +0 -5
  24. package/dist/src/MenuPosition.d.ts.map +0 -1
  25. package/dist/src/MenuTriggerElement.d.ts +0 -86
  26. package/dist/src/MenuTriggerElement.d.ts.map +0 -1
  27. package/dist/src/index.d.ts +0 -8
  28. package/dist/src/index.d.ts.map +0 -1
  29. package/eslint.config.mjs +0 -13
  30. package/rollup.config.js +0 -32
  31. package/src/MenuElement.ts +0 -449
  32. package/src/MenuItemCheckboxElement.ts +0 -178
  33. package/src/MenuItemElement.ts +0 -210
  34. package/src/MenuItemElementBase.ts +0 -158
  35. package/src/MenuItemGroupElement.ts +0 -37
  36. package/src/MenuItemRadioElement.ts +0 -169
  37. package/src/MenuPosition.ts +0 -5
  38. package/src/MenuTriggerElement.ts +0 -154
  39. package/src/index.ts +0 -7
  40. package/tsconfig.json +0 -9
@@ -1,86 +0,0 @@
1
- import { CSSResultGroup, LitElement } from "lit";
2
- import type { M3eMenuElement } from "./MenuElement";
3
- declare const M3eMenuTriggerElement_base: import("node_modules/@m3e/core/dist/src/shared/mixins/Constructor").Constructor<import("@m3e/core").HtmlForMixin> & import("node_modules/@m3e/core/dist/src/shared/mixins/Constructor").Constructor & typeof LitElement;
4
- /**
5
- * @summary
6
- * An element, nested within a clickable element, used to open a menu.
7
- *
8
- * @description
9
- * The `m3e-menu-trigger` component is used to open a menu when nested within a clickable element
10
- * such as a button or menu item. It anchors the menu to its invoker, enabling contextual flows and
11
- * nested hierarchies.
12
- *
13
- * @example
14
- * The following example illustrates a basic menu. The `m3e-menu-trigger` is used to trigger a `m3e-menu` specified
15
- * by the `for` attribute when its parenting element is activated.
16
- * ```html
17
- * <m3e-button>
18
- * <m3e-menu-trigger for="menu1">Basic menu</m3e-menu-trigger>
19
- * </m3e-button>
20
- * <m3e-menu id="menu1">
21
- * <m3e-menu-item>Apple</m3e-menu-item>
22
- * <m3e-menu-item>Apricot</m3e-menu-item>
23
- * <m3e-menu-item>Avocado</m3e-menu-item>
24
- * <m3e-menu-item>Green Apple</m3e-menu-item>
25
- * <m3e-menu-item>Green Grapes</m3e-menu-item>
26
- * <m3e-menu-item>Olive</m3e-menu-item>
27
- * <m3e-menu-item>Orange</m3e-menu-item>
28
- * </m3e-menu>
29
- * ```
30
- *
31
- * @example
32
- * The next example illustrates nested menus. Submenus are triggered by placing a `m3e-menu-trigger` inside a `m3e-menu-item`.
33
- * ```html
34
- * <m3e-button>
35
- * <m3e-menu-trigger for="menu2">Nested menus</m3e-menu-trigger>
36
- * </m3e-button>
37
- * <m3e-menu id="menu2">
38
- * <m3e-menu-item>
39
- * <m3e-menu-trigger for="menu3">Fruits with A</m3e-menu-trigger>
40
- * </m3e-menu-item>
41
- * <m3e-menu-item>Grapes</m3e-menu-item>
42
- * <m3e-menu-item>Olive</m3e-menu-item>
43
- * <m3e-menu-item>Orange</m3e-menu-item>
44
- * </m3e-menu>
45
- * <m3e-menu id="menu3">
46
- * <m3e-menu-item>Apricot</m3e-menu-item>
47
- * <m3e-menu-item>Avocado</m3e-menu-item>
48
- * <m3e-menu-item>
49
- * <m3e-menu-trigger for="menu4">Apples</m3e-menu-trigger>
50
- * </m3e-menu-item>
51
- * </m3e-menu>
52
- * <m3e-menu id="menu4">
53
- * <m3e-menu-item>Fuji</m3e-menu-item>
54
- * <m3e-menu-item>Granny Smith</m3e-menu-item>
55
- * <m3e-menu-item>Red Delicious</m3e-menu-item>
56
- * </m3e-menu>
57
- * ```
58
- *
59
- * @tag m3e-menu-trigger
60
- *
61
- * @slot - Renders the contents of the trigger.
62
- */
63
- export declare class M3eMenuTriggerElement extends M3eMenuTriggerElement_base {
64
- #private;
65
- /** The styles of the element. */
66
- static styles: CSSResultGroup;
67
- /** The menu triggered by the element. */
68
- get menu(): M3eMenuElement | null;
69
- /** @inheritdoc */
70
- connectedCallback(): void;
71
- /** @inheritdoc */
72
- disconnectedCallback(): void;
73
- /** @inheritdoc */
74
- attach(control: HTMLElement): void;
75
- /** @inheritdoc */
76
- detach(): void;
77
- /** @inheritdoc */
78
- protected render(): unknown;
79
- }
80
- declare global {
81
- interface HTMLElementTagNameMap {
82
- "m3e-menu-trigger": M3eMenuTriggerElement;
83
- }
84
- }
85
- export {};
86
- //# sourceMappingURL=MenuTriggerElement.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"MenuTriggerElement.d.ts","sourceRoot":"","sources":["../../src/MenuTriggerElement.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,cAAc,EAAQ,UAAU,EAAE,MAAM,KAAK,CAAC;AAM5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH,qBACa,qBAAsB,SAAQ,0BAAiC;;IAC1E,iCAAiC;IACjC,OAAgB,MAAM,EAAE,cAAc,CAOpC;IAIF,yCAAyC;IACzC,IAAI,IAAI,IAAI,cAAc,GAAG,IAAI,CAEhC;IAED,kBAAkB;IACT,iBAAiB,IAAI,IAAI;IAKlC,kBAAkB;IACT,oBAAoB,IAAI,IAAI;IAKrC,kBAAkB;IACT,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;IAkB3C,kBAAkB;IACT,MAAM,IAAI,IAAI;IAcvB,kBAAkB;cACC,MAAM,IAAI,OAAO;CAcrC;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,kBAAkB,EAAE,qBAAqB,CAAC;KAC3C;CACF"}
@@ -1,8 +0,0 @@
1
- export * from "./MenuElement";
2
- export * from "./MenuItemCheckboxElement";
3
- export * from "./MenuItemElement";
4
- export * from "./MenuItemGroupElement";
5
- export * from "./MenuItemRadioElement";
6
- export * from "./MenuPosition";
7
- export * from "./MenuTriggerElement";
8
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,mBAAmB,CAAC;AAClC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC"}
package/eslint.config.mjs DELETED
@@ -1,13 +0,0 @@
1
- import eslint from "@eslint/js";
2
- import tseslint from "typescript-eslint";
3
- import { fileURLToPath } from "url";
4
- import { dirname } from "path";
5
-
6
- export default tseslint.config(eslint.configs.recommended, tseslint.configs.recommended, {
7
- languageOptions: {
8
- parserOptions: {
9
- project: true,
10
- tsconfigRootDir: dirname(fileURLToPath(import.meta.url)),
11
- },
12
- },
13
- });
package/rollup.config.js DELETED
@@ -1,32 +0,0 @@
1
- import resolve from "@rollup/plugin-node-resolve";
2
- import terser from "@rollup/plugin-terser";
3
- import typescript from "@rollup/plugin-typescript";
4
-
5
- const banner = `/**
6
- * @license MIT
7
- * Copyright (c) 2025 matraic
8
- * See LICENSE file in the project root for full license text.
9
- */`;
10
-
11
- export default [
12
- {
13
- input: "src/index.ts",
14
- output: [
15
- {
16
- file: "dist/index.js",
17
- format: "esm",
18
- sourcemap: true,
19
- banner: banner,
20
- },
21
- {
22
- file: "dist/index.min.js",
23
- format: "esm",
24
- sourcemap: true,
25
- banner: banner,
26
- plugins: [terser({ mangle: true })],
27
- },
28
- ],
29
- external: ["@m3e/core", "@m3e/core/a11y", "@m3e/core/anchoring", "lit"],
30
- plugins: [resolve(), typescript()],
31
- },
32
- ];
@@ -1,449 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */
2
- import { css, CSSResultGroup, html, LitElement, unsafeCSS } from "lit";
3
- import { customElement, property } from "lit/decorators.js";
4
-
5
- import { DesignToken, ScrollController, Role } from "@m3e/core";
6
- import { RovingTabIndexManager } from "@m3e/core/a11y";
7
- import { positionAnchor } from "@m3e/core/anchoring";
8
-
9
- import { M3eMenuItemElement } from "./MenuItemElement";
10
- import { MenuPositionX, MenuPositionY } from "./MenuPosition";
11
- import { MenuItemElementBase } from "./MenuItemElementBase";
12
-
13
- /**
14
- * @summary
15
- * Presents a list of choices on a temporary surface.
16
- *
17
- * @description
18
- * The `m3e-menu` component presents a list of choices on a temporary surface, typically anchored to a trigger element.
19
- * It supports dynamic positioning via `position-x` and `position-y` attributes, and renders its contents through the default slot.
20
- *
21
- * @example
22
- * The following example illustrates a basic menu. The `m3e-menu-trigger` is used to trigger a `m3e-menu` specified
23
- * by the `for` attribute when its parenting element is activated.
24
- * ```html
25
- * <m3e-button>
26
- * <m3e-menu-trigger for="menu1">Basic menu</m3e-menu-trigger>
27
- * </m3e-button>
28
- * <m3e-menu id="menu1">
29
- * <m3e-menu-item>Apple</m3e-menu-item>
30
- * <m3e-menu-item>Apricot</m3e-menu-item>
31
- * <m3e-menu-item>Avocado</m3e-menu-item>
32
- * <m3e-menu-item>Green Apple</m3e-menu-item>
33
- * <m3e-menu-item>Green Grapes</m3e-menu-item>
34
- * <m3e-menu-item>Olive</m3e-menu-item>
35
- * <m3e-menu-item>Orange</m3e-menu-item>
36
- * </m3e-menu>
37
- * ```
38
- *
39
- * @example
40
- * The next example illustrates nested menus. Submenus are triggered by placing a `m3e-menu-trigger` inside a `m3e-menu-item`.
41
- * ```html
42
- * <m3e-button>
43
- * <m3e-menu-trigger for="menu2">Nested menus</m3e-menu-trigger>
44
- * </m3e-button>
45
- * <m3e-menu id="menu2">
46
- * <m3e-menu-item>
47
- * <m3e-menu-trigger for="menu3">Fruits with A</m3e-menu-trigger>
48
- * </m3e-menu-item>
49
- * <m3e-menu-item>Grapes</m3e-menu-item>
50
- * <m3e-menu-item>Olive</m3e-menu-item>
51
- * <m3e-menu-item>Orange</m3e-menu-item>
52
- * </m3e-menu>
53
- * <m3e-menu id="menu3">
54
- * <m3e-menu-item>Apricot</m3e-menu-item>
55
- * <m3e-menu-item>Avocado</m3e-menu-item>
56
- * <m3e-menu-item>
57
- * <m3e-menu-trigger for="menu4">Apples</m3e-menu-trigger>
58
- * </m3e-menu-item>
59
- * </m3e-menu>
60
- * <m3e-menu id="menu4">
61
- * <m3e-menu-item>Fuji</m3e-menu-item>
62
- * <m3e-menu-item>Granny Smith</m3e-menu-item>
63
- * <m3e-menu-item>Red Delicious</m3e-menu-item>
64
- * </m3e-menu>
65
- * ```
66
- *
67
- * @tag m3e-menu
68
- *
69
- * @slot - Renders the contents of the menu.
70
- *
71
- * @attr position-x - The position of the menu, on the x-axis.
72
- * @attr position-y - The position of the menu, on the y-axis.
73
- *
74
- * @fires beforetoggle - Dispatched before the toggle state changes.
75
- * @fires toggle - Dispatched after the toggle state has changed.
76
- *
77
- * @cssprop --m3e-menu-container-shape - Controls the corner radius of the menu container.
78
- * @cssprop --m3e-menu-container-min-width - Minimum width of the menu container.
79
- * @cssprop --m3e-menu-container-max-width - Maximum width of the menu container.
80
- * @cssprop --m3e-menu-container-max-height - Maximum height of the menu container.
81
- * @cssprop --m3e-menu-container-padding-block - Vertical padding inside the menu container.
82
- * @cssprop --m3e-menu-container-color - Background color of the menu container.
83
- * @cssprop --m3e-menu-container-elevation - Box shadow elevation of the menu container.
84
- * @cssprop --m3e-menu-divider-spacing - Vertical spacing around slotted `m3e-divider` elements.
85
- */
86
- @customElement("m3e-menu")
87
- export class M3eMenuElement extends Role(LitElement, "menu") {
88
- /** The styles of the element. */
89
- static override styles: CSSResultGroup = css`
90
- :host {
91
- position: absolute;
92
- flex-direction: column;
93
- padding: unset;
94
- margin: unset;
95
- border: unset;
96
- overflow-y: auto;
97
- scrollbar-width: ${DesignToken.scrollbar.thinWidth};
98
- scrollbar-color: ${DesignToken.scrollbar.color};
99
- border-radius: var(--m3e-menu-container-shape, ${DesignToken.shape.corner.extraSmall});
100
- min-width: var(--m3e-menu-container-min-width, 7rem);
101
- max-width: var(--m3e-menu-container-max-width, 17.5rem);
102
- max-height: var(--m3e-menu-container-max-height, 17.5rem);
103
- padding-block: var(--m3e-menu-container-padding-block, 0.5rem);
104
- background-color: var(--m3e-menu-container-color, ${DesignToken.color.surfaceContainer});
105
- box-shadow: var(--m3e-menu-container-elevation, ${DesignToken.elevation.level3});
106
- opacity: 0;
107
- display: none;
108
- transition: ${unsafeCSS(
109
- `opacity ${DesignToken.motion.duration.short2} ${DesignToken.motion.easing.standard},
110
- transform ${DesignToken.motion.duration.short2} ${DesignToken.motion.easing.standard},
111
- overlay ${DesignToken.motion.duration.short2} ${DesignToken.motion.easing.standard} allow-discrete,
112
- display ${DesignToken.motion.duration.short2} ${DesignToken.motion.easing.standard} allow-discrete`
113
- )};
114
- }
115
- :host(:not([submenu])) {
116
- transform: scaleY(0.8);
117
- }
118
- :host(:not([submenu]):popover-open) {
119
- transform: scaleY(1);
120
- }
121
- :host::backdrop {
122
- background-color: transparent;
123
- }
124
- :host(:popover-open) {
125
- display: inline-flex;
126
- opacity: 1;
127
- }
128
- :host(.-bottom) {
129
- transform-origin: top;
130
- }
131
- :host(.-top) {
132
- transform-origin: bottom;
133
- }
134
- :host(.-shift-down) {
135
- margin-top: calc(0px - var(--m3e-menu-container-padding-block, 0.5rem));
136
- }
137
- :host(.-shift-up) {
138
- margin-top: var(--m3e-menu-container-padding-block, 0.5rem);
139
- }
140
- ::slotted(m3e-divider) {
141
- margin-block: var(--m3e-menu-divider-spacing, 0.5rem);
142
- }
143
- @starting-style {
144
- :host(:popover-open) {
145
- opacity: 0;
146
- }
147
- :host(:not([submenu]):popover-open) {
148
- transform: scaleY(0.8);
149
- }
150
- }
151
- @media (prefers-reduced-motion) {
152
- :host {
153
- transition: none;
154
- }
155
- }
156
- @media (forced-colors: active) {
157
- :host {
158
- background-color: Menu;
159
- color: MenuText;
160
- border: 1px solid CanvasText;
161
- }
162
- }
163
- `;
164
-
165
- /** @private */ #trigger?: HTMLElement;
166
- /** @private */ #anchorCleanup?: () => void;
167
-
168
- /** @private */ readonly #listManager = new RovingTabIndexManager<MenuItemElementBase>()
169
- .withWrap()
170
- .withHomeAndEnd()
171
- .withVerticalOrientation();
172
-
173
- /** @private */ readonly #keyDownHandler = (e: KeyboardEvent) => this.#handleKeyDown(e);
174
- /** @private */ readonly #documentClickHandler = (e: MouseEvent) => this.#handleDocumentClick(e);
175
- /** @private */ readonly #scrollController = new ScrollController(this, {
176
- target: null,
177
- callback: (target) =>
178
- target instanceof M3eMenuElement
179
- ? target.items.filter((x) => x instanceof M3eMenuItemElement).forEach((x) => x.submenu?.hide())
180
- : this.hideAll(),
181
- });
182
-
183
- /** @private */ readonly #toggleHandler = (e: ToggleEvent) => {
184
- if (e.newState === "closed") {
185
- this.#anchorCleanup?.();
186
- this.#anchorCleanup = undefined;
187
- } else {
188
- setTimeout(() => {
189
- this.#listManager.setActiveItem(this.#listManager.items.find((x) => !x.disabled));
190
- }, 40);
191
- }
192
- };
193
-
194
- /**
195
- * The position of the menu, on the x-axis.
196
- * @default "after"
197
- */
198
- @property({ attribute: "position-x" }) positionX: MenuPositionX = "after";
199
-
200
- /**
201
- * The position of the menu, on the y-axis.
202
- * @default "below"
203
- */
204
- @property({ attribute: "position-y" }) positionY: MenuPositionY = "below";
205
-
206
- /** The items of the menu. */
207
- get items(): ReadonlyArray<MenuItemElementBase> {
208
- return this.#listManager.items;
209
- }
210
-
211
- /** A value indicating whether the menu is open. */
212
- get isOpen() {
213
- return this.#trigger !== undefined;
214
- }
215
-
216
- /** A value indicating whether the menu is a submenu. */
217
- @property({ type: Boolean, reflect: true }) submenu = false;
218
-
219
- /** @inheritdoc */
220
- override connectedCallback(): void {
221
- super.connectedCallback();
222
-
223
- this.tabIndex = -1;
224
- this.setAttribute("popover", "manual");
225
- this.addEventListener("keydown", this.#keyDownHandler);
226
- this.addEventListener("toggle", this.#toggleHandler);
227
- document.addEventListener("click", this.#documentClickHandler);
228
- }
229
-
230
- /** @inheritdoc */
231
- override disconnectedCallback(): void {
232
- super.disconnectedCallback();
233
-
234
- this.removeEventListener("keydown", this.#keyDownHandler);
235
- this.removeEventListener("toggle", this.#toggleHandler);
236
- document.removeEventListener("click", this.#documentClickHandler);
237
- }
238
-
239
- /**
240
- * Opens the menu.
241
- * @param {HTMLElement} trigger The element that triggered the menu.
242
- * @returns {Promise<void>} A `Promise` that resolves when the menu is opened.
243
- */
244
- async show(trigger: HTMLElement): Promise<void> {
245
- if (this.#trigger && this.#trigger !== trigger) {
246
- this.hide();
247
- }
248
-
249
- this.#anchorCleanup = await positionAnchor(
250
- this,
251
- trigger,
252
- {
253
- position: this.submenu
254
- ? this.positionX === "before"
255
- ? "left-start"
256
- : "right-start"
257
- : this.positionY === "above"
258
- ? this.positionX === "before"
259
- ? "top-end"
260
- : "top-start"
261
- : this.positionX === "before"
262
- ? "bottom-end"
263
- : "bottom-start",
264
- inline: true,
265
- flip: true,
266
- shift: true,
267
- offset: !this.submenu ? 4 : undefined,
268
- },
269
- (x, y, position) => {
270
- if (!this.submenu) {
271
- this.classList.toggle("-top", position.includes("top"));
272
- this.classList.toggle("-bottom", position.includes("bottom"));
273
- } else if (this.#trigger) {
274
- const top = this.#getAbsolutePosition(this.#trigger).y;
275
- this.classList.toggle("-shift-down", false);
276
- this.classList.toggle("-shift-up", false);
277
- this.classList.toggle(Math.round(y) === Math.round(top) ? "-shift-down" : "-shift-up", true);
278
- }
279
-
280
- this.style.left = `${x}px`;
281
- this.style.top = `${y}px`;
282
- }
283
- );
284
-
285
- this.showPopover();
286
-
287
- this.#trigger = trigger;
288
- this.#trigger.ariaExpanded = "true";
289
- this.#scrollController.observe(this.#trigger);
290
- }
291
-
292
- /**
293
- * Hides the menu.
294
- * @param {boolean} [restoreFocus=false] A value indicating whether to restore focus to the menu's trigger.
295
- */
296
- hide(restoreFocus: boolean = false): void {
297
- for (const item of this.#listManager.items) {
298
- const submenu = (<M3eMenuItemElement>item).submenu;
299
- if (submenu && submenu.isOpen) {
300
- submenu.hide();
301
- }
302
- }
303
-
304
- this.hidePopover();
305
-
306
- if (this.#trigger) {
307
- this.#trigger.ariaExpanded = "false";
308
- if (restoreFocus) {
309
- this.#trigger.focus();
310
- }
311
- this.#scrollController.unobserve(this.#trigger);
312
- this.#trigger = undefined;
313
- }
314
- }
315
-
316
- /**
317
- * Closes this menu and any parenting menus.
318
- * @param {boolean} [restoreFocus=false] A value indicating whether to restore focus to the menu's trigger.
319
- */
320
- hideAll(restoreFocus: boolean = false): void {
321
- // eslint-disable-next-line @typescript-eslint/no-this-alias
322
- let menu: M3eMenuElement = this;
323
- while (menu.#trigger) {
324
- const parent = menu.#trigger.closest("m3e-menu");
325
- if (!parent) {
326
- break;
327
- }
328
- menu = parent;
329
- }
330
- menu.hide(restoreFocus);
331
- }
332
-
333
- /**
334
- * Toggles the menu.
335
- * @param {HTMLElement} trigger The element that triggered the menu.
336
- * @returns {Promise<void>} A `Promise` that resolves when the menu is opened or closed.
337
- */
338
- async toggle(trigger: HTMLElement): Promise<void> {
339
- if (this.#trigger) {
340
- this.hide();
341
- } else {
342
- await this.show(trigger);
343
- }
344
- }
345
-
346
- /** @inheritdoc */
347
- protected override render(): unknown {
348
- return html`<slot @slotchange="${this.#handleSlotChange}"></slot>`;
349
- }
350
-
351
- /** @private */
352
- #handleSlotChange(): void {
353
- const { added } = this.#listManager.setItems(
354
- [
355
- ...this.querySelectorAll<MenuItemElementBase>("m3e-menu-item,m3e-menu-item-checkbox,m3e-menu-item-radio"),
356
- ].filter((x) => x.closest("m3e-menu") === this)
357
- );
358
-
359
- if (!this.#listManager.activeItem) {
360
- this.#listManager.updateActiveItem(added.find((x) => !x.disabled));
361
- }
362
- }
363
-
364
- /** @private */
365
- #handleKeyDown(e: KeyboardEvent): void {
366
- switch (e.key) {
367
- case "Left":
368
- case "ArrowLeft":
369
- e.preventDefault();
370
- this.hide(true);
371
- break;
372
-
373
- case "Tab":
374
- this.hideAll();
375
- break;
376
-
377
- case "Escape":
378
- if (!e.shiftKey && !e.ctrlKey) {
379
- this.hide(true);
380
- }
381
- break;
382
-
383
- default:
384
- this.#listManager.onKeyDown(e);
385
- break;
386
- }
387
- }
388
-
389
- /** @private */
390
- #handleDocumentClick(e: MouseEvent): void {
391
- if (!this.submenu && !e.composedPath().some((x) => x instanceof M3eMenuElement || x === this.#trigger)) {
392
- this.hide();
393
- }
394
- }
395
-
396
- /** @private */
397
- #getAbsolutePosition(element: HTMLElement): { x: number; y: number } {
398
- let x = 0,
399
- y = 0;
400
-
401
- for (
402
- let current: HTMLElement | null = element;
403
- current;
404
- current = current.offsetParent instanceof HTMLElement ? current.offsetParent : null
405
- ) {
406
- x += current.offsetLeft - current.scrollLeft + current.clientLeft;
407
- y += current.offsetTop - current.scrollTop + current.clientTop;
408
- }
409
-
410
- return { x, y };
411
- }
412
- }
413
-
414
- interface M3eMenuElementEventMap extends HTMLElementEventMap {
415
- beforetoggle: ToggleEvent;
416
- toggle: ToggleEvent;
417
- }
418
-
419
- export interface M3eMenuElement {
420
- addEventListener<K extends keyof M3eMenuElementEventMap>(
421
- type: K,
422
- listener: (this: M3eMenuElement, ev: M3eMenuElementEventMap[K]) => void,
423
- options?: boolean | AddEventListenerOptions
424
- ): void;
425
-
426
- addEventListener(
427
- type: string,
428
- listener: EventListenerOrEventListenerObject,
429
- options?: boolean | AddEventListenerOptions
430
- ): void;
431
-
432
- removeEventListener<K extends keyof M3eMenuElementEventMap>(
433
- type: K,
434
- listener: (this: M3eMenuElement, ev: M3eMenuElementEventMap[K]) => void,
435
- options?: boolean | EventListenerOptions
436
- ): void;
437
-
438
- removeEventListener(
439
- type: string,
440
- listener: EventListenerOrEventListenerObject,
441
- options?: boolean | EventListenerOptions
442
- ): void;
443
- }
444
-
445
- declare global {
446
- interface HTMLElementTagNameMap {
447
- "m3e-menu": M3eMenuElement;
448
- }
449
- }