@hortonstudio/main 1.2.35 → 1.4.1
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.
- package/.claude/settings.local.json +22 -1
- package/TEMP-before-after-attributes.md +158 -0
- package/animations/hero.js +741 -611
- package/animations/text.js +532 -317
- package/animations/transition.js +36 -21
- package/autoInit/accessibility.js +173 -50
- package/autoInit/counter.js +338 -0
- package/autoInit/form.js +471 -0
- package/autoInit/modal.js +43 -38
- package/autoInit/navbar.js +494 -371
- package/autoInit/smooth-scroll.js +86 -84
- package/index.js +138 -88
- package/package.json +1 -1
- package/utils/before-after.js +279 -146
- package/utils/scroll-progress.js +26 -21
- package/utils/toc.js +73 -66
- package/CLAUDE.md +0 -45
- package/debug-version.html +0 -37
package/autoInit/navbar.js
CHANGED
@@ -3,26 +3,38 @@ let supportsInert = null;
|
|
3
3
|
let screenReaderLiveRegion = null;
|
4
4
|
|
5
5
|
export const init = () => {
|
6
|
+
// Ensure DOM is ready before initializing
|
7
|
+
if (document.readyState === "loading") {
|
8
|
+
document.addEventListener("DOMContentLoaded", initializeNavbar);
|
9
|
+
} else {
|
10
|
+
initializeNavbar();
|
11
|
+
}
|
12
|
+
return { result: "navbar initialized" };
|
13
|
+
};
|
14
|
+
|
15
|
+
function initializeNavbar() {
|
6
16
|
setupAccessibilityFeatures();
|
7
17
|
setupDynamicDropdowns();
|
8
18
|
setupMobileMenuButton();
|
9
19
|
setupMobileMenuARIA();
|
10
20
|
setupMobileMenuBreakpointHandler();
|
11
|
-
|
12
|
-
};
|
21
|
+
}
|
13
22
|
|
14
23
|
// Accessibility features setup
|
15
24
|
function setupAccessibilityFeatures() {
|
16
25
|
// Check inert support once
|
17
|
-
supportsInert =
|
18
|
-
|
19
|
-
// Create screen reader live region
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
+
supportsInert = "inert" in HTMLElement.prototype;
|
27
|
+
|
28
|
+
// Create screen reader live region only if body exists
|
29
|
+
if (document.body) {
|
30
|
+
screenReaderLiveRegion = document.createElement("div");
|
31
|
+
screenReaderLiveRegion.setAttribute("aria-live", "polite");
|
32
|
+
screenReaderLiveRegion.setAttribute("aria-atomic", "true");
|
33
|
+
screenReaderLiveRegion.className = "u-sr-only";
|
34
|
+
screenReaderLiveRegion.style.cssText =
|
35
|
+
"position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;";
|
36
|
+
document.body.appendChild(screenReaderLiveRegion);
|
37
|
+
}
|
26
38
|
}
|
27
39
|
|
28
40
|
// Inert polyfill for browsers that don't support it
|
@@ -31,29 +43,30 @@ function setElementInert(element, isInert) {
|
|
31
43
|
element.inert = isInert;
|
32
44
|
} else {
|
33
45
|
// Polyfill: manage tabindex for all focusable elements
|
34
|
-
const focusableSelectors =
|
46
|
+
const focusableSelectors =
|
47
|
+
'a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])';
|
35
48
|
const focusableElements = element.querySelectorAll(focusableSelectors);
|
36
|
-
|
49
|
+
|
37
50
|
if (isInert) {
|
38
51
|
// Store original tabindex values and disable
|
39
|
-
focusableElements.forEach(el => {
|
40
|
-
const currentTabindex = el.getAttribute(
|
41
|
-
el.setAttribute(
|
42
|
-
el.setAttribute(
|
52
|
+
focusableElements.forEach((el) => {
|
53
|
+
const currentTabindex = el.getAttribute("tabindex");
|
54
|
+
el.setAttribute("data-inert-tabindex", currentTabindex || "0");
|
55
|
+
el.setAttribute("tabindex", "-1");
|
43
56
|
});
|
44
|
-
element.setAttribute(
|
57
|
+
element.setAttribute("data-inert", "true");
|
45
58
|
} else {
|
46
59
|
// Restore original tabindex values
|
47
|
-
focusableElements.forEach(el => {
|
48
|
-
const originalTabindex = el.getAttribute(
|
49
|
-
if (originalTabindex ===
|
50
|
-
el.removeAttribute(
|
60
|
+
focusableElements.forEach((el) => {
|
61
|
+
const originalTabindex = el.getAttribute("data-inert-tabindex");
|
62
|
+
if (originalTabindex === "0") {
|
63
|
+
el.removeAttribute("tabindex");
|
51
64
|
} else if (originalTabindex) {
|
52
|
-
el.setAttribute(
|
65
|
+
el.setAttribute("tabindex", originalTabindex);
|
53
66
|
}
|
54
|
-
el.removeAttribute(
|
67
|
+
el.removeAttribute("data-inert-tabindex");
|
55
68
|
});
|
56
|
-
element.removeAttribute(
|
69
|
+
element.removeAttribute("data-inert");
|
57
70
|
}
|
58
71
|
}
|
59
72
|
}
|
@@ -62,97 +75,103 @@ function setElementInert(element, isInert) {
|
|
62
75
|
function announceToScreenReader(message) {
|
63
76
|
if (screenReaderLiveRegion) {
|
64
77
|
screenReaderLiveRegion.textContent = message;
|
65
|
-
|
78
|
+
|
66
79
|
// Clear after a delay to allow for repeat announcements
|
67
80
|
setTimeout(() => {
|
68
|
-
screenReaderLiveRegion.textContent =
|
81
|
+
screenReaderLiveRegion.textContent = "";
|
69
82
|
}, 1000);
|
70
83
|
}
|
71
84
|
}
|
72
85
|
|
73
86
|
// Extract menu name from element text or aria-label
|
74
87
|
function getMenuName(element) {
|
75
|
-
const text =
|
76
|
-
|
77
|
-
|
78
|
-
|
88
|
+
const text =
|
89
|
+
(element.textContent && element.textContent.trim()) ||
|
90
|
+
element.getAttribute("aria-label") ||
|
91
|
+
"menu";
|
92
|
+
return text
|
93
|
+
.replace(/^(Open|Close)\s+/i, "")
|
94
|
+
.replace(/\s+(menu|navigation)$/i, "");
|
79
95
|
}
|
80
96
|
|
81
97
|
// Desktop dropdown system
|
82
98
|
function setupDynamicDropdowns() {
|
83
|
-
const dropdownWrappers = document.querySelectorAll(
|
99
|
+
const dropdownWrappers = document.querySelectorAll(
|
100
|
+
'[data-hs-nav="dropdown"]',
|
101
|
+
);
|
84
102
|
const allDropdowns = [];
|
85
|
-
|
103
|
+
|
86
104
|
const closeAllDropdowns = (exceptWrapper = null) => {
|
87
|
-
allDropdowns.forEach(dropdown => {
|
105
|
+
allDropdowns.forEach((dropdown) => {
|
88
106
|
if (dropdown.wrapper !== exceptWrapper && dropdown.isOpen) {
|
89
107
|
dropdown.closeDropdown();
|
90
108
|
}
|
91
109
|
});
|
92
110
|
};
|
93
|
-
|
94
|
-
dropdownWrappers.forEach(wrapper => {
|
95
|
-
const toggle = wrapper.querySelector(
|
111
|
+
|
112
|
+
dropdownWrappers.forEach((wrapper) => {
|
113
|
+
const toggle = wrapper.querySelector("a");
|
96
114
|
if (!toggle) return;
|
97
|
-
|
98
|
-
const allElements = wrapper.querySelectorAll(
|
115
|
+
|
116
|
+
const allElements = wrapper.querySelectorAll("*");
|
99
117
|
let dropdownList = null;
|
100
|
-
|
118
|
+
|
101
119
|
for (const element of allElements) {
|
102
|
-
const links = element.querySelectorAll(
|
120
|
+
const links = element.querySelectorAll("a");
|
103
121
|
if (links.length >= 2 && !element.contains(toggle)) {
|
104
122
|
dropdownList = element;
|
105
123
|
break;
|
106
124
|
}
|
107
125
|
}
|
108
|
-
|
126
|
+
|
109
127
|
if (!dropdownList) return;
|
110
|
-
|
111
|
-
const toggleText =
|
128
|
+
|
129
|
+
const toggleText =
|
130
|
+
(toggle.textContent && toggle.textContent.trim()) || "dropdown";
|
112
131
|
const sanitizedText = sanitizeForID(toggleText);
|
113
132
|
const toggleId = `navbar-dropdown-${sanitizedText}-toggle`;
|
114
133
|
const listId = `navbar-dropdown-${sanitizedText}-list`;
|
115
|
-
|
134
|
+
|
116
135
|
toggle.id = toggleId;
|
117
|
-
toggle.setAttribute(
|
118
|
-
toggle.setAttribute(
|
119
|
-
toggle.setAttribute(
|
120
|
-
|
136
|
+
toggle.setAttribute("aria-haspopup", "menu");
|
137
|
+
toggle.setAttribute("aria-expanded", "false");
|
138
|
+
toggle.setAttribute("aria-controls", listId);
|
139
|
+
|
121
140
|
dropdownList.id = listId;
|
122
|
-
dropdownList.setAttribute(
|
123
|
-
dropdownList.setAttribute(
|
124
|
-
|
125
|
-
const menuItems = dropdownList.querySelectorAll(
|
126
|
-
menuItems.forEach(item => {
|
127
|
-
item.setAttribute(
|
128
|
-
item.setAttribute(
|
141
|
+
dropdownList.setAttribute("role", "menu");
|
142
|
+
dropdownList.setAttribute("aria-hidden", "true");
|
143
|
+
|
144
|
+
const menuItems = dropdownList.querySelectorAll("a");
|
145
|
+
menuItems.forEach((item) => {
|
146
|
+
item.setAttribute("role", "menuitem");
|
147
|
+
item.setAttribute("tabindex", "-1");
|
129
148
|
});
|
130
|
-
|
149
|
+
|
131
150
|
let isOpen = false;
|
132
151
|
let currentMenuItemIndex = -1;
|
133
|
-
|
152
|
+
|
134
153
|
function openDropdown() {
|
135
154
|
if (isOpen) return;
|
136
155
|
closeAllDropdowns(wrapper);
|
137
156
|
isOpen = true;
|
138
|
-
toggle.setAttribute(
|
139
|
-
dropdownList.setAttribute(
|
140
|
-
menuItems.forEach(item => {
|
141
|
-
item.setAttribute(
|
157
|
+
toggle.setAttribute("aria-expanded", "true");
|
158
|
+
dropdownList.setAttribute("aria-hidden", "false");
|
159
|
+
menuItems.forEach((item) => {
|
160
|
+
item.setAttribute("tabindex", "0");
|
142
161
|
});
|
143
|
-
|
162
|
+
|
144
163
|
// Announce to screen readers
|
145
164
|
const menuName = getMenuName(toggle);
|
146
165
|
announceToScreenReader(`${menuName} menu opened`);
|
147
|
-
|
148
|
-
const clickEvent = new MouseEvent(
|
166
|
+
|
167
|
+
const clickEvent = new MouseEvent("click", {
|
149
168
|
bubbles: true,
|
150
169
|
cancelable: true,
|
151
|
-
view: window
|
170
|
+
view: window,
|
152
171
|
});
|
153
172
|
wrapper.dispatchEvent(clickEvent);
|
154
173
|
}
|
155
|
-
|
174
|
+
|
156
175
|
function closeDropdown() {
|
157
176
|
if (!isOpen) return;
|
158
177
|
const shouldRestoreFocus = dropdownList.contains(document.activeElement);
|
@@ -161,76 +180,80 @@ function setupDynamicDropdowns() {
|
|
161
180
|
if (shouldRestoreFocus) {
|
162
181
|
toggle.focus();
|
163
182
|
}
|
164
|
-
toggle.setAttribute(
|
165
|
-
dropdownList.setAttribute(
|
166
|
-
menuItems.forEach(item => {
|
167
|
-
item.setAttribute(
|
183
|
+
toggle.setAttribute("aria-expanded", "false");
|
184
|
+
dropdownList.setAttribute("aria-hidden", "true");
|
185
|
+
menuItems.forEach((item) => {
|
186
|
+
item.setAttribute("tabindex", "-1");
|
168
187
|
});
|
169
|
-
|
188
|
+
|
170
189
|
// Announce to screen readers
|
171
190
|
const menuName = getMenuName(toggle);
|
172
191
|
announceToScreenReader(`${menuName} menu closed`);
|
173
|
-
|
174
|
-
const clickEvent = new MouseEvent(
|
192
|
+
|
193
|
+
const clickEvent = new MouseEvent("click", {
|
175
194
|
bubbles: true,
|
176
195
|
cancelable: true,
|
177
|
-
view: window
|
196
|
+
view: window,
|
178
197
|
});
|
179
198
|
wrapper.dispatchEvent(clickEvent);
|
180
199
|
}
|
181
|
-
|
182
|
-
wrapper.addEventListener(
|
200
|
+
|
201
|
+
wrapper.addEventListener("mouseenter", () => {
|
183
202
|
if (!isOpen) {
|
184
|
-
const clickEvent = new MouseEvent(
|
203
|
+
const clickEvent = new MouseEvent("click", {
|
185
204
|
bubbles: true,
|
186
205
|
cancelable: true,
|
187
|
-
view: window
|
206
|
+
view: window,
|
188
207
|
});
|
189
208
|
wrapper.dispatchEvent(clickEvent);
|
190
209
|
closeAllDropdowns(wrapper);
|
191
210
|
isOpen = true;
|
192
|
-
toggle.setAttribute(
|
193
|
-
dropdownList.setAttribute(
|
194
|
-
menuItems.forEach(item => {
|
195
|
-
item.setAttribute(
|
211
|
+
toggle.setAttribute("aria-expanded", "true");
|
212
|
+
dropdownList.setAttribute("aria-hidden", "false");
|
213
|
+
menuItems.forEach((item) => {
|
214
|
+
item.setAttribute("tabindex", "0");
|
196
215
|
});
|
197
216
|
}
|
198
217
|
});
|
199
|
-
|
200
|
-
wrapper.addEventListener(
|
218
|
+
|
219
|
+
wrapper.addEventListener("mouseleave", () => {
|
201
220
|
if (isOpen) {
|
202
221
|
if (dropdownList.contains(document.activeElement)) {
|
203
222
|
toggle.focus();
|
204
223
|
}
|
205
|
-
const clickEvent = new MouseEvent(
|
224
|
+
const clickEvent = new MouseEvent("click", {
|
206
225
|
bubbles: true,
|
207
226
|
cancelable: true,
|
208
|
-
view: window
|
227
|
+
view: window,
|
209
228
|
});
|
210
229
|
wrapper.dispatchEvent(clickEvent);
|
211
230
|
isOpen = false;
|
212
|
-
toggle.setAttribute(
|
213
|
-
dropdownList.setAttribute(
|
214
|
-
menuItems.forEach(item => {
|
215
|
-
item.setAttribute(
|
231
|
+
toggle.setAttribute("aria-expanded", "false");
|
232
|
+
dropdownList.setAttribute("aria-hidden", "true");
|
233
|
+
menuItems.forEach((item) => {
|
234
|
+
item.setAttribute("tabindex", "-1");
|
216
235
|
});
|
217
236
|
currentMenuItemIndex = -1;
|
218
237
|
}
|
219
238
|
});
|
220
|
-
|
221
|
-
document.addEventListener(
|
239
|
+
|
240
|
+
document.addEventListener("keydown", function (e) {
|
222
241
|
if (!isOpen) return;
|
223
242
|
if (!wrapper.contains(document.activeElement)) return;
|
224
|
-
|
225
|
-
if (e.key ===
|
243
|
+
|
244
|
+
if (e.key === "ArrowDown") {
|
226
245
|
e.preventDefault();
|
227
246
|
if (document.activeElement === toggle) {
|
228
247
|
currentMenuItemIndex = 0;
|
229
248
|
menuItems[currentMenuItemIndex].focus();
|
230
249
|
} else {
|
231
250
|
if (currentMenuItemIndex === menuItems.length - 1) {
|
232
|
-
const nextElement =
|
233
|
-
|
251
|
+
const nextElement =
|
252
|
+
(wrapper.nextElementSibling &&
|
253
|
+
wrapper.nextElementSibling.querySelector("a, button")) ||
|
254
|
+
document.querySelector(
|
255
|
+
".navbar_cartsearch_wrap a, .navbar_cartsearch_wrap button",
|
256
|
+
);
|
234
257
|
if (nextElement) {
|
235
258
|
closeDropdown();
|
236
259
|
nextElement.focus();
|
@@ -240,14 +263,16 @@ function setupDynamicDropdowns() {
|
|
240
263
|
currentMenuItemIndex = (currentMenuItemIndex + 1) % menuItems.length;
|
241
264
|
menuItems[currentMenuItemIndex].focus();
|
242
265
|
}
|
243
|
-
} else if (e.key ===
|
266
|
+
} else if (e.key === "ArrowUp") {
|
244
267
|
e.preventDefault();
|
245
268
|
if (document.activeElement === toggle) {
|
246
269
|
currentMenuItemIndex = menuItems.length - 1;
|
247
270
|
menuItems[currentMenuItemIndex].focus();
|
248
271
|
} else {
|
249
272
|
if (currentMenuItemIndex === 0) {
|
250
|
-
const prevElement =
|
273
|
+
const prevElement =
|
274
|
+
wrapper.previousElementSibling &&
|
275
|
+
wrapper.previousElementSibling.querySelector("a, button");
|
251
276
|
if (prevElement) {
|
252
277
|
closeDropdown();
|
253
278
|
prevElement.focus();
|
@@ -258,10 +283,13 @@ function setupDynamicDropdowns() {
|
|
258
283
|
return;
|
259
284
|
}
|
260
285
|
}
|
261
|
-
currentMenuItemIndex =
|
286
|
+
currentMenuItemIndex =
|
287
|
+
currentMenuItemIndex <= 0
|
288
|
+
? menuItems.length - 1
|
289
|
+
: currentMenuItemIndex - 1;
|
262
290
|
menuItems[currentMenuItemIndex].focus();
|
263
291
|
}
|
264
|
-
} else if (e.key ===
|
292
|
+
} else if (e.key === "Tab") {
|
265
293
|
if (e.shiftKey) {
|
266
294
|
if (document.activeElement === menuItems[0]) {
|
267
295
|
e.preventDefault();
|
@@ -271,8 +299,12 @@ function setupDynamicDropdowns() {
|
|
271
299
|
} else {
|
272
300
|
if (document.activeElement === menuItems[menuItems.length - 1]) {
|
273
301
|
e.preventDefault();
|
274
|
-
const nextElement =
|
275
|
-
|
302
|
+
const nextElement =
|
303
|
+
(wrapper.nextElementSibling &&
|
304
|
+
wrapper.nextElementSibling.querySelector("a, button")) ||
|
305
|
+
document.querySelector(
|
306
|
+
".navbar_cartsearch_wrap a, .navbar_cartsearch_wrap button",
|
307
|
+
);
|
276
308
|
closeDropdown();
|
277
309
|
if (nextElement) {
|
278
310
|
setTimeout(() => {
|
@@ -281,32 +313,32 @@ function setupDynamicDropdowns() {
|
|
281
313
|
}
|
282
314
|
}
|
283
315
|
}
|
284
|
-
} else if (e.key ===
|
316
|
+
} else if (e.key === "Escape") {
|
285
317
|
e.preventDefault();
|
286
318
|
closeDropdown();
|
287
319
|
toggle.focus();
|
288
|
-
} else if (e.key ===
|
320
|
+
} else if (e.key === "Home") {
|
289
321
|
e.preventDefault();
|
290
322
|
currentMenuItemIndex = 0;
|
291
323
|
menuItems[0].focus();
|
292
|
-
} else if (e.key ===
|
324
|
+
} else if (e.key === "End") {
|
293
325
|
e.preventDefault();
|
294
326
|
currentMenuItemIndex = menuItems.length - 1;
|
295
327
|
menuItems[menuItems.length - 1].focus();
|
296
|
-
} else if (e.key ===
|
328
|
+
} else if (e.key === " ") {
|
297
329
|
e.preventDefault();
|
298
330
|
}
|
299
331
|
});
|
300
|
-
|
301
|
-
toggle.addEventListener(
|
302
|
-
if (e.key ===
|
332
|
+
|
333
|
+
toggle.addEventListener("keydown", function (e) {
|
334
|
+
if (e.key === "ArrowDown") {
|
303
335
|
e.preventDefault();
|
304
336
|
openDropdown();
|
305
337
|
if (menuItems.length > 0) {
|
306
338
|
currentMenuItemIndex = 0;
|
307
339
|
setTimeout(() => menuItems[0].focus(), 100);
|
308
340
|
}
|
309
|
-
} else if (e.key ===
|
341
|
+
} else if (e.key === " ") {
|
310
342
|
e.preventDefault();
|
311
343
|
if (isOpen) {
|
312
344
|
closeDropdown();
|
@@ -317,7 +349,7 @@ function setupDynamicDropdowns() {
|
|
317
349
|
setTimeout(() => menuItems[0].focus(), 100);
|
318
350
|
}
|
319
351
|
}
|
320
|
-
} else if (e.key ===
|
352
|
+
} else if (e.key === "ArrowUp") {
|
321
353
|
e.preventDefault();
|
322
354
|
if (isOpen) {
|
323
355
|
currentMenuItemIndex = menuItems.length - 1;
|
@@ -325,29 +357,39 @@ function setupDynamicDropdowns() {
|
|
325
357
|
} else {
|
326
358
|
closeDropdown();
|
327
359
|
}
|
328
|
-
} else if (e.key ===
|
360
|
+
} else if (e.key === "Escape") {
|
329
361
|
e.preventDefault();
|
330
362
|
closeDropdown();
|
331
363
|
}
|
332
364
|
});
|
333
|
-
|
334
|
-
|
365
|
+
|
366
|
+
toggle.addEventListener("click", function(e) {
|
367
|
+
if (e.isTrusted) {
|
368
|
+
// This is a real user click - prevent it
|
369
|
+
e.preventDefault();
|
370
|
+
e.stopPropagation();
|
371
|
+
return false;
|
372
|
+
}
|
373
|
+
// Programmatic clicks (from hover/keyboard) proceed normally
|
374
|
+
});
|
375
|
+
|
376
|
+
document.addEventListener("click", function (e) {
|
335
377
|
if (!wrapper.contains(e.target) && isOpen) {
|
336
378
|
closeDropdown();
|
337
379
|
}
|
338
380
|
});
|
339
|
-
|
381
|
+
|
340
382
|
allDropdowns.push({
|
341
383
|
wrapper,
|
342
384
|
isOpen: () => isOpen,
|
343
385
|
closeDropdown,
|
344
386
|
toggle,
|
345
|
-
dropdownList
|
387
|
+
dropdownList,
|
346
388
|
});
|
347
389
|
});
|
348
|
-
|
349
|
-
document.addEventListener(
|
350
|
-
allDropdowns.forEach(dropdown => {
|
390
|
+
|
391
|
+
document.addEventListener("focusin", function (e) {
|
392
|
+
allDropdowns.forEach((dropdown) => {
|
351
393
|
if (dropdown.isOpen() && !dropdown.wrapper.contains(e.target)) {
|
352
394
|
dropdown.closeDropdown();
|
353
395
|
}
|
@@ -359,66 +401,73 @@ function setupDynamicDropdowns() {
|
|
359
401
|
|
360
402
|
// Desktop left/right arrow navigation
|
361
403
|
function addDesktopArrowNavigation() {
|
362
|
-
document.addEventListener(
|
363
|
-
if (e.key !==
|
364
|
-
|
404
|
+
document.addEventListener("keydown", function (e) {
|
405
|
+
if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return;
|
406
|
+
|
365
407
|
const mobileMenu = document.querySelector('[data-hs-nav="menu"]');
|
366
408
|
if (mobileMenu && mobileMenu.contains(document.activeElement)) return;
|
367
|
-
|
368
|
-
const navbar =
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
409
|
+
|
410
|
+
const navbar =
|
411
|
+
document.querySelector('[data-hs-nav="wrapper"]') ||
|
412
|
+
document.querySelector(".navbar_component") ||
|
413
|
+
document.querySelector('nav[role="navigation"]') ||
|
414
|
+
document.querySelector("nav");
|
415
|
+
|
373
416
|
if (!navbar || !navbar.contains(document.activeElement)) return;
|
374
|
-
|
375
|
-
const openDropdownList = navbar.querySelector(
|
376
|
-
|
377
|
-
|
417
|
+
|
418
|
+
const openDropdownList = navbar.querySelector(
|
419
|
+
'[aria-hidden="false"][role="menu"]',
|
420
|
+
);
|
421
|
+
if (openDropdownList && openDropdownList.contains(document.activeElement))
|
422
|
+
return;
|
423
|
+
|
378
424
|
e.preventDefault();
|
379
|
-
|
380
|
-
const allNavbarElements = navbar.querySelectorAll(
|
381
|
-
const focusableElements = Array.from(allNavbarElements).filter(el => {
|
382
|
-
if (el.getAttribute(
|
383
|
-
|
425
|
+
|
426
|
+
const allNavbarElements = navbar.querySelectorAll("a, button");
|
427
|
+
const focusableElements = Array.from(allNavbarElements).filter((el) => {
|
428
|
+
if (el.getAttribute("tabindex") === "-1") return false;
|
429
|
+
|
384
430
|
const isInDropdownList = el.closest('[role="menu"]');
|
385
431
|
if (isInDropdownList) return false;
|
386
|
-
|
432
|
+
|
387
433
|
const isInMobileMenu = el.closest('[data-hs-nav="menu"]');
|
388
434
|
if (isInMobileMenu) return false;
|
389
|
-
|
435
|
+
|
390
436
|
const computedStyle = window.getComputedStyle(el);
|
391
|
-
const isHidden =
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
437
|
+
const isHidden =
|
438
|
+
computedStyle.display === "none" ||
|
439
|
+
computedStyle.visibility === "hidden" ||
|
440
|
+
computedStyle.opacity === "0" ||
|
441
|
+
el.offsetWidth === 0 ||
|
442
|
+
el.offsetHeight === 0;
|
396
443
|
if (isHidden) return false;
|
397
|
-
|
444
|
+
|
398
445
|
let parent = el.parentElement;
|
399
446
|
while (parent && parent !== navbar) {
|
400
447
|
const parentStyle = window.getComputedStyle(parent);
|
401
|
-
const parentHidden =
|
402
|
-
|
403
|
-
|
404
|
-
|
448
|
+
const parentHidden =
|
449
|
+
parentStyle.display === "none" ||
|
450
|
+
parentStyle.visibility === "hidden" ||
|
451
|
+
parent.offsetWidth === 0 ||
|
452
|
+
parent.offsetHeight === 0;
|
405
453
|
if (parentHidden) return false;
|
406
454
|
parent = parent.parentElement;
|
407
455
|
}
|
408
|
-
|
456
|
+
|
409
457
|
return true;
|
410
458
|
});
|
411
|
-
|
459
|
+
|
412
460
|
const currentIndex = focusableElements.indexOf(document.activeElement);
|
413
461
|
if (currentIndex === -1) return;
|
414
|
-
|
462
|
+
|
415
463
|
let nextIndex;
|
416
|
-
if (e.key ===
|
464
|
+
if (e.key === "ArrowRight") {
|
417
465
|
nextIndex = (currentIndex + 1) % focusableElements.length;
|
418
466
|
} else {
|
419
|
-
nextIndex =
|
467
|
+
nextIndex =
|
468
|
+
currentIndex === 0 ? focusableElements.length - 1 : currentIndex - 1;
|
420
469
|
}
|
421
|
-
|
470
|
+
|
422
471
|
focusableElements[nextIndex].focus();
|
423
472
|
});
|
424
473
|
}
|
@@ -427,241 +476,287 @@ function addDesktopArrowNavigation() {
|
|
427
476
|
function setupMobileMenuButton() {
|
428
477
|
const menuButton = document.querySelector('[data-hs-nav="menubtn"]');
|
429
478
|
const mobileMenu = document.querySelector('[data-hs-nav="menu"]');
|
430
|
-
|
479
|
+
|
431
480
|
if (!menuButton || !mobileMenu) return;
|
432
|
-
|
481
|
+
|
433
482
|
const menuId = `mobile-menu-${Date.now()}`;
|
434
|
-
|
435
|
-
menuButton.setAttribute(
|
436
|
-
menuButton.setAttribute(
|
437
|
-
menuButton.setAttribute(
|
438
|
-
|
483
|
+
|
484
|
+
menuButton.setAttribute("aria-expanded", "false");
|
485
|
+
menuButton.setAttribute("aria-controls", menuId);
|
486
|
+
menuButton.setAttribute("aria-label", "Open navigation menu");
|
487
|
+
|
439
488
|
mobileMenu.id = menuId;
|
440
|
-
mobileMenu.setAttribute(
|
441
|
-
mobileMenu.setAttribute(
|
489
|
+
mobileMenu.setAttribute("role", "dialog");
|
490
|
+
mobileMenu.setAttribute("aria-modal", "true");
|
442
491
|
setElementInert(mobileMenu, true);
|
443
|
-
|
492
|
+
|
444
493
|
let isMenuOpen = false;
|
445
|
-
|
494
|
+
|
446
495
|
function shouldPreventMobileMenu() {
|
447
|
-
const menuHideElement = document.querySelector(
|
496
|
+
const menuHideElement = document.querySelector(".menu-hide.is-mobile");
|
448
497
|
if (!menuHideElement) return false;
|
449
|
-
|
498
|
+
|
450
499
|
const computedStyle = window.getComputedStyle(menuHideElement);
|
451
|
-
return computedStyle.display ===
|
500
|
+
return computedStyle.display === "none";
|
452
501
|
}
|
453
|
-
|
502
|
+
|
454
503
|
function openMenu() {
|
455
504
|
if (isMenuOpen || shouldPreventMobileMenu()) return;
|
456
505
|
isMenuOpen = true;
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
506
|
+
|
507
|
+
try {
|
508
|
+
// Add body overflow hidden class
|
509
|
+
document.body.classList.add("u-overflow-hidden");
|
510
|
+
|
511
|
+
// Add blur effect to modal blur elements
|
512
|
+
document
|
513
|
+
.querySelectorAll('[data-hs-nav="modal-blur"]')
|
514
|
+
.forEach((element) => {
|
515
|
+
element.style.display = "block";
|
516
|
+
element.style.opacity = "0.5";
|
517
|
+
element.style.transition = "opacity 0.3s ease";
|
518
|
+
});
|
519
|
+
|
520
|
+
menuButton.setAttribute("aria-expanded", "true");
|
521
|
+
menuButton.setAttribute("aria-label", "Close navigation menu");
|
522
|
+
setElementInert(mobileMenu, false);
|
523
|
+
|
524
|
+
// Announce to screen readers
|
525
|
+
const menuName = getMenuName(menuButton);
|
526
|
+
announceToScreenReader(`${menuName} opened`);
|
527
|
+
|
528
|
+
// Prevent tabbing outside navbar using tabindex management
|
529
|
+
const navbarWrapper =
|
530
|
+
document.querySelector('[data-hs-nav="wrapper"]') ||
|
531
|
+
document.querySelector(".navbar_component") ||
|
532
|
+
document.querySelector('nav[role="navigation"]') ||
|
533
|
+
document.querySelector("nav");
|
534
|
+
|
535
|
+
const allFocusableElements = document.querySelectorAll(
|
536
|
+
'a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])',
|
537
|
+
);
|
538
|
+
allFocusableElements.forEach((el) => {
|
539
|
+
if (navbarWrapper && !navbarWrapper.contains(el)) {
|
540
|
+
el.setAttribute(
|
541
|
+
"data-mobile-menu-tabindex",
|
542
|
+
el.getAttribute("tabindex") || "0",
|
543
|
+
);
|
544
|
+
el.setAttribute("tabindex", "-1");
|
545
|
+
}
|
546
|
+
});
|
547
|
+
|
548
|
+
const clickEvent = new MouseEvent("click", {
|
549
|
+
bubbles: true,
|
550
|
+
cancelable: true,
|
551
|
+
view: window,
|
552
|
+
});
|
553
|
+
menuButton.dispatchEvent(clickEvent);
|
554
|
+
} catch (e) {
|
555
|
+
// DOM operation failed, restore state
|
556
|
+
isMenuOpen = false;
|
557
|
+
menuButton.setAttribute("aria-expanded", "false");
|
558
|
+
menuButton.setAttribute("aria-label", "Open navigation menu");
|
559
|
+
}
|
496
560
|
}
|
497
|
-
|
561
|
+
|
498
562
|
function closeMenu() {
|
499
563
|
if (!isMenuOpen) return;
|
500
564
|
isMenuOpen = false;
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
setElementInert(mobileMenu, true);
|
520
|
-
|
521
|
-
// Announce to screen readers
|
522
|
-
const menuName = getMenuName(menuButton);
|
523
|
-
announceToScreenReader(`${menuName} closed`);
|
524
|
-
|
525
|
-
// Restore tabbing to entire page using tabindex management
|
526
|
-
const elementsToRestore = document.querySelectorAll('[data-mobile-menu-tabindex]');
|
527
|
-
elementsToRestore.forEach(el => {
|
528
|
-
const originalTabindex = el.getAttribute('data-mobile-menu-tabindex');
|
529
|
-
if (originalTabindex === '0') {
|
530
|
-
el.removeAttribute('tabindex');
|
531
|
-
} else {
|
532
|
-
el.setAttribute('tabindex', originalTabindex);
|
565
|
+
|
566
|
+
try {
|
567
|
+
// Remove body overflow hidden class
|
568
|
+
document.body.classList.remove("u-overflow-hidden");
|
569
|
+
|
570
|
+
// Remove blur effect from modal blur elements
|
571
|
+
document
|
572
|
+
.querySelectorAll('[data-hs-nav="modal-blur"]')
|
573
|
+
.forEach((element) => {
|
574
|
+
element.style.opacity = "0";
|
575
|
+
element.style.transition = "opacity 0.3s ease";
|
576
|
+
setTimeout(() => {
|
577
|
+
element.style.display = "none";
|
578
|
+
}, 300);
|
579
|
+
});
|
580
|
+
|
581
|
+
if (mobileMenu.contains(document.activeElement)) {
|
582
|
+
menuButton.focus();
|
533
583
|
}
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
584
|
+
menuButton.setAttribute("aria-expanded", "false");
|
585
|
+
menuButton.setAttribute("aria-label", "Open navigation menu");
|
586
|
+
setElementInert(mobileMenu, true);
|
587
|
+
|
588
|
+
// Announce to screen readers
|
589
|
+
const menuName = getMenuName(menuButton);
|
590
|
+
announceToScreenReader(`${menuName} closed`);
|
591
|
+
|
592
|
+
// Restore tabbing to entire page using tabindex management
|
593
|
+
const elementsToRestore = document.querySelectorAll(
|
594
|
+
"[data-mobile-menu-tabindex]",
|
595
|
+
);
|
596
|
+
elementsToRestore.forEach((el) => {
|
597
|
+
const originalTabindex = el.getAttribute("data-mobile-menu-tabindex");
|
598
|
+
if (originalTabindex === "0") {
|
599
|
+
el.removeAttribute("tabindex");
|
600
|
+
} else {
|
601
|
+
el.setAttribute("tabindex", originalTabindex);
|
602
|
+
}
|
603
|
+
el.removeAttribute("data-mobile-menu-tabindex");
|
604
|
+
});
|
605
|
+
|
606
|
+
const clickEvent = new MouseEvent("click", {
|
607
|
+
bubbles: true,
|
608
|
+
cancelable: true,
|
609
|
+
view: window,
|
610
|
+
});
|
611
|
+
menuButton.dispatchEvent(clickEvent);
|
612
|
+
} catch (e) {
|
613
|
+
// DOM operation failed, ensure consistent state
|
614
|
+
isMenuOpen = false;
|
615
|
+
menuButton.setAttribute("aria-expanded", "false");
|
616
|
+
menuButton.setAttribute("aria-label", "Open navigation menu");
|
617
|
+
}
|
543
618
|
}
|
544
|
-
|
619
|
+
|
545
620
|
function toggleMenu() {
|
546
621
|
if (shouldPreventMobileMenu()) return;
|
547
|
-
|
622
|
+
|
548
623
|
if (isMenuOpen) {
|
549
624
|
closeMenu();
|
550
625
|
} else {
|
551
626
|
openMenu();
|
552
627
|
}
|
553
628
|
}
|
554
|
-
|
555
|
-
menuButton.addEventListener(
|
556
|
-
if (e.key ===
|
629
|
+
|
630
|
+
menuButton.addEventListener("keydown", function (e) {
|
631
|
+
if (e.key === "Enter" || e.key === " ") {
|
557
632
|
e.preventDefault();
|
558
633
|
toggleMenu();
|
559
|
-
} else if (e.key ===
|
634
|
+
} else if (e.key === "ArrowDown") {
|
560
635
|
e.preventDefault();
|
561
636
|
if (!isMenuOpen) {
|
562
637
|
openMenu();
|
563
638
|
}
|
564
|
-
const firstElement = mobileMenu.querySelector(
|
639
|
+
const firstElement = mobileMenu.querySelector("button, a");
|
565
640
|
if (firstElement) {
|
566
641
|
firstElement.focus();
|
567
642
|
}
|
568
|
-
} else if (e.key ===
|
643
|
+
} else if (e.key === "ArrowUp") {
|
569
644
|
e.preventDefault();
|
570
645
|
if (isMenuOpen) {
|
571
646
|
closeMenu();
|
572
647
|
}
|
573
648
|
}
|
574
649
|
});
|
575
|
-
|
576
|
-
menuButton.addEventListener(
|
650
|
+
|
651
|
+
menuButton.addEventListener("click", function (e) {
|
577
652
|
if (!e.isTrusted) return;
|
578
|
-
|
653
|
+
|
579
654
|
if (shouldPreventMobileMenu()) return;
|
580
|
-
|
655
|
+
|
581
656
|
if (isMenuOpen && mobileMenu.contains(document.activeElement)) {
|
582
657
|
menuButton.focus();
|
583
658
|
}
|
584
|
-
|
659
|
+
|
585
660
|
const newMenuState = !isMenuOpen;
|
586
661
|
isMenuOpen = newMenuState;
|
587
|
-
|
662
|
+
|
588
663
|
// Handle body overflow class
|
589
664
|
if (isMenuOpen) {
|
590
|
-
document.body.classList.add(
|
665
|
+
document.body.classList.add("u-overflow-hidden");
|
591
666
|
} else {
|
592
|
-
document.body.classList.remove(
|
667
|
+
document.body.classList.remove("u-overflow-hidden");
|
593
668
|
}
|
594
|
-
|
669
|
+
|
595
670
|
// Handle blur effect
|
596
|
-
document
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
element.style.
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
671
|
+
document
|
672
|
+
.querySelectorAll('[data-hs-nav="modal-blur"]')
|
673
|
+
.forEach((element) => {
|
674
|
+
if (isMenuOpen) {
|
675
|
+
element.style.display = "block";
|
676
|
+
element.style.opacity = "0.5";
|
677
|
+
element.style.transition = "opacity 0.3s ease";
|
678
|
+
} else {
|
679
|
+
element.style.opacity = "0";
|
680
|
+
element.style.transition = "opacity 0.3s ease";
|
681
|
+
setTimeout(() => {
|
682
|
+
element.style.display = "none";
|
683
|
+
}, 300);
|
684
|
+
}
|
685
|
+
});
|
686
|
+
|
687
|
+
menuButton.setAttribute("aria-expanded", isMenuOpen);
|
688
|
+
menuButton.setAttribute(
|
689
|
+
"aria-label",
|
690
|
+
isMenuOpen ? "Close navigation menu" : "Open navigation menu",
|
613
691
|
);
|
614
692
|
setElementInert(mobileMenu, !isMenuOpen);
|
615
|
-
|
693
|
+
|
616
694
|
// Handle tabindex management for external clicks
|
617
695
|
if (isMenuOpen) {
|
618
|
-
const navbarWrapper =
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
allFocusableElements.
|
696
|
+
const navbarWrapper =
|
697
|
+
document.querySelector('[data-hs-nav="wrapper"]') ||
|
698
|
+
document.querySelector(".navbar_component") ||
|
699
|
+
document.querySelector('nav[role="navigation"]') ||
|
700
|
+
document.querySelector("nav");
|
701
|
+
|
702
|
+
const allFocusableElements = document.querySelectorAll(
|
703
|
+
'a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])',
|
704
|
+
);
|
705
|
+
allFocusableElements.forEach((el) => {
|
625
706
|
if (navbarWrapper && !navbarWrapper.contains(el)) {
|
626
|
-
el.setAttribute(
|
627
|
-
|
707
|
+
el.setAttribute(
|
708
|
+
"data-mobile-menu-tabindex",
|
709
|
+
el.getAttribute("tabindex") || "0",
|
710
|
+
);
|
711
|
+
el.setAttribute("tabindex", "-1");
|
628
712
|
}
|
629
713
|
});
|
630
714
|
} else {
|
631
|
-
const elementsToRestore = document.querySelectorAll(
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
715
|
+
const elementsToRestore = document.querySelectorAll(
|
716
|
+
"[data-mobile-menu-tabindex]",
|
717
|
+
);
|
718
|
+
elementsToRestore.forEach((el) => {
|
719
|
+
const originalTabindex = el.getAttribute("data-mobile-menu-tabindex");
|
720
|
+
if (originalTabindex === "0") {
|
721
|
+
el.removeAttribute("tabindex");
|
636
722
|
} else {
|
637
|
-
el.setAttribute(
|
723
|
+
el.setAttribute("tabindex", originalTabindex);
|
638
724
|
}
|
639
|
-
el.removeAttribute(
|
725
|
+
el.removeAttribute("data-mobile-menu-tabindex");
|
640
726
|
});
|
641
727
|
}
|
642
728
|
});
|
643
|
-
|
729
|
+
|
644
730
|
// Store the menu state and functions for breakpoint handler
|
645
731
|
window.mobileMenuState = {
|
646
732
|
isMenuOpen: () => isMenuOpen,
|
647
733
|
closeMenu: closeMenu,
|
648
|
-
openMenu: openMenu
|
734
|
+
openMenu: openMenu,
|
649
735
|
};
|
736
|
+
|
737
|
+
// Cleanup function for window.mobileMenuState
|
738
|
+
if (typeof window !== "undefined") {
|
739
|
+
window.addEventListener("pagehide", () => {
|
740
|
+
if (window.mobileMenuState) {
|
741
|
+
delete window.mobileMenuState;
|
742
|
+
}
|
743
|
+
});
|
744
|
+
}
|
650
745
|
}
|
651
746
|
|
652
747
|
// Mobile menu breakpoint handler
|
653
748
|
function setupMobileMenuBreakpointHandler() {
|
654
749
|
let preventedMenuState = false;
|
655
|
-
|
750
|
+
|
656
751
|
function handleBreakpointChange() {
|
657
|
-
const menuHideElement = document.querySelector(
|
752
|
+
const menuHideElement = document.querySelector(".menu-hide.is-mobile");
|
658
753
|
if (!menuHideElement) return;
|
659
|
-
|
754
|
+
|
660
755
|
const computedStyle = window.getComputedStyle(menuHideElement);
|
661
|
-
const shouldPrevent = computedStyle.display ===
|
662
|
-
|
756
|
+
const shouldPrevent = computedStyle.display === "none";
|
757
|
+
|
663
758
|
if (!window.mobileMenuState) return;
|
664
|
-
|
759
|
+
|
665
760
|
if (shouldPrevent && window.mobileMenuState.isMenuOpen()) {
|
666
761
|
// Store that the menu was open before being prevented
|
667
762
|
preventedMenuState = true;
|
@@ -672,19 +767,24 @@ function setupMobileMenuBreakpointHandler() {
|
|
672
767
|
window.mobileMenuState.openMenu();
|
673
768
|
}
|
674
769
|
}
|
675
|
-
|
770
|
+
|
676
771
|
// Use ResizeObserver for more accurate detection
|
677
|
-
if (
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
772
|
+
if (typeof ResizeObserver !== "undefined") {
|
773
|
+
try {
|
774
|
+
const resizeObserver = new ResizeObserver(handleBreakpointChange);
|
775
|
+
const menuHideElement = document.querySelector(".menu-hide.is-mobile");
|
776
|
+
if (menuHideElement) {
|
777
|
+
resizeObserver.observe(menuHideElement);
|
778
|
+
}
|
779
|
+
} catch (e) {
|
780
|
+
// ResizeObserver not supported or error occurred
|
781
|
+
// Silently fall back to resize event
|
682
782
|
}
|
683
783
|
}
|
684
|
-
|
784
|
+
|
685
785
|
// Fallback to resize event
|
686
|
-
window.addEventListener(
|
687
|
-
|
786
|
+
window.addEventListener("resize", handleBreakpointChange);
|
787
|
+
|
688
788
|
// Initial check
|
689
789
|
handleBreakpointChange();
|
690
790
|
}
|
@@ -692,9 +792,9 @@ function setupMobileMenuBreakpointHandler() {
|
|
692
792
|
function sanitizeForID(text) {
|
693
793
|
return text
|
694
794
|
.toLowerCase()
|
695
|
-
.replace(/[^a-z0-9\s]/g,
|
696
|
-
.replace(/\s+/g,
|
697
|
-
.replace(/^-+|-+$/g,
|
795
|
+
.replace(/[^a-z0-9\s]/g, "")
|
796
|
+
.replace(/\s+/g, "-")
|
797
|
+
.replace(/^-+|-+$/g, "")
|
698
798
|
.substring(0, 50);
|
699
799
|
}
|
700
800
|
|
@@ -703,11 +803,11 @@ function setupMobileMenuARIA() {
|
|
703
803
|
const menuContainer = document.querySelector('[data-hs-nav="menu"]');
|
704
804
|
if (!menuContainer) return;
|
705
805
|
|
706
|
-
const buttons = menuContainer.querySelectorAll(
|
707
|
-
const links = menuContainer.querySelectorAll(
|
806
|
+
const buttons = menuContainer.querySelectorAll("button");
|
807
|
+
const links = menuContainer.querySelectorAll("a");
|
708
808
|
|
709
|
-
buttons.forEach(button => {
|
710
|
-
const buttonText = button.textContent
|
809
|
+
buttons.forEach((button) => {
|
810
|
+
const buttonText = button.textContent && button.textContent.trim();
|
711
811
|
if (!buttonText) return;
|
712
812
|
|
713
813
|
const sanitizedText = sanitizeForID(buttonText);
|
@@ -715,66 +815,77 @@ function setupMobileMenuARIA() {
|
|
715
815
|
const listId = `navbar-mobile-${sanitizedText}-list`;
|
716
816
|
|
717
817
|
button.id = buttonId;
|
718
|
-
button.setAttribute(
|
719
|
-
button.setAttribute(
|
818
|
+
button.setAttribute("aria-expanded", "false");
|
819
|
+
button.setAttribute("aria-controls", listId);
|
720
820
|
|
721
821
|
// Look for dropdown list in the same container as the button
|
722
822
|
let dropdownList = null;
|
723
|
-
const buttonContainer = button.closest(
|
724
|
-
|
823
|
+
const buttonContainer = button.closest(
|
824
|
+
'.menu-card_dropdown, .menu_contain, [data-hs-nav="menu"]',
|
825
|
+
);
|
826
|
+
|
725
827
|
if (buttonContainer) {
|
726
828
|
// First try to find a list element within the same container
|
727
|
-
dropdownList = buttonContainer.querySelector(
|
728
|
-
|
829
|
+
dropdownList = buttonContainer.querySelector(
|
830
|
+
'.menu-card_list, .dropdown-list, [role="menu"]',
|
831
|
+
);
|
832
|
+
|
729
833
|
// If not found, look for any element with multiple links that's not the button itself
|
730
|
-
if (!dropdownList || !dropdownList.querySelector(
|
731
|
-
const allListElements =
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
834
|
+
if (!dropdownList || !dropdownList.querySelector("a")) {
|
835
|
+
const allListElements =
|
836
|
+
buttonContainer.querySelectorAll("div, ul, nav");
|
837
|
+
dropdownList = Array.from(allListElements).find(
|
838
|
+
(el) =>
|
839
|
+
el.querySelectorAll("a").length > 1 &&
|
840
|
+
!el.contains(button) &&
|
841
|
+
el !== button,
|
736
842
|
);
|
737
843
|
}
|
738
844
|
}
|
739
845
|
|
740
|
-
if (dropdownList && dropdownList.querySelector(
|
846
|
+
if (dropdownList && dropdownList.querySelector("a")) {
|
741
847
|
dropdownList.id = listId;
|
742
848
|
setElementInert(dropdownList, true);
|
743
849
|
|
744
|
-
button.addEventListener(
|
745
|
-
const isExpanded = button.getAttribute(
|
850
|
+
button.addEventListener("click", function () {
|
851
|
+
const isExpanded = button.getAttribute("aria-expanded") === "true";
|
746
852
|
const newState = !isExpanded;
|
747
|
-
button.setAttribute(
|
853
|
+
button.setAttribute("aria-expanded", newState);
|
748
854
|
setElementInert(dropdownList, !newState);
|
749
|
-
|
855
|
+
|
750
856
|
// Announce to screen readers
|
751
857
|
const menuName = getMenuName(button);
|
752
|
-
announceToScreenReader(
|
858
|
+
announceToScreenReader(
|
859
|
+
`${menuName} submenu ${newState ? "opened" : "closed"}`,
|
860
|
+
);
|
753
861
|
});
|
754
862
|
}
|
755
863
|
});
|
756
864
|
|
757
|
-
links.forEach(link => {
|
758
|
-
const linkText = link.textContent
|
865
|
+
links.forEach((link) => {
|
866
|
+
const linkText = link.textContent && link.textContent.trim();
|
759
867
|
if (!linkText) return;
|
760
868
|
|
761
869
|
const sanitizedText = sanitizeForID(linkText);
|
762
870
|
const linkId = `navbar-mobile-${sanitizedText}-link`;
|
763
871
|
link.id = linkId;
|
764
872
|
});
|
765
|
-
|
873
|
+
|
766
874
|
setupMobileMenuArrowNavigation(menuContainer);
|
767
875
|
}
|
768
876
|
|
769
877
|
// Mobile menu arrow navigation
|
770
878
|
function setupMobileMenuArrowNavigation(menuContainer) {
|
771
879
|
function getFocusableElements() {
|
772
|
-
const allElements = menuContainer.querySelectorAll(
|
773
|
-
return Array.from(allElements).filter(el => {
|
880
|
+
const allElements = menuContainer.querySelectorAll("button, a");
|
881
|
+
return Array.from(allElements).filter((el) => {
|
774
882
|
let current = el;
|
775
883
|
while (current && current !== menuContainer) {
|
776
884
|
// Check both native inert and polyfill inert
|
777
|
-
if (
|
885
|
+
if (
|
886
|
+
current.inert === true ||
|
887
|
+
current.getAttribute("data-inert") === "true"
|
888
|
+
) {
|
778
889
|
return false;
|
779
890
|
}
|
780
891
|
current = current.parentElement;
|
@@ -782,17 +893,17 @@ function setupMobileMenuArrowNavigation(menuContainer) {
|
|
782
893
|
return true;
|
783
894
|
});
|
784
895
|
}
|
785
|
-
|
896
|
+
|
786
897
|
let currentFocusIndex = -1;
|
787
|
-
|
788
|
-
menuContainer.addEventListener(
|
898
|
+
|
899
|
+
menuContainer.addEventListener("keydown", function (e) {
|
789
900
|
const focusableElements = getFocusableElements();
|
790
901
|
if (focusableElements.length === 0) return;
|
791
|
-
|
902
|
+
|
792
903
|
const activeElement = document.activeElement;
|
793
904
|
currentFocusIndex = focusableElements.indexOf(activeElement);
|
794
|
-
|
795
|
-
if (e.key ===
|
905
|
+
|
906
|
+
if (e.key === "ArrowDown") {
|
796
907
|
e.preventDefault();
|
797
908
|
if (currentFocusIndex >= focusableElements.length - 1) {
|
798
909
|
currentFocusIndex = 0;
|
@@ -800,10 +911,12 @@ function setupMobileMenuArrowNavigation(menuContainer) {
|
|
800
911
|
currentFocusIndex = currentFocusIndex + 1;
|
801
912
|
}
|
802
913
|
focusableElements[currentFocusIndex].focus();
|
803
|
-
} else if (e.key ===
|
914
|
+
} else if (e.key === "ArrowUp") {
|
804
915
|
e.preventDefault();
|
805
916
|
if (currentFocusIndex <= 0) {
|
806
|
-
const mobileMenuButton = document.querySelector(
|
917
|
+
const mobileMenuButton = document.querySelector(
|
918
|
+
'[data-hs-nav="menubtn"]',
|
919
|
+
);
|
807
920
|
if (mobileMenuButton) {
|
808
921
|
mobileMenuButton.focus();
|
809
922
|
return;
|
@@ -811,40 +924,50 @@ function setupMobileMenuArrowNavigation(menuContainer) {
|
|
811
924
|
}
|
812
925
|
currentFocusIndex = currentFocusIndex - 1;
|
813
926
|
focusableElements[currentFocusIndex].focus();
|
814
|
-
} else if (e.key ===
|
927
|
+
} else if (e.key === "ArrowRight") {
|
815
928
|
e.preventDefault();
|
816
|
-
if (
|
817
|
-
|
929
|
+
if (
|
930
|
+
activeElement.tagName === "BUTTON" &&
|
931
|
+
activeElement.hasAttribute("aria-controls")
|
932
|
+
) {
|
933
|
+
const isExpanded =
|
934
|
+
activeElement.getAttribute("aria-expanded") === "true";
|
818
935
|
if (!isExpanded) {
|
819
936
|
activeElement.click();
|
820
937
|
}
|
821
938
|
return;
|
822
939
|
}
|
823
|
-
} else if (e.key ===
|
940
|
+
} else if (e.key === "ArrowLeft") {
|
824
941
|
e.preventDefault();
|
825
|
-
if (
|
826
|
-
|
942
|
+
if (
|
943
|
+
activeElement.tagName === "BUTTON" &&
|
944
|
+
activeElement.hasAttribute("aria-controls")
|
945
|
+
) {
|
946
|
+
const isExpanded =
|
947
|
+
activeElement.getAttribute("aria-expanded") === "true";
|
827
948
|
if (isExpanded) {
|
828
949
|
activeElement.click();
|
829
950
|
}
|
830
951
|
return;
|
831
952
|
}
|
832
|
-
} else if (e.key ===
|
953
|
+
} else if (e.key === "Home") {
|
833
954
|
e.preventDefault();
|
834
955
|
currentFocusIndex = 0;
|
835
956
|
focusableElements[0].focus();
|
836
|
-
} else if (e.key ===
|
957
|
+
} else if (e.key === "End") {
|
837
958
|
e.preventDefault();
|
838
959
|
currentFocusIndex = focusableElements.length - 1;
|
839
960
|
focusableElements[focusableElements.length - 1].focus();
|
840
|
-
} else if (e.key ===
|
961
|
+
} else if (e.key === " " && activeElement.tagName === "A") {
|
841
962
|
e.preventDefault();
|
842
|
-
} else if (e.key ===
|
843
|
-
const mobileMenuButton = document.querySelector(
|
963
|
+
} else if (e.key === "Escape") {
|
964
|
+
const mobileMenuButton = document.querySelector(
|
965
|
+
'[data-hs-nav="menubtn"]',
|
966
|
+
);
|
844
967
|
if (mobileMenuButton) {
|
845
968
|
mobileMenuButton.click();
|
846
969
|
mobileMenuButton.focus();
|
847
970
|
}
|
848
971
|
}
|
849
972
|
});
|
850
|
-
}
|
973
|
+
}
|