@schukai/monster 4.43.6 → 4.43.7

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.
@@ -13,20 +13,20 @@
13
13
  */
14
14
 
15
15
  import {
16
- CustomElement,
17
- getSlottedElements,
18
- registerCustomElement,
19
- assembleMethodSymbol,
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
- computePosition,
25
- autoUpdate,
26
- flip,
27
- shift,
28
- offset,
29
- size,
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
- 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
- }
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
- 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
- );
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,106 @@ function initControlReferences() {
147
147
  * @this {SiteNavigation}
148
148
  */
149
149
  function initEventHandler() {
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 handleOutsideClick = (event) => {
160
- if (
161
- !hamburgerButton.contains(event.target) &&
162
- !hamburgerNav.contains(event.target)
163
- ) {
164
- hideMenu();
165
- }
166
- };
167
-
168
- const hideMenu = () => {
169
- hamburgerNav.style.display = "none";
170
- document.body.classList.remove("monster-navigation-open");
171
-
172
- fireCustomEvent(this, "monster-hamburger-hide", {
173
- button: hamburgerButton,
174
- menu: hamburgerNav,
175
- });
176
-
177
- if (this.getOption("features.resetOnClose") === true) {
178
- this[hiddenElementsSymbol]
179
- .querySelectorAll(".is-open")
180
- .forEach((submenu) => submenu.classList.remove("is-open"));
181
- }
182
-
183
- if (cleanup) {
184
- cleanup();
185
- cleanup = undefined;
186
- }
187
- document.removeEventListener("click", handleOutsideClick);
188
- };
189
-
190
- this[hideHamburgerMenuSymbol] = hideMenu;
191
-
192
- const showMenu = () => {
193
- this[activeSubmenuHiderSymbol]?.();
194
- hamburgerNav.style.display = "block";
195
- document.body.classList.add("monster-navigation-open");
196
-
197
- fireCustomEvent(this, "monster-hamburger-show", {
198
- button: hamburgerButton,
199
- menu: hamburgerNav,
200
- });
201
-
202
- cleanup = autoUpdate(hamburgerButton, hamburgerNav, () => {
203
- // Desktop view where the hamburger menu is a dropdown
204
- if (window.innerWidth > 768) {
205
- computePosition(hamburgerButton, hamburgerNav, {
206
- placement: "bottom-end",
207
- strategy: "fixed",
208
- middleware: [
209
- offset(8),
210
- flip(),
211
- shift({ padding: 8 }),
212
- size({
213
- apply: ({ availableHeight, elements }) => {
214
- Object.assign(elements.floating.style, {
215
- maxHeight: `${availableHeight}px`,
216
- overflowY: "auto",
217
- });
218
- },
219
- padding: 8,
220
- }),
221
- ],
222
- }).then(({ x, y, strategy }) => {
223
- Object.assign(hamburgerNav.style, {
224
- position: strategy,
225
- left: `${x}px`,
226
- top: `${y}px`,
227
- });
228
- });
229
- } else {
230
- // Mobile view (fullscreen overlay), position is handled by CSS
231
- Object.assign(hamburgerNav.style, { position: "", left: "", top: "" });
232
- }
233
- });
234
- setTimeout(() => document.addEventListener("click", handleOutsideClick), 0);
235
- };
236
-
237
- hamburgerButton.addEventListener("click", (event) => {
238
- event.stopPropagation();
239
- const isVisible = hamburgerNav.style.display === "block";
240
- if (isVisible) {
241
- hideMenu();
242
- } else {
243
- showMenu();
244
- }
245
- });
246
-
247
- hamburgerCloseButton.addEventListener("click", (event) => {
248
- event.stopPropagation();
249
- hideMenu();
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 handleOutsideClick = (event) => {
160
+ if (
161
+ !hamburgerButton.contains(event.target) &&
162
+ !hamburgerNav.contains(event.target)
163
+ ) {
164
+ hideMenu();
165
+ }
166
+ };
167
+
168
+ const hideMenu = () => {
169
+ hamburgerNav.style.display = "none";
170
+ document.body.classList.remove("monster-navigation-open");
171
+
172
+ fireCustomEvent(this, "monster-hamburger-hide", {
173
+ button: hamburgerButton,
174
+ menu: hamburgerNav,
175
+ });
176
+
177
+ if (this.getOption("features.resetOnClose") === true) {
178
+ this[hiddenElementsSymbol]
179
+ .querySelectorAll(".is-open")
180
+ .forEach((submenu) => submenu.classList.remove("is-open"));
181
+ }
182
+
183
+ if (cleanup) {
184
+ cleanup();
185
+ cleanup = undefined;
186
+ }
187
+ document.removeEventListener("click", handleOutsideClick);
188
+ };
189
+
190
+ this[hideHamburgerMenuSymbol] = hideMenu;
191
+
192
+ const showMenu = () => {
193
+ this[activeSubmenuHiderSymbol]?.();
194
+ hamburgerNav.style.display = "block";
195
+ document.body.classList.add("monster-navigation-open");
196
+
197
+ fireCustomEvent(this, "monster-hamburger-show", {
198
+ button: hamburgerButton,
199
+ menu: hamburgerNav,
200
+ });
201
+
202
+ cleanup = autoUpdate(hamburgerButton, hamburgerNav, () => {
203
+ // Desktop view where the hamburger menu is a dropdown
204
+ if (window.innerWidth > 768) {
205
+ computePosition(hamburgerButton, hamburgerNav, {
206
+ placement: "bottom-end",
207
+ middleware: [
208
+ offset(8),
209
+ flip(),
210
+ shift({ padding: 8 }),
211
+ size({
212
+ apply: ({ availableHeight, elements }) => {
213
+ Object.assign(elements.floating.style, {
214
+ maxHeight: `${availableHeight}px`,
215
+ overflowY: "auto",
216
+ });
217
+ },
218
+ padding: 8,
219
+ }),
220
+ ],
221
+ }).then(({ x, y, strategy }) => {
222
+ Object.assign(hamburgerNav.style, {
223
+ position: strategy,
224
+ left: `${x}px`,
225
+ top: `${y}px`,
226
+ });
227
+ });
228
+ } else {
229
+ // Mobile view (fullscreen overlay), position is handled by CSS
230
+ Object.assign(hamburgerNav.style, { position: "", left: "", top: "" });
231
+ }
232
+ });
233
+ setTimeout(() => document.addEventListener("click", handleOutsideClick), 0);
234
+ };
235
+
236
+ hamburgerButton.addEventListener("click", (event) => {
237
+ event.stopPropagation();
238
+ const isVisible = hamburgerNav.style.display === "block";
239
+ if (isVisible) {
240
+ hideMenu();
241
+ } else {
242
+ showMenu();
243
+ }
244
+ });
245
+
246
+ hamburgerCloseButton.addEventListener("click", (event) => {
247
+ event.stopPropagation();
248
+ hideMenu();
249
+ });
251
250
  }
252
251
 
253
252
  /**
@@ -257,20 +256,20 @@ function initEventHandler() {
257
256
  * @this {SiteNavigation}
258
257
  */
259
258
  function attachResizeObserver() {
260
- this[resizeObserverSymbol] = new ResizeObserver(() => {
261
- if (this[timerCallbackSymbol] instanceof DeadMansSwitch) {
262
- try {
263
- this[timerCallbackSymbol].touch();
264
- return;
265
- } catch (e) {
266
- delete this[timerCallbackSymbol];
267
- }
268
- }
269
- this[timerCallbackSymbol] = new DeadMansSwitch(200, () =>
270
- populateTabs.call(this),
271
- );
272
- });
273
- this[resizeObserverSymbol].observe(this[navElementSymbol]);
259
+ this[resizeObserverSymbol] = new ResizeObserver(() => {
260
+ if (this[timerCallbackSymbol] instanceof DeadMansSwitch) {
261
+ try {
262
+ this[timerCallbackSymbol].touch();
263
+ return;
264
+ } catch (e) {
265
+ delete this[timerCallbackSymbol];
266
+ }
267
+ }
268
+ this[timerCallbackSymbol] = new DeadMansSwitch(200, () =>
269
+ populateTabs.call(this),
270
+ );
271
+ });
272
+ this[resizeObserverSymbol].observe(this[navElementSymbol]);
274
273
  }
275
274
 
276
275
  /**
@@ -279,10 +278,10 @@ function attachResizeObserver() {
279
278
  * @this {SiteNavigation}
280
279
  */
281
280
  function detachResizeObserver() {
282
- if (this[resizeObserverSymbol] instanceof ResizeObserver) {
283
- this[resizeObserverSymbol].disconnect();
284
- delete this[resizeObserverSymbol];
285
- }
281
+ if (this[resizeObserverSymbol] instanceof ResizeObserver) {
282
+ this[resizeObserverSymbol].disconnect();
283
+ delete this[resizeObserverSymbol];
284
+ }
286
285
  }
287
286
 
288
287
  /**
@@ -295,167 +294,177 @@ function detachResizeObserver() {
295
294
  * @param {number} level The nesting level of the submenu (starts at 1).
296
295
  */
297
296
  function setupSubmenu(parentLi, context = "visible", level = 1) {
298
- const submenu = parentLi.querySelector(
299
- ":scope > ul, :scope > div[part='mega-menu']",
300
- );
301
- if (!submenu) return;
302
-
303
- if (submenu.tagName === "UL") {
304
- submenu.setAttribute("part", "submenu");
305
- }
306
-
307
- const interaction = this.getOption("interactionModel", "auto");
308
- const useHover =
309
- interaction === "hover" ||
310
- (interaction === "auto" && context === "visible");
311
-
312
- if (useHover) {
313
- const component = this;
314
- let cleanup;
315
- let hideTimeout;
316
-
317
- const immediateHide = () => {
318
- submenu.style.display = "none";
319
- submenu
320
- .querySelectorAll(
321
- "ul[style*='display: block'], div[part='mega-menu'][style*='display: block']",
322
- )
323
- .forEach((sub) => {
324
- sub.style.display = "none";
325
- });
326
-
327
- fireCustomEvent(this, "monster-submenu-hide", {
328
- context,
329
- trigger: parentLi,
330
- submenu,
331
- level,
332
- });
333
-
334
- if (cleanup) {
335
- cleanup();
336
- cleanup = null;
337
- }
338
-
339
- if (
340
- level === 1 &&
341
- component[activeSubmenuHiderSymbol] === immediateHide
342
- ) {
343
- component[activeSubmenuHiderSymbol] = null;
344
- }
345
- };
346
-
347
- const show = () => {
348
- component[hideHamburgerMenuSymbol]?.();
349
- clearTimeout(hideTimeout);
350
-
351
- if (level === 1) {
352
- if (
353
- component[activeSubmenuHiderSymbol] &&
354
- component[activeSubmenuHiderSymbol] !== immediateHide
355
- ) {
356
- component[activeSubmenuHiderSymbol]();
357
- }
358
- component[activeSubmenuHiderSymbol] = immediateHide;
359
- } else {
360
- [...parentLi.parentElement.children]
361
- .filter((li) => li !== parentLi)
362
- .forEach((sibling) => {
363
- const siblingSubmenu = sibling.querySelector(
364
- ":scope > ul, :scope > div[part='mega-menu']",
365
- );
366
- if (siblingSubmenu) {
367
- siblingSubmenu.style.display = "none";
368
- }
369
- });
370
- }
371
-
372
- submenu.style.display = "block";
373
- fireCustomEvent(this, "monster-submenu-show", {
374
- context,
375
- trigger: parentLi,
376
- submenu,
377
- level,
378
- });
379
-
380
- if (level === 1 && !cleanup) {
381
- cleanup = autoUpdate(parentLi, submenu, () => {
382
- computePosition(parentLi, submenu, {
383
- placement: "bottom-start",
384
- strategy: "fixed",
385
- middleware: [
386
- offset(8),
387
- flip({ fallbackPlacements: ["top-start"] }),
388
- shift({ padding: 8 }),
389
- size({
390
- apply: ({ availableHeight, elements }) => {
391
- Object.assign(elements.floating.style, {
392
- maxHeight: `${availableHeight}px`,
393
- overflowY: "auto",
394
- });
395
- },
396
- padding: 8,
397
- }),
398
- ],
399
- }).then(({ x, y, strategy }) => {
400
- Object.assign(submenu.style, {
401
- position: strategy,
402
- left: `${x}px`,
403
- top: `${y}px`,
404
- });
405
- });
406
- });
407
- }
408
- };
409
-
410
- const hideWithDelay = () => {
411
- hideTimeout = setTimeout(immediateHide, 200);
412
- };
413
-
414
- parentLi.addEventListener("mouseenter", show);
415
- parentLi.addEventListener("mouseleave", hideWithDelay);
416
- submenu.addEventListener("mouseenter", () => clearTimeout(hideTimeout));
417
- submenu.addEventListener("mouseleave", hideWithDelay);
418
- } else {
419
- // Click behavior
420
- const anchor = parentLi.querySelector(":scope > a");
421
- if (anchor) {
422
- anchor.addEventListener("click", (event) => {
423
- event.preventDefault();
424
- event.stopPropagation();
425
-
426
- if (!submenu.classList.contains("is-open")) {
427
- [...parentLi.parentElement.children]
428
- .filter((li) => li !== parentLi)
429
- .forEach((sibling) => {
430
- const siblingSubmenu = sibling.querySelector(
431
- ":scope > ul, :scope > div[part='mega-menu']",
432
- );
433
- if (siblingSubmenu) {
434
- siblingSubmenu.classList.remove("is-open");
435
- }
436
- });
437
- }
438
-
439
- const isOpen = submenu.classList.toggle("is-open");
440
- const eventName = isOpen
441
- ? "monster-submenu-show"
442
- : "monster-submenu-hide";
443
- fireCustomEvent(this, eventName, {
444
- context,
445
- trigger: parentLi,
446
- submenu,
447
- level,
448
- });
449
- });
450
- }
451
- }
452
-
453
- // Recursive call for nested lists
454
- if (submenu.tagName === "UL") {
455
- submenu
456
- .querySelectorAll(":scope > li")
457
- .forEach((li) => setupSubmenu.call(this, li, context, level + 1));
458
- }
297
+ const submenu = parentLi.querySelector(
298
+ ":scope > ul, :scope > div[part='mega-menu']",
299
+ );
300
+ if (!submenu) return;
301
+
302
+ if (submenu.tagName === "UL") {
303
+ submenu.setAttribute("part", "submenu");
304
+ }
305
+
306
+ const interaction = this.getOption("interactionModel", "auto");
307
+ const useHover =
308
+ interaction === "hover" ||
309
+ (interaction === "auto" && context === "visible");
310
+
311
+ if (useHover) {
312
+ const component = this;
313
+ let cleanup;
314
+ let hideTimeout;
315
+ let isHovering = false;
316
+
317
+ const immediateHide = () => {
318
+ submenu.style.display = "none";
319
+ Object.assign(submenu.style, {
320
+ maxHeight: "",
321
+ overflowY: "",
322
+ });
323
+ submenu
324
+ .querySelectorAll(
325
+ "ul[style*='display: block'], div[part='mega-menu'][style*='display: block']",
326
+ )
327
+ .forEach((sub) => {
328
+ sub.style.display = "none";
329
+ });
330
+ fireCustomEvent(this, "monster-submenu-hide", {
331
+ context,
332
+ trigger: parentLi,
333
+ submenu,
334
+ level,
335
+ });
336
+ if (cleanup) {
337
+ cleanup();
338
+ cleanup = null;
339
+ }
340
+ if (
341
+ level === 1 &&
342
+ component[activeSubmenuHiderSymbol] === immediateHide
343
+ ) {
344
+ component[activeSubmenuHiderSymbol] = null;
345
+ }
346
+ };
347
+
348
+ const show = () => {
349
+ component[hideHamburgerMenuSymbol]?.();
350
+ if (level === 1) {
351
+ if (
352
+ component[activeSubmenuHiderSymbol] &&
353
+ component[activeSubmenuHiderSymbol] !== immediateHide
354
+ ) {
355
+ component[activeSubmenuHiderSymbol]();
356
+ }
357
+ component[activeSubmenuHiderSymbol] = immediateHide;
358
+ } else {
359
+ [...parentLi.parentElement.children]
360
+ .filter((li) => li !== parentLi)
361
+ .forEach((sibling) => {
362
+ const siblingSubmenu = sibling.querySelector(
363
+ ":scope > ul, :scope > div[part='mega-menu']",
364
+ );
365
+ if (siblingSubmenu) {
366
+ siblingSubmenu.style.display = "none";
367
+ }
368
+ });
369
+ }
370
+ submenu.style.display = "block";
371
+ fireCustomEvent(this, "monster-submenu-show", {
372
+ context,
373
+ trigger: parentLi,
374
+ submenu,
375
+ level,
376
+ });
377
+ if (!cleanup) {
378
+ cleanup = autoUpdate(parentLi, submenu, () => {
379
+ const middleware = [offset(8), flip(), shift({ padding: 8 })];
380
+ const containsSubmenus = submenu.querySelector(
381
+ "ul, div[part='mega-menu']",
382
+ );
383
+ if (!containsSubmenus) {
384
+ middleware.push(
385
+ size({
386
+ apply: ({ availableHeight, elements }) => {
387
+ Object.assign(elements.floating.style, {
388
+ maxHeight: `${availableHeight}px`,
389
+ overflowY: "auto",
390
+ });
391
+ },
392
+ padding: 8,
393
+ }),
394
+ );
395
+ }
396
+ computePosition(parentLi, submenu, {
397
+ placement: level === 1 ? "bottom-start" : "right-start",
398
+ middleware: middleware,
399
+ }).then(({ x, y, strategy }) => {
400
+ Object.assign(submenu.style, {
401
+ position: strategy,
402
+ left: `${x}px`,
403
+ top: `${y}px`,
404
+ });
405
+ });
406
+ });
407
+ }
408
+ };
409
+
410
+ const handleMouseEnter = () => {
411
+ isHovering = true; // Status auf "aktiv" setzen
412
+ clearTimeout(hideTimeout);
413
+ if (submenu.style.display !== "block") {
414
+ show();
415
+ }
416
+ };
417
+
418
+ const handleMouseLeave = () => {
419
+ isHovering = false; // Status auf "inaktiv" setzen
420
+ hideTimeout = setTimeout(() => {
421
+ if (!isHovering) {
422
+ immediateHide();
423
+ }
424
+ }, 250);
425
+ };
426
+
427
+ parentLi.addEventListener("mouseenter", handleMouseEnter);
428
+ parentLi.addEventListener("mouseleave", handleMouseLeave);
429
+ submenu.addEventListener("mouseenter", handleMouseEnter);
430
+ submenu.addEventListener("mouseleave", handleMouseLeave);
431
+ } else {
432
+ const anchor = parentLi.querySelector(":scope > a");
433
+ if (anchor) {
434
+ anchor.addEventListener("click", (event) => {
435
+ event.preventDefault();
436
+ event.stopPropagation();
437
+ if (!submenu.classList.contains("is-open")) {
438
+ [...parentLi.parentElement.children]
439
+ .filter((li) => li !== parentLi)
440
+ .forEach((sibling) => {
441
+ const siblingSubmenu = sibling.querySelector(
442
+ ":scope > ul, :scope > div[part='mega-menu']",
443
+ );
444
+ if (siblingSubmenu) {
445
+ siblingSubmenu.classList.remove("is-open");
446
+ }
447
+ });
448
+ }
449
+ const isOpen = submenu.classList.toggle("is-open");
450
+ const eventName = isOpen
451
+ ? "monster-submenu-show"
452
+ : "monster-submenu-hide";
453
+ fireCustomEvent(this, eventName, {
454
+ context,
455
+ trigger: parentLi,
456
+ submenu,
457
+ level,
458
+ });
459
+ });
460
+ }
461
+ }
462
+
463
+ if (submenu.tagName === "UL") {
464
+ submenu
465
+ .querySelectorAll(":scope > li")
466
+ .forEach((li) => setupSubmenu.call(this, li, context, level + 1));
467
+ }
459
468
  }
460
469
 
461
470
  /**
@@ -466,20 +475,20 @@ function setupSubmenu(parentLi, context = "visible", level = 1) {
466
475
  * @returns {HTMLLIElement} The cloned and configured list item.
467
476
  */
468
477
  function cloneNavItem(item) {
469
- const liClone = item.cloneNode(true);
470
- const aClone = liClone.querySelector("a");
471
- let navItemPart = "nav-item";
472
- let navLinkPart = "nav-link";
478
+ const liClone = item.cloneNode(true);
479
+ const aClone = liClone.querySelector("a");
480
+ let navItemPart = "nav-item";
481
+ let navLinkPart = "nav-link";
473
482
 
474
- if (item.classList.contains("active")) {
475
- navItemPart += " nav-item-active";
476
- if (aClone) navLinkPart += " nav-link-active";
477
- }
483
+ if (item.classList.contains("active")) {
484
+ navItemPart += " nav-item-active";
485
+ if (aClone) navLinkPart += " nav-link-active";
486
+ }
478
487
 
479
- liClone.setAttribute("part", navItemPart);
480
- if (aClone) aClone.setAttribute("part", navLinkPart);
488
+ liClone.setAttribute("part", navItemPart);
489
+ if (aClone) aClone.setAttribute("part", navLinkPart);
481
490
 
482
- return liClone;
491
+ return liClone;
483
492
  }
484
493
 
485
494
  /**
@@ -489,96 +498,96 @@ function cloneNavItem(item) {
489
498
  * @this {SiteNavigation}
490
499
  */
491
500
  function populateTabs() {
492
- const visibleList = this[visibleElementsSymbol];
493
- const hiddenList = this[hiddenElementsSymbol];
494
- const hamburgerButton = this[hamburgerButtonSymbol];
495
- const hamburgerNav = this[hamburgerNavSymbol];
496
- const topLevelUl = [...getSlottedElements.call(this, "ul")].find(
497
- (ul) => ul.parentElement === this,
498
- );
499
-
500
- if (!topLevelUl) {
501
- visibleList.style.visibility = "visible";
502
- hamburgerButton.style.display = "none";
503
- return;
504
- }
505
-
506
- const sourceItems = Array.from(topLevelUl.children).filter(
507
- (item) => item.tagName === "LI",
508
- );
509
-
510
- visibleList.style.visibility = "hidden";
511
- hamburgerButton.style.display = "none";
512
- hamburgerNav.style.display = "none";
513
- visibleList.innerHTML = "";
514
- hiddenList.innerHTML = "";
515
-
516
- const originalDisplay = hamburgerButton.style.display;
517
- hamburgerButton.style.visibility = "hidden";
518
- hamburgerButton.style.display = "flex";
519
- const hamburgerWidth = hamburgerButton.offsetWidth;
520
- hamburgerButton.style.display = originalDisplay;
521
- hamburgerButton.style.visibility = "visible";
522
-
523
- const navWidth = this[navElementSymbol].clientWidth;
524
- if (navWidth === 0) {
525
- visibleList.style.visibility = "visible";
526
- return;
527
- }
528
-
529
- const measurementList = visibleList.cloneNode(true);
530
- Object.assign(measurementList.style, {
531
- position: "absolute",
532
- left: "0",
533
- top: "0",
534
- width: "auto",
535
- visibility: "hidden",
536
- });
537
- this.shadowRoot.appendChild(measurementList);
538
-
539
- const itemsToMove = [];
540
- const availableWidthForTabs = navWidth - hamburgerWidth;
541
-
542
- for (let i = 0; i < sourceItems.length; i++) {
543
- const item = sourceItems[i];
544
- const itemClone = item.cloneNode(true);
545
- const submenu = itemClone.querySelector("ul, div[part='mega-menu']");
546
- if (submenu) submenu.style.display = "none";
547
-
548
- measurementList.appendChild(itemClone);
549
- const isLastItem = i === sourceItems.length - 1;
550
- const effectiveMaxWidth = isLastItem ? navWidth : availableWidthForTabs;
551
-
552
- if (measurementList.scrollWidth > effectiveMaxWidth) {
553
- break;
554
- } else {
555
- itemsToMove.push(item);
556
- }
557
- }
558
- this.shadowRoot.removeChild(measurementList);
559
-
560
- const visibleItems = itemsToMove;
561
- const hiddenItems = sourceItems.slice(visibleItems.length);
562
-
563
- if (visibleItems.length > 0) {
564
- const clonedVisibleItems = visibleItems.map(cloneNavItem);
565
- visibleList.append(...clonedVisibleItems);
566
- visibleList
567
- .querySelectorAll(":scope > li")
568
- .forEach((li) => setupSubmenu.call(this, li, "visible", 1));
569
- }
570
-
571
- if (hiddenItems.length > 0) {
572
- const clonedHiddenItems = hiddenItems.map(cloneNavItem);
573
- hiddenList.append(...clonedHiddenItems);
574
- hamburgerButton.style.display = "flex";
575
- hiddenList
576
- .querySelectorAll(":scope > li")
577
- .forEach((li) => setupSubmenu.call(this, li, "hidden", 1));
578
- }
579
-
580
- visibleList.style.visibility = "visible";
581
- fireCustomEvent(this, "monster-layout-change", { visibleItems, hiddenItems });
501
+ const visibleList = this[visibleElementsSymbol];
502
+ const hiddenList = this[hiddenElementsSymbol];
503
+ const hamburgerButton = this[hamburgerButtonSymbol];
504
+ const hamburgerNav = this[hamburgerNavSymbol];
505
+ const topLevelUl = [...getSlottedElements.call(this, "ul")].find(
506
+ (ul) => ul.parentElement === this,
507
+ );
508
+
509
+ if (!topLevelUl) {
510
+ visibleList.style.visibility = "visible";
511
+ hamburgerButton.style.display = "none";
512
+ return;
513
+ }
514
+
515
+ const sourceItems = Array.from(topLevelUl.children).filter(
516
+ (item) => item.tagName === "LI",
517
+ );
518
+
519
+ visibleList.style.visibility = "hidden";
520
+ hamburgerButton.style.display = "none";
521
+ hamburgerNav.style.display = "none";
522
+ visibleList.innerHTML = "";
523
+ hiddenList.innerHTML = "";
524
+
525
+ const originalDisplay = hamburgerButton.style.display;
526
+ hamburgerButton.style.visibility = "hidden";
527
+ hamburgerButton.style.display = "flex";
528
+ const hamburgerWidth = hamburgerButton.offsetWidth;
529
+ hamburgerButton.style.display = originalDisplay;
530
+ hamburgerButton.style.visibility = "visible";
531
+
532
+ const navWidth = this[navElementSymbol].clientWidth;
533
+ if (navWidth === 0) {
534
+ visibleList.style.visibility = "visible";
535
+ return;
536
+ }
537
+
538
+ const measurementList = visibleList.cloneNode(true);
539
+ Object.assign(measurementList.style, {
540
+ position: "absolute",
541
+ left: "0",
542
+ top: "0",
543
+ width: "auto",
544
+ visibility: "hidden",
545
+ });
546
+ this.shadowRoot.appendChild(measurementList);
547
+
548
+ const itemsToMove = [];
549
+ const availableWidthForTabs = navWidth - hamburgerWidth;
550
+
551
+ for (let i = 0; i < sourceItems.length; i++) {
552
+ const item = sourceItems[i];
553
+ const itemClone = item.cloneNode(true);
554
+ const submenu = itemClone.querySelector("ul, div[part='mega-menu']");
555
+ if (submenu) submenu.style.display = "none";
556
+
557
+ measurementList.appendChild(itemClone);
558
+ const isLastItem = i === sourceItems.length - 1;
559
+ const effectiveMaxWidth = isLastItem ? navWidth : availableWidthForTabs;
560
+
561
+ if (measurementList.scrollWidth > effectiveMaxWidth) {
562
+ break;
563
+ } else {
564
+ itemsToMove.push(item);
565
+ }
566
+ }
567
+ this.shadowRoot.removeChild(measurementList);
568
+
569
+ const visibleItems = itemsToMove;
570
+ const hiddenItems = sourceItems.slice(visibleItems.length);
571
+
572
+ if (visibleItems.length > 0) {
573
+ const clonedVisibleItems = visibleItems.map(cloneNavItem);
574
+ visibleList.append(...clonedVisibleItems);
575
+ visibleList
576
+ .querySelectorAll(":scope > li")
577
+ .forEach((li) => setupSubmenu.call(this, li, "visible", 1));
578
+ }
579
+
580
+ if (hiddenItems.length > 0) {
581
+ const clonedHiddenItems = hiddenItems.map(cloneNavItem);
582
+ hiddenList.append(...clonedHiddenItems);
583
+ hamburgerButton.style.display = "flex";
584
+ hiddenList
585
+ .querySelectorAll(":scope > li")
586
+ .forEach((li) => setupSubmenu.call(this, li, "hidden", 1));
587
+ }
588
+
589
+ visibleList.style.visibility = "visible";
590
+ fireCustomEvent(this, "monster-layout-change", { visibleItems, hiddenItems });
582
591
  }
583
592
 
584
593
  /**
@@ -588,7 +597,7 @@ function populateTabs() {
588
597
  * @returns {string} The combined string.
589
598
  */
590
599
  function html(strings) {
591
- return strings.join("");
600
+ return strings.join("");
592
601
  }
593
602
 
594
603
  /**
@@ -597,7 +606,7 @@ function html(strings) {
597
606
  * @returns {string} The HTML template string.
598
607
  */
599
608
  function getTemplate() {
600
- return html`<div data-monster-role="control" part="control">
609
+ return html`<div data-monster-role="control" part="control">
601
610
  <nav data-monster-role="navigation" role="navigation" part="nav">
602
611
  <ul id="visible-elements" part="visible-list"></ul>
603
612
  </nav>