PageSourceSearch

https://sharethedignity.org.au/wp-content/themes/skelly/js/dropdown.js

js sharethedignity.org.au collected 2026-10-02 03:05:43 UTC 5,292 bytes, 171 lines download raw bytes

1(function() {
2  'use strict';
3
4  const HOVER_DELAY = 150;
5  const CLOSE_DELAY = 100;
6
7  let openDropdown = null;
8  let hoverTimeout = null;
9  let closeTimeout = null;
10
11  function adjustDropdownPosition(navItem) {
12    const dropdownMenu = navItem.querySelector('.dropdown-menu');
13    if (!dropdownMenu) return;
14
15    // Reset position first
16    dropdownMenu.style.left = '-30px';
17    dropdownMenu.style.right = 'auto';
18
19    // Force reflow to get accurate measurements
20    dropdownMenu.offsetHeight;
21
22    // Check if it goes off the right edge (with 20px padding)
23    const rect = dropdownMenu.getBoundingClientRect();
24    const viewportWidth = document.documentElement.clientWidth;
25
26    if (rect.right > viewportWidth - 20) {
27      // Align to right edge instead
28      dropdownMenu.style.left = 'auto';
29      dropdownMenu.style.right = '0';
30    }
31  }
32
33  function openDropdownMenu(navItem) {
34    // Close any other open dropdown
35    if (openDropdown && openDropdown !== navItem) {
36      closeDropdownMenu(openDropdown);
37    }
38
39    navItem.classList.add('is-open');
40    const trigger = navItem.querySelector('a[aria-haspopup="true"]');
41    if (trigger) {
42      trigger.setAttribute('aria-expanded', 'true');
43    }
44    openDropdown = navItem;
45
46    // Adjust dropdown position
47    adjustDropdownPosition(navItem);
48  }
49
50  function closeDropdownMenu(navItem) {
51    navItem.classList.remove('is-open');
52    const trigger = navItem.querySelector('a[aria-haspopup="true"]');
53    if (trigger) {
54      trigger.setAttribute('aria-expanded', 'false');
55    }
56    if (openDropdown === navItem) {
57      openDropdown = null;
58    }
59  }
60
61  function closeAllDropdowns() {
62    document.querySelectorAll('.nav-item.has-dropdown.is-open').forEach(function(item) {
63      closeDropdownMenu(item);
64    });
65  }
66
67  function initDropdowns() {
68    const dropdownItems = document.querySelectorAll('.nav-item.has-dropdown');
69
70    dropdownItems.forEach(function(navItem) {
71      const trigger = navItem.querySelector('a[aria-haspopup="true"]');
72      const dropdownMenu = navItem.querySelector('.dropdown-menu');
73
74      if (!trigger || !dropdownMenu) return;
75
76      // Hover: mouse enter with delay
77      navItem.addEventListener('mouseenter', function() {
78        clearTimeout(closeTimeout);
79        hoverTimeout = setTimeout(function() {
80          openDropdownMenu(navItem);
81        }, HOVER_DELAY);
82      });
83
84      // Hover: mouse leave with grace period
85      navItem.addEventListener('mouseleave', function() {
86        clearTimeout(hoverTimeout);
87        closeTimeout = setTimeout(function() {
88          closeDropdownMenu(navItem);
89        }, CLOSE_DELAY);
90      });
91
92      // Keep dropdown open when hovering over the full-width dropdown bar
93      dropdownMenu.addEventListener('mouseenter', function() {
94        clearTimeout(closeTimeout);
95      });
96
97      dropdownMenu.addEventListener('mouseleave', function() {
98        closeTimeout = setTimeout(function() {
99          closeDropdownMenu(navItem);
100        }, CLOSE_DELAY);
101      });
102
103      // Keyboard: Enter to open
104      trigger.addEventListener('keydown', function(e) {
105        if (e.key === 'Enter') {
106          e.preventDefault();
107          if (navItem.classList.contains('is-open')) {
108            closeDropdownMenu(navItem);
109          } else {
110            openDropdownMenu(navItem);
111            // Focus first dropdown item
112            const firstItem = dropdownMenu.querySelector('.dropdown-item');
113            if (firstItem) {
114              firstItem.focus();
115            }
116          }
117        }
118      });
119
120      // Keyboard: Escape to close
121      navItem.addEventListener('keydown', function(e) {
122        if (e.key === 'Escape' && navItem.classList.contains('is-open')) {
123          e.preventDefault();
124          closeDropdownMenu(navItem);
125          trigger.focus();
126        }
127      });
128
129      // Keyboard: Arrow navigation within dropdown
130      dropdownMenu.addEventListener('keydown', function(e) {
131        const items = Array.from(dropdownMenu.querySelectorAll('.dropdown-item'));
132        const currentIndex = items.indexOf(document.activeElement);
133
134        if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
135          e.preventDefault();
136          const nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0;
137          items[nextIndex].focus();
138        } else if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {
139          e.preventDefault();
140          const prevIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1;
141          items[prevIndex].focus();
142        }
143      });
144    });
145
146    // Close dropdowns when clicking outside
147    document.addEventListener('click', function(e) {
148      if (!e.target.closest('.nav-item.has-dropdown') && !e.target.closest('.dropdown-menu')) {
149        closeAllDropdowns();
150      }
151    });
152
153    // Re-adjust dropdown position on window resize
154    let resizeTimeout;
155    window.addEventListener('resize', function() {
156      clearTimeout(resizeTimeout);
157      resizeTimeout = setTimeout(function() {
158        if (openDropdown) {
159          adjustDropdownPosition(openDropdown);
160        }
161      }, 100);
162    });
163  }
164
165  // Initialize when DOM is ready
166  if (document.readyState === 'loading') {
167    document.addEventListener('DOMContentLoaded', initDropdowns);
168  } else {
169    initDropdowns();
170  }
171})();

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.