@m3e/nav-menu 1.0.0-rc.1 → 1.0.0-rc.3
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 +2629 -8
- package/dist/html-custom-data.json +1 -1
- package/dist/index.js +21 -4
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +37 -37
- package/dist/index.min.js.map +1 -1
- package/dist/src/NavMenuElement.d.ts +0 -1
- package/dist/src/NavMenuElement.d.ts.map +1 -1
- package/dist/src/NavMenuItemElement.d.ts +3 -1
- package/dist/src/NavMenuItemElement.d.ts.map +1 -1
- package/package.json +4 -4
- package/cem.config.mjs +0 -16
- package/demo/index.html +0 -64
- package/eslint.config.mjs +0 -13
- package/rollup.config.js +0 -32
- package/src/NavMenuElement.ts +0 -325
- package/src/NavMenuItemElement.ts +0 -560
- package/src/index.ts +0 -2
- package/tsconfig.json +0 -9
|
@@ -1,560 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */
|
|
2
|
-
import { css, CSSResultGroup, html, LitElement, PropertyValues, unsafeCSS } from "lit";
|
|
3
|
-
import { customElement, property, query, state } from "lit/decorators.js";
|
|
4
|
-
import { ifDefined } from "lit/directives/if-defined.js";
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
AttachInternals,
|
|
8
|
-
DesignToken,
|
|
9
|
-
Disabled,
|
|
10
|
-
EventAttribute,
|
|
11
|
-
hasAssignedNodes,
|
|
12
|
-
M3eFocusRingElement,
|
|
13
|
-
M3eRippleElement,
|
|
14
|
-
M3eStateLayerElement,
|
|
15
|
-
Role,
|
|
16
|
-
Selected,
|
|
17
|
-
} from "@m3e/core";
|
|
18
|
-
|
|
19
|
-
import { selectionManager } from "@m3e/core/a11y";
|
|
20
|
-
|
|
21
|
-
import type { M3eNavMenuElement } from "./NavMenuElement";
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* @summary
|
|
25
|
-
* An expandable item in a navigation menu.
|
|
26
|
-
*
|
|
27
|
-
* @description
|
|
28
|
-
* The `m3e-nav-menu-item` component represents an expandable, selectable item within a navigation menu.
|
|
29
|
-
* It supports nested child items, selection, disabled and indeterminate states, and emits events for
|
|
30
|
-
* open/close transitions. The component is highly customizable via slots and CSS custom properties, and
|
|
31
|
-
* is designed for accessible, keyboard-navigable menu structures.
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* The following example illustrates a multilevel navigation menu.
|
|
35
|
-
* ```html
|
|
36
|
-
* <m3e-nav-menu>
|
|
37
|
-
* <m3e-nav-menu-item open>
|
|
38
|
-
* <m3e-icon slot="icon" name="rocket_launch"></m3e-icon>
|
|
39
|
-
* <span slot="label">Getting Started</span>
|
|
40
|
-
* <m3e-nav-menu-item>
|
|
41
|
-
* <m3e-icon slot="icon" name="widgets"></m3e-icon>
|
|
42
|
-
* <span slot="label">Overview</span>
|
|
43
|
-
* </m3e-nav-menu-item>
|
|
44
|
-
* <m3e-nav-menu-item>
|
|
45
|
-
* <m3e-icon slot="icon" name="package_2"></m3e-icon>
|
|
46
|
-
* <span slot="label">Installation</span>
|
|
47
|
-
* </m3e-nav-menu-item>
|
|
48
|
-
* </m3e-nav-menu-item>
|
|
49
|
-
* <m3e-nav-menu-item>
|
|
50
|
-
* <span slot="label">Actions</span>
|
|
51
|
-
* <m3e-nav-menu-item><span slot="label">Button</span></m3e-nav-menu-item>
|
|
52
|
-
* <m3e-nav-menu-item><span slot="label">Icon</span></m3e-nav-menu-item>
|
|
53
|
-
* <m3e-nav-menu-item><span slot="label">Icon Button</span></m3e-nav-menu-item>
|
|
54
|
-
* </m3e-nav-menu-item>
|
|
55
|
-
* </m3e-nav-menu>
|
|
56
|
-
* ```
|
|
57
|
-
*
|
|
58
|
-
* @tag m3e-nav-menu-item
|
|
59
|
-
*
|
|
60
|
-
* @slot - Renders the nested child items.
|
|
61
|
-
* @slot label - Renders the label of the item.
|
|
62
|
-
* @slot icon - Renders the icon of the item.
|
|
63
|
-
* @slot selected-icon - Renders the icon of the item when selected.
|
|
64
|
-
* @slot toggle-icon - Renders the toggle icon.
|
|
65
|
-
*
|
|
66
|
-
* @attr disabled - Whether the element is disabled.
|
|
67
|
-
* @attr indeterminate - Whether the element's selected / checked state is indeterminate.
|
|
68
|
-
* @attr open - Whether the item is expanded.
|
|
69
|
-
* @attr selected - Whether the item is selected.
|
|
70
|
-
*
|
|
71
|
-
* @fires opening - Emitted when the item begins to open.
|
|
72
|
-
* @fires opened - Emitted when the item has opened.
|
|
73
|
-
* @fires closing - Emitted when the item begins to close.
|
|
74
|
-
* @fires closed - Emitted when the item has closed.
|
|
75
|
-
*
|
|
76
|
-
* @cssprop --m3e-nav-menu-item-font-size - Font size for the item label.
|
|
77
|
-
* @cssprop --m3e-nav-menu-item-font-weight - Font weight for the item label.
|
|
78
|
-
* @cssprop --m3e-nav-menu-item-line-height - Line height for the item label.
|
|
79
|
-
* @cssprop --m3e-nav-menu-item-tracking - Letter spacing for the item label.
|
|
80
|
-
* @cssprop --m3e-nav-menu-item-padding - Inline padding for the item.
|
|
81
|
-
* @cssprop --m3e-nav-menu-item-height - Height of the item.
|
|
82
|
-
* @cssprop --m3e-nav-menu-item-spacing - Spacing between icon and label.
|
|
83
|
-
* @cssprop --m3e-nav-menu-item-shape - Border radius of the item and focus ring.
|
|
84
|
-
* @cssprop --m3e-nav-menu-item-icon-size - Size of the icon.
|
|
85
|
-
* @cssprop --m3e-nav-menu-item-inset - Indentation for nested items.
|
|
86
|
-
* @cssprop --m3e-nav-menu-item-label-color - Text color for the item label.
|
|
87
|
-
* @cssprop --m3e-nav-menu-item-selected-label-color - Text color for selected item label.
|
|
88
|
-
* @cssprop --m3e-nav-menu-item-selected-container-color - Background color for selected item.
|
|
89
|
-
* @cssprop --m3e-nav-menu-item-selected-container-focus-color - Focus color for selected item container.
|
|
90
|
-
* @cssprop --m3e-nav-menu-item-selected-container-hover-color - Hover color for selected item container.
|
|
91
|
-
* @cssprop --m3e-nav-menu-item-selected-ripple-color - Ripple color for selected item.
|
|
92
|
-
* @cssprop --m3e-nav-menu-item-unselected-container-focus-color - Focus color for unselected item container.
|
|
93
|
-
* @cssprop --m3e-nav-menu-item-unselected-container-hover-color - Hover color for unselected item container.
|
|
94
|
-
* @cssprop --m3e-nav-menu-item-unselected-ripple-color - Ripple color for unselected item.
|
|
95
|
-
* @cssprop --m3e-nav-menu-item-open-container-color - Background color for open item with children.
|
|
96
|
-
* @cssprop --m3e-nav-menu-item-open-container-focus-color - Focus color for open item container.
|
|
97
|
-
* @cssprop --m3e-nav-menu-item-open-container-hover-color - Hover color for open item container.
|
|
98
|
-
* @cssprop --m3e-nav-menu-item-open-ripple-color - Ripple color for open item.
|
|
99
|
-
* @cssprop --m3e-nav-menu-item-disabled-color - Text color for disabled item.
|
|
100
|
-
* @cssprop --m3e-nav-menu-item-disabled-color-opacity - Opacity for disabled item text color.
|
|
101
|
-
* @cssprop --m3e-nav-menu-item-badge-font-size - Font size for badge slot.
|
|
102
|
-
* @cssprop --m3e-nav-menu-item-badge-font-weight - Font weight for badge slot.
|
|
103
|
-
* @cssprop --m3e-nav-menu-item-badge-line-height - Line height for badge slot.
|
|
104
|
-
* @cssprop --m3e-nav-menu-badge-item-tracking - Letter spacing for badge slot.
|
|
105
|
-
* @cssprop --m3e-nav-menu-divider-margin - Margin for divider elements.
|
|
106
|
-
* @cssprop --m3e-nav-menu-item-vertical-inset - Vertical margin for first/last child items.
|
|
107
|
-
*/
|
|
108
|
-
@customElement("m3e-nav-menu-item")
|
|
109
|
-
export class M3eNavMenuItemElement extends Selected(
|
|
110
|
-
Disabled(
|
|
111
|
-
EventAttribute(AttachInternals(Role(LitElement, "treeitem"), true), "opening", "opened", "closing", "closed")
|
|
112
|
-
)
|
|
113
|
-
) {
|
|
114
|
-
/** The styles of the element. */
|
|
115
|
-
static override styles: CSSResultGroup = css`
|
|
116
|
-
:host {
|
|
117
|
-
display: block;
|
|
118
|
-
flex: none;
|
|
119
|
-
outline: none;
|
|
120
|
-
position: relative;
|
|
121
|
-
font-size: var(--m3e-nav-menu-item-font-size, ${DesignToken.typescale.standard.label.large.fontSize});
|
|
122
|
-
font-weight: var(--m3e-nav-menu-item-font-weight, ${DesignToken.typescale.standard.label.large.fontWeight});
|
|
123
|
-
line-height: var(--m3e-nav-menu-item-line-height, ${DesignToken.typescale.standard.label.large.lineHeight});
|
|
124
|
-
letter-spacing: var(--m3e-nav-menu-item-tracking, ${DesignToken.typescale.standard.label.large.tracking});
|
|
125
|
-
}
|
|
126
|
-
.base {
|
|
127
|
-
display: flex;
|
|
128
|
-
align-items: center;
|
|
129
|
-
position: relative;
|
|
130
|
-
padding-inline: var(--m3e-nav-menu-item-padding, 1.5rem);
|
|
131
|
-
height: calc(var(--m3e-nav-menu-item-height, 3.5rem) + ${DesignToken.density.calc(-3)});
|
|
132
|
-
column-gap: var(--m3e-nav-menu-item-spacing, 0.75rem);
|
|
133
|
-
transition: ${unsafeCSS(
|
|
134
|
-
`color ${DesignToken.motion.duration.short4} ${DesignToken.motion.easing.standard},
|
|
135
|
-
background-color ${DesignToken.motion.duration.short4} ${DesignToken.motion.easing.standard}`
|
|
136
|
-
)};
|
|
137
|
-
}
|
|
138
|
-
.base,
|
|
139
|
-
.focus-ring {
|
|
140
|
-
border-radius: var(--m3e-nav-menu-item-shape, ${DesignToken.shape.corner.full});
|
|
141
|
-
}
|
|
142
|
-
.label {
|
|
143
|
-
flex: 1 1 auto;
|
|
144
|
-
display: flex;
|
|
145
|
-
align-items: center;
|
|
146
|
-
position: relative;
|
|
147
|
-
overflow: hidden;
|
|
148
|
-
vertical-align: middle;
|
|
149
|
-
}
|
|
150
|
-
.icon,
|
|
151
|
-
.toggle {
|
|
152
|
-
flex: none;
|
|
153
|
-
display: flex;
|
|
154
|
-
align-items: center;
|
|
155
|
-
justify-content: center;
|
|
156
|
-
position: relative;
|
|
157
|
-
vertical-align: middle;
|
|
158
|
-
}
|
|
159
|
-
::slotted([slot="badge"]) {
|
|
160
|
-
flex: none;
|
|
161
|
-
position: relative;
|
|
162
|
-
font-size: var(--m3e-nav-menu-item-badge-font-size, ${DesignToken.typescale.standard.label.large.fontSize});
|
|
163
|
-
font-weight: var(--m3e-nav-menu-item-badge-font-weight, ${DesignToken.typescale.standard.label.large.fontWeight});
|
|
164
|
-
line-height: var(--m3e-nav-menu-item-badge-line-height, ${DesignToken.typescale.standard.label.large.lineHeight});
|
|
165
|
-
letter-spacing: var(--m3e-nav-menu-badge-item-tracking, ${DesignToken.typescale.standard.label.large.tracking});
|
|
166
|
-
}
|
|
167
|
-
.toggle {
|
|
168
|
-
transition: ${unsafeCSS(`transform var(--m3e-collapsible-animation-duration, ${DesignToken.motion.duration.medium1})
|
|
169
|
-
${DesignToken.motion.easing.standard}`)};
|
|
170
|
-
}
|
|
171
|
-
:host(:not(.-with-icon)) .icon {
|
|
172
|
-
display: none;
|
|
173
|
-
}
|
|
174
|
-
.icon {
|
|
175
|
-
margin-inline-start: -0.5rem;
|
|
176
|
-
}
|
|
177
|
-
.toggle {
|
|
178
|
-
margin-inline-end: -0.5rem;
|
|
179
|
-
}
|
|
180
|
-
.group {
|
|
181
|
-
padding-inline-start: var(--m3e-nav-menu-item-inset, 1rem);
|
|
182
|
-
}
|
|
183
|
-
:host([open]) .toggle {
|
|
184
|
-
transform: rotate(180deg);
|
|
185
|
-
}
|
|
186
|
-
:host(:not(.-has-items)) .toggle,
|
|
187
|
-
:host(:not(.-has-items)) .group {
|
|
188
|
-
display: none;
|
|
189
|
-
}
|
|
190
|
-
::slotted([slot="selected-icon"]),
|
|
191
|
-
::slotted([slot="icon"]),
|
|
192
|
-
::slotted([slot="toggle-icon"]),
|
|
193
|
-
.toggle-icon {
|
|
194
|
-
vertical-align: middle;
|
|
195
|
-
width: 1em;
|
|
196
|
-
height: 1em;
|
|
197
|
-
font-size: var(--m3e-nav-menu-item-icon-size, 1.5rem);
|
|
198
|
-
}
|
|
199
|
-
:host(:not(:disabled)) .base {
|
|
200
|
-
cursor: pointer;
|
|
201
|
-
}
|
|
202
|
-
:host(:not(:disabled)) .base {
|
|
203
|
-
color: var(--m3e-nav-menu-item-label-color, ${DesignToken.color.onSurfaceVariant});
|
|
204
|
-
}
|
|
205
|
-
:host(:disabled) .base {
|
|
206
|
-
color: color-mix(
|
|
207
|
-
in srgb,
|
|
208
|
-
var(--m3e-nav-menu-item-disabled-color, ${DesignToken.color.onSurface})
|
|
209
|
-
var(--m3e-nav-menu-item-disabled-color-opacity, 38%),
|
|
210
|
-
transparent
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
:host([selected]:not(.-has-items):not(:disabled)) .base {
|
|
214
|
-
color: var(--m3e-nav-menu-item-selected-label-color, ${DesignToken.color.onSecondaryContainer});
|
|
215
|
-
background-color: var(--m3e-nav-menu-item-selected-container-color, ${DesignToken.color.secondaryContainer});
|
|
216
|
-
--m3e-state-layer-focus-color: var(
|
|
217
|
-
--m3e-nav-menu-item-selected-container-focus-color,
|
|
218
|
-
${DesignToken.color.onSecondaryContainer}
|
|
219
|
-
);
|
|
220
|
-
--m3e-state-layer-hover-color: var(
|
|
221
|
-
--m3e-nav-menu-item-selected-container-hover-color,
|
|
222
|
-
${DesignToken.color.onSecondaryContainer}
|
|
223
|
-
);
|
|
224
|
-
--m3e-ripple-color: var(--m3e-nav-menu-item-selected-ripple-color, ${DesignToken.color.onSecondaryContainer});
|
|
225
|
-
}
|
|
226
|
-
:host(:not([selected]):not(.-has-items):not(:disabled)) .base {
|
|
227
|
-
--m3e-state-layer-focus-color: var(
|
|
228
|
-
--m3e-nav-menu-item-unselected-container-focus-color,
|
|
229
|
-
${DesignToken.color.onSurface}
|
|
230
|
-
);
|
|
231
|
-
--m3e-state-layer-hover-color: var(
|
|
232
|
-
--m3e-nav-menu-item-unselected-container-hover-color,
|
|
233
|
-
${DesignToken.color.onSurface}
|
|
234
|
-
);
|
|
235
|
-
--m3e-ripple-color: var(--m3e-nav-menu-item-unselected-ripple-color, ${DesignToken.color.onSurface});
|
|
236
|
-
}
|
|
237
|
-
.state-layer {
|
|
238
|
-
margin-inline: auto;
|
|
239
|
-
}
|
|
240
|
-
:host([selected].-has-items:not(:disabled)) .base {
|
|
241
|
-
background-color: var(--m3e-nav-menu-item-open-container-color, ${DesignToken.color.surfaceContainerHighest});
|
|
242
|
-
--m3e-state-layer-focus-color: var(
|
|
243
|
-
--m3e-nav-menu-item-open-container-focus-color,
|
|
244
|
-
${DesignToken.color.onSurface}
|
|
245
|
-
);
|
|
246
|
-
--m3e-state-layer-hover-color: var(
|
|
247
|
-
--m3e-nav-menu-item-open-container-hover-color,
|
|
248
|
-
${DesignToken.color.onSurface}
|
|
249
|
-
);
|
|
250
|
-
--m3e-ripple-color: var(--m3e-nav-menu-item-open-ripple-color, ${DesignToken.color.onSurface});
|
|
251
|
-
}
|
|
252
|
-
::slotted(a[slot="label"]) {
|
|
253
|
-
all: unset;
|
|
254
|
-
}
|
|
255
|
-
::slotted(m3e-divider) {
|
|
256
|
-
margin-block: var(--m3e-nav-menu-divider-margin, 0.25rem);
|
|
257
|
-
}
|
|
258
|
-
::slotted(m3e-nav-menu-item:first-of-type) {
|
|
259
|
-
margin-block-start: var(--m3e-nav-menu-item-vertical-inset, 0.25rem);
|
|
260
|
-
}
|
|
261
|
-
::slotted(m3e-nav-menu-item:last-of-type) {
|
|
262
|
-
margin-block-end: var(--m3e-nav-menu-item-vertical-inset, 0.25rem);
|
|
263
|
-
}
|
|
264
|
-
@media (prefers-reduced-motion) {
|
|
265
|
-
.base,
|
|
266
|
-
.toggle,
|
|
267
|
-
.state-layer {
|
|
268
|
-
transition: none !important;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
@media (forced-colors: active) {
|
|
272
|
-
.base,
|
|
273
|
-
.state-layer {
|
|
274
|
-
transition: none !important;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
:host(:disabled) .base {
|
|
278
|
-
color: GrayText;
|
|
279
|
-
}
|
|
280
|
-
:host(:not([selected]):not(:disabled)) .base {
|
|
281
|
-
color: ButtonText;
|
|
282
|
-
}
|
|
283
|
-
:host([selected]:not(.-has-items):not(:disabled)) .base {
|
|
284
|
-
forced-color-adjust: none;
|
|
285
|
-
color: ButtonFace;
|
|
286
|
-
background-color: ButtonText;
|
|
287
|
-
}
|
|
288
|
-
:host([selected].-has-items:not(:disabled)) .base {
|
|
289
|
-
background-color: unset;
|
|
290
|
-
color: ButtonText;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
`;
|
|
294
|
-
|
|
295
|
-
/** @internal */ @query(".state-layer") readonly stateLayer?: M3eStateLayerElement;
|
|
296
|
-
/** @internal */ @query(".focus-ring") readonly focusRing?: M3eFocusRingElement;
|
|
297
|
-
/** @internal */ @query(".ripple") readonly ripple?: M3eRippleElement;
|
|
298
|
-
/** @private */ @query(".base") private readonly _base?: HTMLElement;
|
|
299
|
-
|
|
300
|
-
/** @private */ @state() private _hasChildItems = false;
|
|
301
|
-
|
|
302
|
-
/** @private */ #items: M3eNavMenuItemElement[] = [];
|
|
303
|
-
/** @private */ #menu: M3eNavMenuElement | null = null;
|
|
304
|
-
/** @private */ #path = new Array<M3eNavMenuItemElement>();
|
|
305
|
-
/** @private */ #link: HTMLAnchorElement | null = null;
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* Whether the item is expanded.
|
|
309
|
-
* @default false
|
|
310
|
-
*/
|
|
311
|
-
@property({ type: Boolean, reflect: true }) open = false;
|
|
312
|
-
|
|
313
|
-
/** A reference to the nested `HTMLAnchorElement`. */
|
|
314
|
-
get link(): HTMLAnchorElement | null {
|
|
315
|
-
return this.#link;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/** Whether the item is visible. */
|
|
319
|
-
get visible(): boolean {
|
|
320
|
-
return !this.#path.some((x) => !x.open);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
/** The full path of the item, starting with the top-most ancestor, including this item. */
|
|
324
|
-
get path(): ReadonlyArray<M3eNavMenuItemElement> {
|
|
325
|
-
return [...this.#path, this];
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/** Whether the item has child items. */
|
|
329
|
-
get hasChildItems(): boolean {
|
|
330
|
-
return this._hasChildItems;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/** The parenting item. */
|
|
334
|
-
get parentItem(): M3eNavMenuItemElement | null {
|
|
335
|
-
return this.#path[this.#path.length - 1] ?? null;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/** The items that immediately descend from this item. */
|
|
339
|
-
get childItems(): readonly M3eNavMenuItemElement[] {
|
|
340
|
-
return this.#items;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/** The one-based level of the item. */
|
|
344
|
-
get level(): number {
|
|
345
|
-
return this.#path.length + 1;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* Expands this item, and optionally, all descendants.
|
|
350
|
-
* @param {boolean} [descendants=false] Whether to expand all descendants.
|
|
351
|
-
*/
|
|
352
|
-
expand(descendants: boolean = false): void {
|
|
353
|
-
if (this.hasChildItems) {
|
|
354
|
-
this.open = true;
|
|
355
|
-
if (descendants) {
|
|
356
|
-
this.childItems.forEach((x) => x.expand(true));
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
/**
|
|
362
|
-
* Collapses this item, and optionally, all descendants.
|
|
363
|
-
* @param {boolean} [descendants=false] Whether to expand all descendants.
|
|
364
|
-
*/
|
|
365
|
-
collapse(descendants: boolean = false): void {
|
|
366
|
-
if (this.hasChildItems) {
|
|
367
|
-
this.open = false;
|
|
368
|
-
if (descendants) {
|
|
369
|
-
this.childItems.forEach((x) => x.collapse(true));
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
/** Toggles the expanded state of the item. */
|
|
375
|
-
toggle(): void {
|
|
376
|
-
if (this.hasChildItems) {
|
|
377
|
-
this.open = !this.open;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
/** @inheritdoc */
|
|
382
|
-
override connectedCallback(): void {
|
|
383
|
-
super.connectedCallback();
|
|
384
|
-
|
|
385
|
-
this.#path.length = 0;
|
|
386
|
-
for (
|
|
387
|
-
let item = this.parentElement?.closest("m3e-nav-menu-item");
|
|
388
|
-
item;
|
|
389
|
-
item = item.parentElement?.closest("m3e-nav-menu-item")
|
|
390
|
-
) {
|
|
391
|
-
this.#path.push(item);
|
|
392
|
-
}
|
|
393
|
-
this.#path.reverse();
|
|
394
|
-
|
|
395
|
-
this.style.setProperty("--_nav-menu-item-level", `${this.level}`);
|
|
396
|
-
this.#menu = this.closest("m3e-nav-menu");
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
/** @inheritdoc */
|
|
400
|
-
override disconnectedCallback(): void {
|
|
401
|
-
super.disconnectedCallback();
|
|
402
|
-
this.#path.length = 0;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
/** @inheritdoc */
|
|
406
|
-
protected override update(changedProperties: PropertyValues<this>): void {
|
|
407
|
-
super.update(changedProperties);
|
|
408
|
-
|
|
409
|
-
if (changedProperties.has("selected")) {
|
|
410
|
-
this.ariaCurrent = this.hasChildItems ? null : `${this.selected}`;
|
|
411
|
-
for (const icon of this.querySelectorAll(":scope > m3e-icon[slot]")) {
|
|
412
|
-
icon.toggleAttribute("filled", this.selected);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
this.#path.forEach((x) => (x.selected = this.selected));
|
|
416
|
-
if (this.selected && !this.hasChildItems) {
|
|
417
|
-
this.closest("m3e-nav-menu")?.[selectionManager].notifySelectionChange(this);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
/** @inheritdoc */
|
|
423
|
-
protected override firstUpdated(_changedProperties: PropertyValues<this>): void {
|
|
424
|
-
super.firstUpdated(_changedProperties);
|
|
425
|
-
|
|
426
|
-
const base = this._base;
|
|
427
|
-
if (base) {
|
|
428
|
-
[this.focusRing, this.stateLayer, this.ripple].forEach((x) => x?.attach(base));
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
/** @inheritdoc */
|
|
433
|
-
protected override render(): unknown {
|
|
434
|
-
return html`<div class="base" @click="${this.#handleClick}">
|
|
435
|
-
<m3e-state-layer class="state-layer" ?disabled="${this.disabled}"></m3e-state-layer>
|
|
436
|
-
<m3e-focus-ring class="focus-ring" inward ?disabled="${this.disabled}"></m3e-focus-ring>
|
|
437
|
-
<m3e-ripple class="ripple" centered ?disabled="${this.disabled}"></m3e-ripple>
|
|
438
|
-
<div class="icon">${this.#renderIcon()}</div>
|
|
439
|
-
<div class="label">
|
|
440
|
-
<slot name="label" @slotchange="${this.#handleSlotChange}"></slot>
|
|
441
|
-
</div>
|
|
442
|
-
<slot name="badge"></slot>
|
|
443
|
-
<div aria-hidden="true" class="toggle">
|
|
444
|
-
<slot name="toggle-icon">
|
|
445
|
-
<svg class="toggle-icon" viewBox="0 -960 960 960" fill="currentColor">
|
|
446
|
-
<path d="M480-360 280-560h400L480-360Z" />
|
|
447
|
-
</svg>
|
|
448
|
-
</slot>
|
|
449
|
-
</div>
|
|
450
|
-
</div>
|
|
451
|
-
<m3e-collapsible
|
|
452
|
-
class="group"
|
|
453
|
-
role="group"
|
|
454
|
-
aria-hidden="${ifDefined(this._hasChildItems ? undefined : "true")}"
|
|
455
|
-
?open="${this._hasChildItems && this.open}"
|
|
456
|
-
@opening="${this.#handleCollapsibleEvent}"
|
|
457
|
-
@opened="${this.#handleCollapsibleEvent}"
|
|
458
|
-
@closing="${this.#handleCollapsibleEvent}"
|
|
459
|
-
@closed="${this.#handleCollapsibleEvent}"
|
|
460
|
-
>
|
|
461
|
-
<slot @slotchange="${this.#handleItemSlotChange}"></slot>
|
|
462
|
-
</m3e-collapsible>`;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
/** @private */
|
|
466
|
-
#renderIcon(): unknown {
|
|
467
|
-
const icon = html`<slot name="icon" @slotchange="${this.#handleIconSlotChange}"></slot>`;
|
|
468
|
-
return this.selected && !this.hasChildItems
|
|
469
|
-
? html`<slot name="selected-icon" @slotchange="${this.#handleIconSlotChange}">${icon}</slot>`
|
|
470
|
-
: icon;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
/** @private */
|
|
474
|
-
#handleIconSlotChange(e: Event) {
|
|
475
|
-
this.classList.toggle("-with-icon", hasAssignedNodes(<HTMLSlotElement>e.target));
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
/** @private */
|
|
479
|
-
#handleSlotChange(e: Event): void {
|
|
480
|
-
this.#link =
|
|
481
|
-
(<HTMLSlotElement>e.target).assignedElements({ flatten: true }).find((x) => x instanceof HTMLAnchorElement) ??
|
|
482
|
-
null;
|
|
483
|
-
|
|
484
|
-
this.#link?.setAttribute("tabindex", "-1");
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
/** @private */
|
|
488
|
-
#handleItemSlotChange(e: Event): void {
|
|
489
|
-
this.#items = (<HTMLSlotElement>e.target)
|
|
490
|
-
.assignedElements({ flatten: true })
|
|
491
|
-
.filter((x) => x instanceof M3eNavMenuItemElement);
|
|
492
|
-
|
|
493
|
-
const hadChildItems = this._hasChildItems;
|
|
494
|
-
this._hasChildItems = this.#items.length > 0;
|
|
495
|
-
this.classList.toggle("-has-items", this._hasChildItems);
|
|
496
|
-
|
|
497
|
-
if (hadChildItems || this._hasChildItems) {
|
|
498
|
-
this.selected = this.#items.some((x) => x.selected);
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
/** @private */
|
|
503
|
-
#handleClick(): void {
|
|
504
|
-
if (this.disabled) return;
|
|
505
|
-
|
|
506
|
-
this.#menu?.[selectionManager].setActiveItem(this);
|
|
507
|
-
if (!this._hasChildItems) {
|
|
508
|
-
this.#menu?.[selectionManager].select(this);
|
|
509
|
-
this.#path.forEach((x) => (x.selected = this.selected));
|
|
510
|
-
this.#link?.click();
|
|
511
|
-
} else {
|
|
512
|
-
this.toggle();
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
/** @private */
|
|
517
|
-
#handleCollapsibleEvent(e: Event): void {
|
|
518
|
-
e.stopPropagation();
|
|
519
|
-
this.dispatchEvent(new Event(e.type, { bubbles: true }));
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
interface M3eNavMenuItemElementEventMap extends HTMLElementEventMap {
|
|
524
|
-
opening: Event;
|
|
525
|
-
opened: Event;
|
|
526
|
-
closing: Event;
|
|
527
|
-
closed: Event;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
export interface M3eNavMenuItemElement {
|
|
531
|
-
addEventListener<K extends keyof M3eNavMenuItemElementEventMap>(
|
|
532
|
-
type: K,
|
|
533
|
-
listener: (this: M3eNavMenuItemElement, ev: M3eNavMenuItemElementEventMap[K]) => void,
|
|
534
|
-
options?: boolean | AddEventListenerOptions
|
|
535
|
-
): void;
|
|
536
|
-
|
|
537
|
-
addEventListener(
|
|
538
|
-
type: string,
|
|
539
|
-
listener: EventListenerOrEventListenerObject,
|
|
540
|
-
options?: boolean | AddEventListenerOptions
|
|
541
|
-
): void;
|
|
542
|
-
|
|
543
|
-
removeEventListener<K extends keyof M3eNavMenuItemElementEventMap>(
|
|
544
|
-
type: K,
|
|
545
|
-
listener: (this: M3eNavMenuItemElement, ev: M3eNavMenuItemElementEventMap[K]) => void,
|
|
546
|
-
options?: boolean | EventListenerOptions
|
|
547
|
-
): void;
|
|
548
|
-
|
|
549
|
-
removeEventListener(
|
|
550
|
-
type: string,
|
|
551
|
-
listener: EventListenerOrEventListenerObject,
|
|
552
|
-
options?: boolean | EventListenerOptions
|
|
553
|
-
): void;
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
declare global {
|
|
557
|
-
interface HTMLElementTagNameMap {
|
|
558
|
-
"m3e-nav-menu-item": M3eNavMenuItemElement;
|
|
559
|
-
}
|
|
560
|
-
}
|
package/src/index.ts
DELETED