@schukai/monster 4.43.10 → 4.43.12

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