@schukai/monster 4.43.6 → 4.43.8
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/CHANGELOG.md +20 -0
- package/package.json +1 -1
- package/source/components/navigation/site-navigation.mjs +483 -460
- package/source/components/navigation/style/site-navigation.pcss +0 -16
- package/source/components/navigation/stylesheet/site-navigation.mjs +7 -14
- package/source/types/version.mjs +1 -1
- package/test/cases/monster.mjs +1 -1
- package/test/web/test.html +2 -2
- package/test/web/tests.js +3 -3
|
@@ -13,20 +13,20 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
CustomElement,
|
|
17
|
+
getSlottedElements,
|
|
18
|
+
registerCustomElement,
|
|
19
|
+
assembleMethodSymbol,
|
|
20
20
|
} from "../../dom/customelement.mjs";
|
|
21
21
|
import { DeadMansSwitch } from "../../util/deadmansswitch.mjs";
|
|
22
22
|
import { SiteNavigationStyleSheet } from "./stylesheet/site-navigation.mjs";
|
|
23
23
|
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
computePosition,
|
|
25
|
+
autoUpdate,
|
|
26
|
+
flip,
|
|
27
|
+
shift,
|
|
28
|
+
offset,
|
|
29
|
+
size,
|
|
30
30
|
} from "@floating-ui/dom";
|
|
31
31
|
import { fireCustomEvent } from "../../dom/events.mjs";
|
|
32
32
|
|
|
@@ -64,57 +64,57 @@ const hamburgerCloseButtonSymbol = Symbol("hamburgerCloseButton");
|
|
|
64
64
|
* @fires monster-submenu-hide - Fired when a submenu is hidden. The event detail contains `{context, trigger, submenu, level}`.
|
|
65
65
|
*/
|
|
66
66
|
class SiteNavigation extends CustomElement {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
67
|
+
static get [instanceSymbol]() {
|
|
68
|
+
return Symbol.for("@schukai/monster/components/navigation/site@@instance");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Configuration options for the SiteNavigation component.
|
|
73
|
+
* @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
|
|
74
|
+
*
|
|
75
|
+
* To set these options via an HTML tag, use the `data-monster-options` attribute.
|
|
76
|
+
* The individual configuration values are detailed in the table below.
|
|
77
|
+
*
|
|
78
|
+
* @property {Object} templates - Template definitions.
|
|
79
|
+
* @property {string} templates.main - The main HTML template for the component.
|
|
80
|
+
* @property {string} interactionModel="auto" - Defines the interaction with submenus. Possible values: `auto`, `click`, `hover`. With `auto`, `hover` is used on desktop and `click` is used in the hamburger menu.
|
|
81
|
+
* @property {Object} features - Container for additional feature flags.
|
|
82
|
+
* @property {boolean} features.resetOnClose=true - If `true`, all open submenus within the hamburger menu will be reset when it is closed.
|
|
83
|
+
*/
|
|
84
|
+
get defaults() {
|
|
85
|
+
return Object.assign({}, super.defaults, {
|
|
86
|
+
templates: { main: getTemplate() },
|
|
87
|
+
interactionModel: "auto", // 'auto', 'click', 'hover'
|
|
88
|
+
features: {
|
|
89
|
+
resetOnClose: true,
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
[assembleMethodSymbol]() {
|
|
95
|
+
super[assembleMethodSymbol]();
|
|
96
|
+
initControlReferences.call(this);
|
|
97
|
+
initEventHandler.call(this);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
static getCSSStyleSheet() {
|
|
101
|
+
return [SiteNavigationStyleSheet];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
static getTag() {
|
|
105
|
+
return "monster-site-navigation";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
connectedCallback() {
|
|
109
|
+
super.connectedCallback();
|
|
110
|
+
attachResizeObserver.call(this);
|
|
111
|
+
setTimeout(() => populateTabs.call(this), 0);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
disconnectedCallback() {
|
|
115
|
+
super.disconnectedCallback();
|
|
116
|
+
detachResizeObserver.call(this);
|
|
117
|
+
}
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
/**
|
|
@@ -123,22 +123,22 @@ class SiteNavigation extends CustomElement {
|
|
|
123
123
|
* @this {SiteNavigation}
|
|
124
124
|
*/
|
|
125
125
|
function initControlReferences() {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
126
|
+
if (!this.shadowRoot) throw new Error("Component requires a shadowRoot.");
|
|
127
|
+
this[navElementSymbol] = this.shadowRoot.querySelector(
|
|
128
|
+
'[data-monster-role="navigation"]',
|
|
129
|
+
);
|
|
130
|
+
this[visibleElementsSymbol] =
|
|
131
|
+
this.shadowRoot.querySelector("#visible-elements");
|
|
132
|
+
this[hiddenElementsSymbol] =
|
|
133
|
+
this.shadowRoot.querySelector("#hidden-elements");
|
|
134
|
+
this[hamburgerButtonSymbol] =
|
|
135
|
+
this.shadowRoot.querySelector("#hamburger-button");
|
|
136
|
+
this[hamburgerNavSymbol] = this.shadowRoot.querySelector(
|
|
137
|
+
'[data-monster-role="hamburger-nav"]',
|
|
138
|
+
);
|
|
139
|
+
this[hamburgerCloseButtonSymbol] = this.shadowRoot.querySelector(
|
|
140
|
+
'[part="hamburger-close-button"]',
|
|
141
|
+
);
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
/**
|
|
@@ -147,107 +147,120 @@ function initControlReferences() {
|
|
|
147
147
|
* @this {SiteNavigation}
|
|
148
148
|
*/
|
|
149
149
|
function initEventHandler() {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
150
|
+
if (!this.shadowRoot) throw new Error("Component requires a shadowRoot.");
|
|
151
|
+
|
|
152
|
+
const hamburgerButton = this[hamburgerButtonSymbol];
|
|
153
|
+
const hamburgerNav = this[hamburgerNavSymbol];
|
|
154
|
+
const hamburgerCloseButton = this[hamburgerCloseButtonSymbol];
|
|
155
|
+
let cleanup;
|
|
156
|
+
|
|
157
|
+
if (!hamburgerButton || !hamburgerNav || !hamburgerCloseButton) return;
|
|
158
|
+
|
|
159
|
+
const getBestPositionStrategy = (element) => {
|
|
160
|
+
let parent = element.parentElement;
|
|
161
|
+
while (parent) {
|
|
162
|
+
const parentPosition = window.getComputedStyle(parent).position;
|
|
163
|
+
if (["fixed", "sticky"].includes(parentPosition)) {
|
|
164
|
+
return "fixed";
|
|
165
|
+
}
|
|
166
|
+
parent = parent.parentElement;
|
|
167
|
+
}
|
|
168
|
+
return "absolute";
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const handleOutsideClick = (event) => {
|
|
172
|
+
if (
|
|
173
|
+
!hamburgerButton.contains(event.target) &&
|
|
174
|
+
!hamburgerNav.contains(event.target)
|
|
175
|
+
) {
|
|
176
|
+
hideMenu();
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const hideMenu = () => {
|
|
181
|
+
hamburgerNav.style.display = "none";
|
|
182
|
+
document.body.classList.remove("monster-navigation-open");
|
|
183
|
+
|
|
184
|
+
fireCustomEvent(this, "monster-hamburger-hide", {
|
|
185
|
+
button: hamburgerButton,
|
|
186
|
+
menu: hamburgerNav,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
if (this.getOption("features.resetOnClose") === true) {
|
|
190
|
+
this[hiddenElementsSymbol]
|
|
191
|
+
.querySelectorAll(".is-open")
|
|
192
|
+
.forEach((submenu) => submenu.classList.remove("is-open"));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (cleanup) {
|
|
196
|
+
cleanup();
|
|
197
|
+
cleanup = undefined;
|
|
198
|
+
}
|
|
199
|
+
document.removeEventListener("click", handleOutsideClick);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
this[hideHamburgerMenuSymbol] = hideMenu;
|
|
203
|
+
|
|
204
|
+
const showMenu = () => {
|
|
205
|
+
this[activeSubmenuHiderSymbol]?.();
|
|
206
|
+
hamburgerNav.style.display = "block";
|
|
207
|
+
document.body.classList.add("monster-navigation-open");
|
|
208
|
+
|
|
209
|
+
fireCustomEvent(this, "monster-hamburger-show", {
|
|
210
|
+
button: hamburgerButton,
|
|
211
|
+
menu: hamburgerNav,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
cleanup = autoUpdate(hamburgerButton, hamburgerNav, () => {
|
|
215
|
+
if (window.innerWidth > 768) {
|
|
216
|
+
const strategy = getBestPositionStrategy(this);
|
|
217
|
+
|
|
218
|
+
computePosition(hamburgerButton, hamburgerNav, {
|
|
219
|
+
placement: "bottom-end",
|
|
220
|
+
strategy: strategy,
|
|
221
|
+
middleware: [
|
|
222
|
+
offset(8),
|
|
223
|
+
flip(),
|
|
224
|
+
shift({ padding: 8 }),
|
|
225
|
+
size({
|
|
226
|
+
apply: ({ availableHeight, elements }) => {
|
|
227
|
+
Object.assign(elements.floating.style, {
|
|
228
|
+
maxHeight: `${availableHeight}px`,
|
|
229
|
+
overflowY: "auto",
|
|
230
|
+
});
|
|
231
|
+
},
|
|
232
|
+
padding: 8,
|
|
233
|
+
}),
|
|
234
|
+
],
|
|
235
|
+
}).then(({ x, y, strategy }) => {
|
|
236
|
+
Object.assign(hamburgerNav.style, {
|
|
237
|
+
position: strategy,
|
|
238
|
+
left: `${x}px`,
|
|
239
|
+
top: `${y}px`,
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
} else {
|
|
243
|
+
// Mobile view (fullscreen overlay), position is handled by CSS
|
|
244
|
+
Object.assign(hamburgerNav.style, { position: "", left: "", top: "" });
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
setTimeout(() => document.addEventListener("click", handleOutsideClick), 0);
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
hamburgerButton.addEventListener("click", (event) => {
|
|
251
|
+
event.stopPropagation();
|
|
252
|
+
const isVisible = hamburgerNav.style.display === "block";
|
|
253
|
+
if (isVisible) {
|
|
254
|
+
hideMenu();
|
|
255
|
+
} else {
|
|
256
|
+
showMenu();
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
hamburgerCloseButton.addEventListener("click", (event) => {
|
|
261
|
+
event.stopPropagation();
|
|
262
|
+
hideMenu();
|
|
263
|
+
});
|
|
251
264
|
}
|
|
252
265
|
|
|
253
266
|
/**
|
|
@@ -257,20 +270,20 @@ function initEventHandler() {
|
|
|
257
270
|
* @this {SiteNavigation}
|
|
258
271
|
*/
|
|
259
272
|
function attachResizeObserver() {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
273
|
+
this[resizeObserverSymbol] = new ResizeObserver(() => {
|
|
274
|
+
if (this[timerCallbackSymbol] instanceof DeadMansSwitch) {
|
|
275
|
+
try {
|
|
276
|
+
this[timerCallbackSymbol].touch();
|
|
277
|
+
return;
|
|
278
|
+
} catch (e) {
|
|
279
|
+
delete this[timerCallbackSymbol];
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
this[timerCallbackSymbol] = new DeadMansSwitch(200, () =>
|
|
283
|
+
populateTabs.call(this),
|
|
284
|
+
);
|
|
285
|
+
});
|
|
286
|
+
this[resizeObserverSymbol].observe(this[navElementSymbol]);
|
|
274
287
|
}
|
|
275
288
|
|
|
276
289
|
/**
|
|
@@ -279,10 +292,10 @@ function attachResizeObserver() {
|
|
|
279
292
|
* @this {SiteNavigation}
|
|
280
293
|
*/
|
|
281
294
|
function detachResizeObserver() {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
295
|
+
if (this[resizeObserverSymbol] instanceof ResizeObserver) {
|
|
296
|
+
this[resizeObserverSymbol].disconnect();
|
|
297
|
+
delete this[resizeObserverSymbol];
|
|
298
|
+
}
|
|
286
299
|
}
|
|
287
300
|
|
|
288
301
|
/**
|
|
@@ -295,167 +308,177 @@ function detachResizeObserver() {
|
|
|
295
308
|
* @param {number} level The nesting level of the submenu (starts at 1).
|
|
296
309
|
*/
|
|
297
310
|
function setupSubmenu(parentLi, context = "visible", level = 1) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
311
|
+
const submenu = parentLi.querySelector(
|
|
312
|
+
":scope > ul, :scope > div[part='mega-menu']",
|
|
313
|
+
);
|
|
314
|
+
if (!submenu) return;
|
|
315
|
+
|
|
316
|
+
if (submenu.tagName === "UL") {
|
|
317
|
+
submenu.setAttribute("part", "submenu");
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const interaction = this.getOption("interactionModel", "auto");
|
|
321
|
+
const useHover =
|
|
322
|
+
interaction === "hover" ||
|
|
323
|
+
(interaction === "auto" && context === "visible");
|
|
324
|
+
|
|
325
|
+
if (useHover) {
|
|
326
|
+
const component = this;
|
|
327
|
+
let cleanup;
|
|
328
|
+
let hideTimeout;
|
|
329
|
+
let isHovering = false;
|
|
330
|
+
|
|
331
|
+
const immediateHide = () => {
|
|
332
|
+
submenu.style.display = "none";
|
|
333
|
+
Object.assign(submenu.style, {
|
|
334
|
+
maxHeight: "",
|
|
335
|
+
overflowY: "",
|
|
336
|
+
});
|
|
337
|
+
submenu
|
|
338
|
+
.querySelectorAll(
|
|
339
|
+
"ul[style*='display: block'], div[part='mega-menu'][style*='display: block']",
|
|
340
|
+
)
|
|
341
|
+
.forEach((sub) => {
|
|
342
|
+
sub.style.display = "none";
|
|
343
|
+
});
|
|
344
|
+
fireCustomEvent(this, "monster-submenu-hide", {
|
|
345
|
+
context,
|
|
346
|
+
trigger: parentLi,
|
|
347
|
+
submenu,
|
|
348
|
+
level,
|
|
349
|
+
});
|
|
350
|
+
if (cleanup) {
|
|
351
|
+
cleanup();
|
|
352
|
+
cleanup = null;
|
|
353
|
+
}
|
|
354
|
+
if (
|
|
355
|
+
level === 1 &&
|
|
356
|
+
component[activeSubmenuHiderSymbol] === immediateHide
|
|
357
|
+
) {
|
|
358
|
+
component[activeSubmenuHiderSymbol] = null;
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
const show = () => {
|
|
363
|
+
component[hideHamburgerMenuSymbol]?.();
|
|
364
|
+
if (level === 1) {
|
|
365
|
+
if (
|
|
366
|
+
component[activeSubmenuHiderSymbol] &&
|
|
367
|
+
component[activeSubmenuHiderSymbol] !== immediateHide
|
|
368
|
+
) {
|
|
369
|
+
component[activeSubmenuHiderSymbol]();
|
|
370
|
+
}
|
|
371
|
+
component[activeSubmenuHiderSymbol] = immediateHide;
|
|
372
|
+
} else {
|
|
373
|
+
[...parentLi.parentElement.children]
|
|
374
|
+
.filter((li) => li !== parentLi)
|
|
375
|
+
.forEach((sibling) => {
|
|
376
|
+
const siblingSubmenu = sibling.querySelector(
|
|
377
|
+
":scope > ul, :scope > div[part='mega-menu']",
|
|
378
|
+
);
|
|
379
|
+
if (siblingSubmenu) {
|
|
380
|
+
siblingSubmenu.style.display = "none";
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
submenu.style.display = "block";
|
|
385
|
+
fireCustomEvent(this, "monster-submenu-show", {
|
|
386
|
+
context,
|
|
387
|
+
trigger: parentLi,
|
|
388
|
+
submenu,
|
|
389
|
+
level,
|
|
390
|
+
});
|
|
391
|
+
if (!cleanup) {
|
|
392
|
+
cleanup = autoUpdate(parentLi, submenu, () => {
|
|
393
|
+
const middleware = [offset(8), flip(), shift({ padding: 8 })];
|
|
394
|
+
const containsSubmenus = submenu.querySelector(
|
|
395
|
+
"ul, div[part='mega-menu']",
|
|
396
|
+
);
|
|
397
|
+
if (!containsSubmenus) {
|
|
398
|
+
middleware.push(
|
|
399
|
+
size({
|
|
400
|
+
apply: ({ availableHeight, elements }) => {
|
|
401
|
+
Object.assign(elements.floating.style, {
|
|
402
|
+
maxHeight: `${availableHeight}px`,
|
|
403
|
+
overflowY: "auto",
|
|
404
|
+
});
|
|
405
|
+
},
|
|
406
|
+
padding: 8,
|
|
407
|
+
}),
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
computePosition(parentLi, submenu, {
|
|
411
|
+
placement: level === 1 ? "bottom-start" : "right-start",
|
|
412
|
+
middleware: middleware,
|
|
413
|
+
}).then(({ x, y, strategy }) => {
|
|
414
|
+
Object.assign(submenu.style, {
|
|
415
|
+
position: strategy,
|
|
416
|
+
left: `${x}px`,
|
|
417
|
+
top: `${y}px`,
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
const handleMouseEnter = () => {
|
|
425
|
+
isHovering = true; // Status auf "aktiv" setzen
|
|
426
|
+
clearTimeout(hideTimeout);
|
|
427
|
+
if (submenu.style.display !== "block") {
|
|
428
|
+
show();
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
const handleMouseLeave = () => {
|
|
433
|
+
isHovering = false; // Status auf "inaktiv" setzen
|
|
434
|
+
hideTimeout = setTimeout(() => {
|
|
435
|
+
if (!isHovering) {
|
|
436
|
+
immediateHide();
|
|
437
|
+
}
|
|
438
|
+
}, 250);
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
parentLi.addEventListener("mouseenter", handleMouseEnter);
|
|
442
|
+
parentLi.addEventListener("mouseleave", handleMouseLeave);
|
|
443
|
+
submenu.addEventListener("mouseenter", handleMouseEnter);
|
|
444
|
+
submenu.addEventListener("mouseleave", handleMouseLeave);
|
|
445
|
+
} else {
|
|
446
|
+
const anchor = parentLi.querySelector(":scope > a");
|
|
447
|
+
if (anchor) {
|
|
448
|
+
anchor.addEventListener("click", (event) => {
|
|
449
|
+
event.preventDefault();
|
|
450
|
+
event.stopPropagation();
|
|
451
|
+
if (!submenu.classList.contains("is-open")) {
|
|
452
|
+
[...parentLi.parentElement.children]
|
|
453
|
+
.filter((li) => li !== parentLi)
|
|
454
|
+
.forEach((sibling) => {
|
|
455
|
+
const siblingSubmenu = sibling.querySelector(
|
|
456
|
+
":scope > ul, :scope > div[part='mega-menu']",
|
|
457
|
+
);
|
|
458
|
+
if (siblingSubmenu) {
|
|
459
|
+
siblingSubmenu.classList.remove("is-open");
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
const isOpen = submenu.classList.toggle("is-open");
|
|
464
|
+
const eventName = isOpen
|
|
465
|
+
? "monster-submenu-show"
|
|
466
|
+
: "monster-submenu-hide";
|
|
467
|
+
fireCustomEvent(this, eventName, {
|
|
468
|
+
context,
|
|
469
|
+
trigger: parentLi,
|
|
470
|
+
submenu,
|
|
471
|
+
level,
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (submenu.tagName === "UL") {
|
|
478
|
+
submenu
|
|
479
|
+
.querySelectorAll(":scope > li")
|
|
480
|
+
.forEach((li) => setupSubmenu.call(this, li, context, level + 1));
|
|
481
|
+
}
|
|
459
482
|
}
|
|
460
483
|
|
|
461
484
|
/**
|
|
@@ -466,20 +489,20 @@ function setupSubmenu(parentLi, context = "visible", level = 1) {
|
|
|
466
489
|
* @returns {HTMLLIElement} The cloned and configured list item.
|
|
467
490
|
*/
|
|
468
491
|
function cloneNavItem(item) {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
492
|
+
const liClone = item.cloneNode(true);
|
|
493
|
+
const aClone = liClone.querySelector("a");
|
|
494
|
+
let navItemPart = "nav-item";
|
|
495
|
+
let navLinkPart = "nav-link";
|
|
473
496
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
497
|
+
if (item.classList.contains("active")) {
|
|
498
|
+
navItemPart += " nav-item-active";
|
|
499
|
+
if (aClone) navLinkPart += " nav-link-active";
|
|
500
|
+
}
|
|
478
501
|
|
|
479
|
-
|
|
480
|
-
|
|
502
|
+
liClone.setAttribute("part", navItemPart);
|
|
503
|
+
if (aClone) aClone.setAttribute("part", navLinkPart);
|
|
481
504
|
|
|
482
|
-
|
|
505
|
+
return liClone;
|
|
483
506
|
}
|
|
484
507
|
|
|
485
508
|
/**
|
|
@@ -489,96 +512,96 @@ function cloneNavItem(item) {
|
|
|
489
512
|
* @this {SiteNavigation}
|
|
490
513
|
*/
|
|
491
514
|
function populateTabs() {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
515
|
+
const visibleList = this[visibleElementsSymbol];
|
|
516
|
+
const hiddenList = this[hiddenElementsSymbol];
|
|
517
|
+
const hamburgerButton = this[hamburgerButtonSymbol];
|
|
518
|
+
const hamburgerNav = this[hamburgerNavSymbol];
|
|
519
|
+
const topLevelUl = [...getSlottedElements.call(this, "ul")].find(
|
|
520
|
+
(ul) => ul.parentElement === this,
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
if (!topLevelUl) {
|
|
524
|
+
visibleList.style.visibility = "visible";
|
|
525
|
+
hamburgerButton.style.display = "none";
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const sourceItems = Array.from(topLevelUl.children).filter(
|
|
530
|
+
(item) => item.tagName === "LI",
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
visibleList.style.visibility = "hidden";
|
|
534
|
+
hamburgerButton.style.display = "none";
|
|
535
|
+
hamburgerNav.style.display = "none";
|
|
536
|
+
visibleList.innerHTML = "";
|
|
537
|
+
hiddenList.innerHTML = "";
|
|
538
|
+
|
|
539
|
+
const originalDisplay = hamburgerButton.style.display;
|
|
540
|
+
hamburgerButton.style.visibility = "hidden";
|
|
541
|
+
hamburgerButton.style.display = "flex";
|
|
542
|
+
const hamburgerWidth = hamburgerButton.offsetWidth;
|
|
543
|
+
hamburgerButton.style.display = originalDisplay;
|
|
544
|
+
hamburgerButton.style.visibility = "visible";
|
|
545
|
+
|
|
546
|
+
const navWidth = this[navElementSymbol].clientWidth;
|
|
547
|
+
if (navWidth === 0) {
|
|
548
|
+
visibleList.style.visibility = "visible";
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const measurementList = visibleList.cloneNode(true);
|
|
553
|
+
Object.assign(measurementList.style, {
|
|
554
|
+
position: "absolute",
|
|
555
|
+
left: "0",
|
|
556
|
+
top: "0",
|
|
557
|
+
width: "auto",
|
|
558
|
+
visibility: "hidden",
|
|
559
|
+
});
|
|
560
|
+
this.shadowRoot.appendChild(measurementList);
|
|
561
|
+
|
|
562
|
+
const itemsToMove = [];
|
|
563
|
+
const availableWidthForTabs = navWidth - hamburgerWidth;
|
|
564
|
+
|
|
565
|
+
for (let i = 0; i < sourceItems.length; i++) {
|
|
566
|
+
const item = sourceItems[i];
|
|
567
|
+
const itemClone = item.cloneNode(true);
|
|
568
|
+
const submenu = itemClone.querySelector("ul, div[part='mega-menu']");
|
|
569
|
+
if (submenu) submenu.style.display = "none";
|
|
570
|
+
|
|
571
|
+
measurementList.appendChild(itemClone);
|
|
572
|
+
const isLastItem = i === sourceItems.length - 1;
|
|
573
|
+
const effectiveMaxWidth = isLastItem ? navWidth : availableWidthForTabs;
|
|
574
|
+
|
|
575
|
+
if (measurementList.scrollWidth > effectiveMaxWidth) {
|
|
576
|
+
break;
|
|
577
|
+
} else {
|
|
578
|
+
itemsToMove.push(item);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
this.shadowRoot.removeChild(measurementList);
|
|
582
|
+
|
|
583
|
+
const visibleItems = itemsToMove;
|
|
584
|
+
const hiddenItems = sourceItems.slice(visibleItems.length);
|
|
585
|
+
|
|
586
|
+
if (visibleItems.length > 0) {
|
|
587
|
+
const clonedVisibleItems = visibleItems.map(cloneNavItem);
|
|
588
|
+
visibleList.append(...clonedVisibleItems);
|
|
589
|
+
visibleList
|
|
590
|
+
.querySelectorAll(":scope > li")
|
|
591
|
+
.forEach((li) => setupSubmenu.call(this, li, "visible", 1));
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
if (hiddenItems.length > 0) {
|
|
595
|
+
const clonedHiddenItems = hiddenItems.map(cloneNavItem);
|
|
596
|
+
hiddenList.append(...clonedHiddenItems);
|
|
597
|
+
hamburgerButton.style.display = "flex";
|
|
598
|
+
hiddenList
|
|
599
|
+
.querySelectorAll(":scope > li")
|
|
600
|
+
.forEach((li) => setupSubmenu.call(this, li, "hidden", 1));
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
visibleList.style.visibility = "visible";
|
|
604
|
+
fireCustomEvent(this, "monster-layout-change", { visibleItems, hiddenItems });
|
|
582
605
|
}
|
|
583
606
|
|
|
584
607
|
/**
|
|
@@ -588,7 +611,7 @@ function populateTabs() {
|
|
|
588
611
|
* @returns {string} The combined string.
|
|
589
612
|
*/
|
|
590
613
|
function html(strings) {
|
|
591
|
-
|
|
614
|
+
return strings.join("");
|
|
592
615
|
}
|
|
593
616
|
|
|
594
617
|
/**
|
|
@@ -597,7 +620,7 @@ function html(strings) {
|
|
|
597
620
|
* @returns {string} The HTML template string.
|
|
598
621
|
*/
|
|
599
622
|
function getTemplate() {
|
|
600
|
-
|
|
623
|
+
return html`<div data-monster-role="control" part="control">
|
|
601
624
|
<nav data-monster-role="navigation" role="navigation" part="nav">
|
|
602
625
|
<ul id="visible-elements" part="visible-list"></ul>
|
|
603
626
|
</nav>
|