PageSourceSearch

https://www.elle.cz/js/modules/headerSubmenu.js

js elle.cz collected 2026-10-02 07:55:38 UTC 4,031 bytes, 136 lines download raw bytes

1import {setAriaExpanded, setElementHidden} from '../core/aria.js';
2
3const closeDelayMs = 180;
4
5const setSubmenuOpenState = (submenu, isOpen) => {
6  submenu.classList.toggle('is-open', isOpen);
7
8  const trigger = submenu.querySelector('.header-submenu__trigger');
9  const panel = submenu.querySelector('.header-submenu__panel');
10  setAriaExpanded(trigger, isOpen);
11  setElementHidden(panel, !isOpen);
12};
13
14const closeSubmenus = (submenus, exceptSubmenu = null) => {
15  submenus.forEach((submenu) => {
16    if (submenu !== exceptSubmenu) {
17      setSubmenuOpenState(submenu, false);
18    }
19  });
20};
21
22const closeMegaMenuDropdowns = () => {
23  document.querySelectorAll('[data-mega-menu-dropdown].is-open').forEach((dropdown) => {
24    dropdown.classList.remove('is-open');
25    setAriaExpanded(dropdown.querySelector('.mega-menu-dropdown__trigger'), false);
26    setElementHidden(dropdown.querySelector('.mega-menu-dropdown__panel'), true);
27  });
28};
29
30export const initHeaderSubmenus = () => {
31  const submenus = Array.from(document.querySelectorAll('[data-header-submenu]'));
32  if (!submenus.length) {
33    return;
34  }
35
36  closeSubmenus(submenus);
37
38  submenus.forEach((submenu) => {
39    let closeTimeout = null;
40    let openedByHover = false;
41    const trigger = submenu.querySelector('.header-submenu__trigger');
42
43    const clearCloseTimeout = () => {
44      if (closeTimeout !== null) {
45        window.clearTimeout(closeTimeout);
46        closeTimeout = null;
47      }
48    };
49
50    const scheduleClose = () => {
51      clearCloseTimeout();
52      closeTimeout = window.setTimeout(() => {
53        if (submenu.contains(document.activeElement)) {
54          closeTimeout = null;
55          return;
56        }
57
58        setSubmenuOpenState(submenu, false);
59        closeTimeout = null;
60      }, closeDelayMs);
61    };
62
63    submenu.addEventListener('mouseenter', () => {
64      clearCloseTimeout();
65      openedByHover = !submenu.classList.contains('is-open');
66      closeSubmenus(submenus, submenu);
67      closeMegaMenuDropdowns();
68      setSubmenuOpenState(submenu, true);
69    });
70
71    submenu.addEventListener('mouseleave', () => {
72      openedByHover = false;
73      scheduleClose();
74    });
75
76    submenu.addEventListener('focusin', () => {
77      clearCloseTimeout();
78      if (document.activeElement === trigger) {
79        return;
80      }
81
82      openedByHover = false;
83      closeSubmenus(submenus, submenu);
84      closeMegaMenuDropdowns();
85      setSubmenuOpenState(submenu, true);
86    });
87
88    submenu.addEventListener('focusout', (event) => {
89      const nextFocusedElement = event.relatedTarget;
90      if (!(nextFocusedElement instanceof Node) || !submenu.contains(nextFocusedElement)) {
91        scheduleClose();
92      }
93    });
94
95    if (trigger instanceof HTMLElement) {
96      trigger.addEventListener('click', (event) => {
97        clearCloseTimeout();
98        const shouldOpen = event.detail > 0 && openedByHover
99          ? true
100          : !submenu.classList.contains('is-open');
101        openedByHover = false;
102        closeSubmenus(submenus, submenu);
103        if (shouldOpen) {
104          closeMegaMenuDropdowns();
105        }
106        setSubmenuOpenState(submenu, shouldOpen);
107      });
108    }
109  });
110
111  document.addEventListener('click', (event) => {
112    const target = event.target;
113    if (!(target instanceof Node)) {
114      return;
115    }
116
117    const clickedInsideSubmenu = submenus.some((submenu) => submenu.contains(target));
118    if (!clickedInsideSubmenu) {
119      closeSubmenus(submenus);
120    }
121  });
122
123  document.addEventListener('keydown', (event) => {
124    if (event.key === 'Escape') {
125      const activeElement = document.activeElement;
126      const activeSubmenu = activeElement instanceof Node
127        ? submenus.find((submenu) => submenu.classList.contains('is-open') && submenu.contains(activeElement))
128        : null;
129      const activeTrigger = activeSubmenu?.querySelector('.header-submenu__trigger');
130      closeSubmenus(submenus);
131      if (activeTrigger instanceof HTMLElement) {
132        activeTrigger.focus();
133      }
134    }
135  });
136};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.