PageSourceSearch

https://muslimhands.ca/assets/js/header-menu.js

js muslimhands.ca collected 2026-09-24 13:22:41 UTC 3,419 bytes, 109 lines download raw bytes

1const setupMenu = () => {
2  /** @type {HTMLElement} */
3  const $header = document.querySelector('.Header');
4  if (!$header) {
5    console.warn('Cannot find `.Header` element');
6    return;
7  }
8
9  /** Open header menu */
10  const openMenu = ($link, $menu, options = {}) => {
11    $link.setAttribute('aria-expanded', 'true');
12    $menu.hidden = false;
13    // Fix Safari triggering `focus-visible` state on click events
14    if (options.isMouse) {
15      $menu.dataset.focusVisible = false;
16    }
17    $menu.focus({preventScroll: true, focusVisible: options.isKeyboard});
18    $header.dispatchEvent(new CustomEvent('menu-open'));
19  };
20
21  /** Close header menu */
22  const closeMenu = ($link, $menu) => {
23    $link.setAttribute('aria-expanded', 'false');
24    $menu.hidden = true;
25    delete $menu.dataset.focusVisible;
26    $header.dispatchEvent(new CustomEvent('menu-close'));
27  };
28
29  /** Toggle header menu (open or closed) */
30  const toggleMenu = ($link, options = {}) => {
31    const id = $link.getAttribute('aria-controls');
32    /** @type {HTMLElement} */
33    const $menu = document.querySelector(`#${id}`);
34    if (!$menu) return false;
35
36    const $otherLinks = Array.from(
37      document.querySelectorAll('.Header__nav a[aria-controls]')
38    ).filter(l => l !== $link && l.getAttribute('aria-expanded') === 'true');
39    
40    $otherLinks.forEach(l => {
41      const otherId = l.getAttribute('aria-controls');
42      const $otherMenu = document.querySelector(`#${otherId}`);
43      if ($otherMenu) closeMenu(l, $otherMenu);
44    });
45
46    globalThis.safeViewTransition(() => {
47      if ($link.getAttribute('aria-expanded') === 'true') {
48        closeMenu($link, $menu, options);
49      } else {
50        openMenu($link, $menu, options);
51      }
52    });
53  };
54
55  // Ensure attributes are set on Header-Menu elements
56  /** @type {Array<HTMLElement>} */
57  const $menus = Array.from($header.querySelectorAll('.Header-Menu'));
58  $menus.forEach(($menu) => {
59    $menu.tabIndex = -1;
60    $menu.hidden = true;
61  });
62
63  /** @type {Array<HTMLElement>} */
64  const $headerLinks = Array.from(
65    $header.querySelectorAll('.Header__nav a[aria-controls]')
66  );
67
68  for (const $link of $headerLinks) {
69    // Set default state
70    $link.setAttribute('aria-expanded', 'false');
71    // Handle keyboard events to open/close menu
72    $link.addEventListener('keydown', (ev) => {
73      if ([' ', 'Enter'].includes(ev.key)) {
74        ev.preventDefault();
75        toggleMenu($link, {isKeyboard: true});
76      }
77    });
78    // Handle pointer event to open/close menu
79    $link.addEventListener('click', (ev) => {
80      ev.preventDefault();
81      toggleMenu($link, {isMouse: true});
82    });
83  }
84
85  // Handle custom events
86  $header.addEventListener('search-open', () => {
87    for (const $link of $headerLinks) {
88      if ($link.getAttribute('aria-expanded') === 'true') {
89        toggleMenu($link);
90      }
91    }
92  });
93
94    document.addEventListener('click', ev => {
95        const isClickInside = $header.contains(ev.target);
96        if (!isClickInside) {
97            $headerLinks.forEach($link => {
98                if ($link.getAttribute('aria-expanded') === 'true') {
99                    const id = $link.getAttribute('aria-controls');
100                    const $menu = document.querySelector(`#${id}`);
101                    if ($menu) closeMenu($link, $menu);
102                }
103            });
104        }
105    });
106};
107
108// Activate on document ready
109setupMenu();

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.