@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.
- package/README.md +1 -2
- package/dist/custom-elements.json +3270 -34
- package/dist/html-custom-data.json +12 -6
- package/dist/index.js +8 -8
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +7 -7
- package/dist/index.min.js.map +1 -1
- package/package.json +4 -4
- package/cem.config.mjs +0 -16
- package/demo/index.html +0 -112
- package/dist/src/MenuElement.d.ts +0 -143
- package/dist/src/MenuElement.d.ts.map +0 -1
- package/dist/src/MenuItemCheckboxElement.d.ts +0 -76
- package/dist/src/MenuItemCheckboxElement.d.ts.map +0 -1
- package/dist/src/MenuItemElement.d.ts +0 -113
- package/dist/src/MenuItemElement.d.ts.map +0 -1
- package/dist/src/MenuItemElementBase.d.ts +0 -21
- package/dist/src/MenuItemElementBase.d.ts.map +0 -1
- package/dist/src/MenuItemGroupElement.d.ts +0 -28
- package/dist/src/MenuItemGroupElement.d.ts.map +0 -1
- package/dist/src/MenuItemRadioElement.d.ts +0 -77
- package/dist/src/MenuItemRadioElement.d.ts.map +0 -1
- package/dist/src/MenuPosition.d.ts +0 -5
- package/dist/src/MenuPosition.d.ts.map +0 -1
- package/dist/src/MenuTriggerElement.d.ts +0 -86
- package/dist/src/MenuTriggerElement.d.ts.map +0 -1
- package/dist/src/index.d.ts +0 -8
- package/dist/src/index.d.ts.map +0 -1
- package/eslint.config.mjs +0 -13
- package/rollup.config.js +0 -32
- package/src/MenuElement.ts +0 -449
- package/src/MenuItemCheckboxElement.ts +0 -178
- package/src/MenuItemElement.ts +0 -210
- package/src/MenuItemElementBase.ts +0 -158
- package/src/MenuItemGroupElement.ts +0 -37
- package/src/MenuItemRadioElement.ts +0 -169
- package/src/MenuPosition.ts +0 -5
- package/src/MenuTriggerElement.ts +0 -154
- package/src/index.ts +0 -7
- 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"}
|
package/dist/src/index.d.ts
DELETED
|
@@ -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
|
package/dist/src/index.d.ts.map
DELETED
|
@@ -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
|
-
];
|
package/src/MenuElement.ts
DELETED
|
@@ -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
|
-
}
|