@schukai/monster 4.43.14 → 4.43.16

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.
@@ -111,7 +111,6 @@ class SiteNavigation extends CustomElement {
111
111
  requestAnimationFrame(() => {
112
112
  populateTabs.call(this);
113
113
  });
114
-
115
114
  }
116
115
 
117
116
  disconnectedCallback() {
@@ -304,7 +303,7 @@ function detachResizeObserver() {
304
303
  }
305
304
 
306
305
  /**
307
- * Sets up interaction logic (hover or click) for a submenu.
306
+ * Sets up interaction logic (hover, click, or touch) for a submenu.
308
307
  * This function is called recursively for nested submenus.
309
308
  * @private
310
309
  * @this {SiteNavigation}
@@ -323,111 +322,174 @@ function setupSubmenu(parentLi, context = "visible", level = 1) {
323
322
  }
324
323
 
325
324
  const interaction = this.getOption("interactionModel", "auto");
326
- const useHover =
327
- interaction === "hover" ||
328
- (interaction === "auto" && context === "visible");
325
+ const isTouchDevice =
326
+ "ontouchstart" in window || navigator.maxTouchPoints > 0;
329
327
 
330
- if (useHover) {
331
- const component = this;
332
- let cleanup;
333
- let hideTimeout;
334
- let isHovering = false;
328
+ const effectiveInteraction =
329
+ interaction === "auto"
330
+ ? context === "visible"
331
+ ? "hover"
332
+ : "click"
333
+ : interaction;
334
+
335
+ const component = this;
336
+ let cleanup;
335
337
 
336
- const immediateHide = () => {
337
- submenu.style.display = "none";
338
- Object.assign(submenu.style, {
339
- maxHeight: "",
340
- overflowY: "",
338
+ const immediateHide = () => {
339
+ submenu.style.display = "none";
340
+ Object.assign(submenu.style, {
341
+ maxHeight: "",
342
+ overflowY: "",
343
+ });
344
+ submenu
345
+ .querySelectorAll(
346
+ "ul[style*='display: block'], div[part='mega-menu'][style*='display: block']",
347
+ )
348
+ .forEach((sub) => {
349
+ sub.style.display = "none";
341
350
  });
342
- submenu
343
- .querySelectorAll(
344
- "ul[style*='display: block'], div[part='mega-menu'][style*='display: block']",
345
- )
346
- .forEach((sub) => {
347
- sub.style.display = "none";
351
+ fireCustomEvent(this, "monster-submenu-hide", {
352
+ context,
353
+ trigger: parentLi,
354
+ submenu,
355
+ level,
356
+ });
357
+ if (cleanup) {
358
+ cleanup();
359
+ cleanup = null;
360
+ }
361
+ if (level === 1 && component[activeSubmenuHiderSymbol] === immediateHide) {
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
+ });
348
423
  });
349
- fireCustomEvent(this, "monster-submenu-hide", {
350
- context,
351
- trigger: parentLi,
352
- submenu,
353
- level,
354
424
  });
355
- if (cleanup) {
356
- cleanup();
357
- cleanup = null;
358
- }
425
+ }
426
+ };
427
+
428
+ if (effectiveInteraction === "hover" && isTouchDevice) {
429
+ let lastTap = 0;
430
+ const DOUBLE_TAP_DELAY = 300;
431
+ const anchor = parentLi.querySelector(":scope > a");
432
+
433
+ if (!anchor) return;
434
+
435
+ const handleOutsideClickForSubmenu = (event) => {
359
436
  if (
360
- level === 1 &&
361
- component[activeSubmenuHiderSymbol] === immediateHide
437
+ submenu.style.display === "block" &&
438
+ !parentLi.contains(event.target)
362
439
  ) {
363
- component[activeSubmenuHiderSymbol] = null;
440
+ immediateHide();
441
+ document.removeEventListener(
442
+ "click",
443
+ handleOutsideClickForSubmenu,
444
+ true,
445
+ );
364
446
  }
365
447
  };
366
448
 
367
- const show = () => {
368
- component[hideHamburgerMenuSymbol]?.();
369
- if (level === 1) {
370
- if (
371
- component[activeSubmenuHiderSymbol] &&
372
- component[activeSubmenuHiderSymbol] !== immediateHide
373
- ) {
374
- component[activeSubmenuHiderSymbol]();
375
- }
376
- component[activeSubmenuHiderSymbol] = immediateHide;
449
+ anchor.addEventListener("click", (event) => {
450
+ const now = Date.now();
451
+ const timeSinceLastTap = now - lastTap;
452
+ lastTap = now;
453
+
454
+ if (timeSinceLastTap < DOUBLE_TAP_DELAY && timeSinceLastTap > 0) {
455
+ lastTap = 0;
456
+ document.removeEventListener(
457
+ "click",
458
+ handleOutsideClickForSubmenu,
459
+ true,
460
+ );
461
+ immediateHide();
377
462
  } else {
378
- [...parentLi.parentElement.children]
379
- .filter((li) => li !== parentLi)
380
- .forEach((sibling) => {
381
- const siblingSubmenu = sibling.querySelector(
382
- ":scope > ul, :scope > div[part='mega-menu']",
383
- );
384
- if (siblingSubmenu) {
385
- siblingSubmenu.style.display = "none";
386
- }
387
- });
388
- }
389
- submenu.style.display = "block";
390
- fireCustomEvent(this, "monster-submenu-show", {
391
- context,
392
- trigger: parentLi,
393
- submenu,
394
- level,
395
- });
396
- if (!cleanup) {
397
- cleanup = autoUpdate(parentLi, submenu, () => {
398
- const middleware = [offset(8), flip(), shift({ padding: 8 })];
399
- const containsSubmenus = submenu.querySelector(
400
- "ul, div[part='mega-menu']",
463
+ event.preventDefault();
464
+ event.stopPropagation();
465
+
466
+ const isMenuOpen = submenu.style.display === "block";
467
+
468
+ if (isMenuOpen) {
469
+ document.removeEventListener(
470
+ "click",
471
+ handleOutsideClickForSubmenu,
472
+ true,
401
473
  );
402
- if (!containsSubmenus) {
403
- middleware.push(
404
- size({
405
- apply: ({ availableHeight, elements }) => {
406
- Object.assign(elements.floating.style, {
407
- maxHeight: `${availableHeight}px`,
408
- overflowY: "auto",
409
- });
410
- },
411
- padding: 8,
412
- }),
474
+ immediateHide();
475
+ } else {
476
+ show();
477
+ setTimeout(() => {
478
+ document.addEventListener(
479
+ "click",
480
+ handleOutsideClickForSubmenu,
481
+ true,
413
482
  );
414
- }
415
- computePosition(parentLi, submenu, {
416
- placement: level === 1 ? "bottom-start" : "right-start",
417
- middleware: middleware,
418
- }).then(({ x, y, strategy }) => {
419
- Object.assign(submenu.style, {
420
- position: strategy,
421
- left: `${x}px`,
422
- top: `${y}px`,
423
- });
424
- });
425
- });
483
+ }, 0);
484
+ }
426
485
  }
427
- };
486
+ });
487
+ } else if (effectiveInteraction === "hover" && !isTouchDevice) {
488
+ let hideTimeout;
489
+ let isHovering = false;
428
490
 
429
491
  const handleMouseEnter = () => {
430
- isHovering = true; // Status auf "aktiv" setzen
492
+ isHovering = true;
431
493
  clearTimeout(hideTimeout);
432
494
  if (submenu.style.display !== "block") {
433
495
  show();
@@ -435,7 +497,7 @@ function setupSubmenu(parentLi, context = "visible", level = 1) {
435
497
  };
436
498
 
437
499
  const handleMouseLeave = () => {
438
- isHovering = false; // Status auf "inaktiv" setzen
500
+ isHovering = false;
439
501
  hideTimeout = setTimeout(() => {
440
502
  if (!isHovering) {
441
503
  immediateHide();
@@ -524,7 +586,6 @@ function cloneNavItem(item) {
524
586
  * @this {SiteNavigation}
525
587
  */
526
588
  function populateTabs() {
527
-
528
589
  const visibleList = this[visibleElementsSymbol];
529
590
  const hiddenList = this[hiddenElementsSymbol];
530
591
  const hamburgerButton = this[hamburgerButtonSymbol];
@@ -551,7 +612,6 @@ function populateTabs() {
551
612
  return;
552
613
  }
553
614
 
554
-
555
615
  const navWidth = navEl.clientWidth;
556
616
 
557
617
  const originalDisplay = hamburgerButton.style.display;
@@ -607,7 +667,7 @@ function populateTabs() {
607
667
  visibleList.appendChild(firstHiddenItemClone);
608
668
  const firstHiddenItemWidth =
609
669
  firstHiddenItemClone.getBoundingClientRect().width;
610
- visibleList.removeChild(firstHiddenItemClone);
670
+ visibleList.removeChild(firstHiddenItemClone);
611
671
 
612
672
  const gap = parseFloat(getComputedStyle(visibleList).gap || "0") || 0;
613
673
  if (visibleItemsWidth + gap + firstHiddenItemWidth <= navWidth) {