@hortonstudio/main 1.8.2 → 1.9.0
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/autoInit/accessibility.js +122 -93
- package/autoInit/counter.js +17 -1
- package/autoInit/form.js +58 -13
- package/autoInit/navbar.js +136 -77
- package/autoInit/smooth-scroll.js +33 -10
- package/autoInit/transition.js +88 -62
- package/index.js +18 -9
- package/package.json +1 -1
- package/utils/slider.js +58 -19
package/autoInit/navbar.js
CHANGED
|
@@ -1,18 +1,65 @@
|
|
|
1
1
|
export const init = () => {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
// Centralized cleanup tracking
|
|
3
|
+
const cleanup = {
|
|
4
|
+
observers: [],
|
|
5
|
+
handlers: [],
|
|
6
|
+
state: {
|
|
7
|
+
focusTrapHandler: null,
|
|
8
|
+
dropdownKeydownHandler: null,
|
|
9
|
+
menuKeydownHandler: null
|
|
10
|
+
}
|
|
11
|
+
};
|
|
5
12
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
const addObserver = (observer) => cleanup.observers.push(observer);
|
|
14
|
+
const addHandler = (element, event, handler, options) => {
|
|
15
|
+
element.addEventListener(event, handler, options);
|
|
16
|
+
cleanup.handlers.push({ element, event, handler, options });
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
setupDynamicDropdowns(addObserver, addHandler);
|
|
20
|
+
setupMenuButton(addHandler, cleanup.state);
|
|
21
|
+
setupMenuARIA(addHandler);
|
|
22
|
+
setupMenuDisplayObserver(addObserver);
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
result: "navbar initialized",
|
|
26
|
+
destroy: () => {
|
|
27
|
+
// Disconnect all observers
|
|
28
|
+
cleanup.observers.forEach(obs => obs.disconnect());
|
|
29
|
+
cleanup.observers.length = 0;
|
|
30
|
+
|
|
31
|
+
// Remove all event listeners
|
|
32
|
+
cleanup.handlers.forEach(({ element, event, handler, options }) => {
|
|
33
|
+
element.removeEventListener(event, handler, options);
|
|
34
|
+
});
|
|
35
|
+
cleanup.handlers.length = 0;
|
|
36
|
+
|
|
37
|
+
// Clean up focus trap
|
|
38
|
+
if (cleanup.state.focusTrapHandler) {
|
|
39
|
+
document.removeEventListener('keydown', cleanup.state.focusTrapHandler);
|
|
40
|
+
cleanup.state.focusTrapHandler = null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Clean up global document listeners
|
|
44
|
+
if (cleanup.state.dropdownKeydownHandler) {
|
|
45
|
+
document.removeEventListener('keydown', cleanup.state.dropdownKeydownHandler);
|
|
46
|
+
cleanup.state.dropdownKeydownHandler = null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (cleanup.state.menuKeydownHandler) {
|
|
50
|
+
document.removeEventListener('keydown', cleanup.state.menuKeydownHandler);
|
|
51
|
+
cleanup.state.menuKeydownHandler = null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Remove body overflow class if present
|
|
55
|
+
document.body.classList.remove("u-overflow-hidden");
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
};
|
|
12
59
|
|
|
13
60
|
|
|
14
61
|
// Desktop dropdown system
|
|
15
|
-
function setupDynamicDropdowns() {
|
|
62
|
+
function setupDynamicDropdowns(addObserver, addHandler) {
|
|
16
63
|
const dropdownWrappers = document.querySelectorAll(
|
|
17
64
|
'[data-hs-nav="dropdown"]',
|
|
18
65
|
);
|
|
@@ -53,7 +100,7 @@ function setupDynamicDropdowns() {
|
|
|
53
100
|
menuItems.forEach((item, index) => {
|
|
54
101
|
item.setAttribute("role", "menuitem");
|
|
55
102
|
item.setAttribute("tabindex", "-1");
|
|
56
|
-
|
|
103
|
+
|
|
57
104
|
// Add context for first item to help screen readers understand dropdown content
|
|
58
105
|
if (index === 0) {
|
|
59
106
|
const toggleText = toggle.textContent?.trim() || "menu";
|
|
@@ -78,12 +125,12 @@ function setupDynamicDropdowns() {
|
|
|
78
125
|
function updateARIAStates() {
|
|
79
126
|
const isOpen = isDropdownOpen();
|
|
80
127
|
const wasOpen = toggle.getAttribute("aria-expanded") === "true";
|
|
81
|
-
|
|
128
|
+
|
|
82
129
|
// If dropdown is closing (was open, now closed), focus the toggle first
|
|
83
130
|
if (wasOpen && !isOpen && dropdownList.contains(document.activeElement)) {
|
|
84
131
|
toggle.focus();
|
|
85
132
|
}
|
|
86
|
-
|
|
133
|
+
|
|
87
134
|
toggle.setAttribute("aria-expanded", isOpen ? "true" : "false");
|
|
88
135
|
dropdownList.setAttribute("aria-hidden", isOpen ? "false" : "true");
|
|
89
136
|
menuItems.forEach((item) => {
|
|
@@ -96,27 +143,24 @@ function setupDynamicDropdowns() {
|
|
|
96
143
|
}
|
|
97
144
|
|
|
98
145
|
// Monitor for class changes and update ARIA states
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
// Initialize monitoring
|
|
111
|
-
monitorDropdownState();
|
|
146
|
+
const observer = new MutationObserver(() => {
|
|
147
|
+
updateARIAStates();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
observer.observe(wrapper, {
|
|
151
|
+
attributes: true,
|
|
152
|
+
attributeFilter: ['class']
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
addObserver(observer);
|
|
112
156
|
|
|
113
157
|
// Hover interactions now handled entirely by native Webflow
|
|
114
158
|
|
|
115
159
|
// Add keyboard interactions that trigger programmatic mouse events
|
|
116
|
-
|
|
160
|
+
const toggleKeydownHandler = function (e) {
|
|
117
161
|
if (e.key === "ArrowDown") {
|
|
118
162
|
e.preventDefault();
|
|
119
|
-
|
|
163
|
+
|
|
120
164
|
if (!isDropdownOpen()) {
|
|
121
165
|
// Trigger programmatic mouseenter to open dropdown
|
|
122
166
|
const mouseEnterEvent = new MouseEvent("mouseenter", {
|
|
@@ -126,7 +170,7 @@ function setupDynamicDropdowns() {
|
|
|
126
170
|
relatedTarget: null
|
|
127
171
|
});
|
|
128
172
|
wrapper.dispatchEvent(mouseEnterEvent);
|
|
129
|
-
|
|
173
|
+
|
|
130
174
|
// Focus first menu item after a brief delay
|
|
131
175
|
if (menuItems.length > 0) {
|
|
132
176
|
setTimeout(() => {
|
|
@@ -137,7 +181,7 @@ function setupDynamicDropdowns() {
|
|
|
137
181
|
}
|
|
138
182
|
} else if (e.key === " ") {
|
|
139
183
|
e.preventDefault();
|
|
140
|
-
|
|
184
|
+
|
|
141
185
|
if (!isDropdownOpen()) {
|
|
142
186
|
// Trigger programmatic mouseenter to open dropdown
|
|
143
187
|
const mouseEnterEvent = new MouseEvent("mouseenter", {
|
|
@@ -147,7 +191,7 @@ function setupDynamicDropdowns() {
|
|
|
147
191
|
relatedTarget: null
|
|
148
192
|
});
|
|
149
193
|
wrapper.dispatchEvent(mouseEnterEvent);
|
|
150
|
-
|
|
194
|
+
|
|
151
195
|
// Focus first menu item after a brief delay
|
|
152
196
|
if (menuItems.length > 0) {
|
|
153
197
|
setTimeout(() => {
|
|
@@ -164,7 +208,7 @@ function setupDynamicDropdowns() {
|
|
|
164
208
|
relatedTarget: document.body
|
|
165
209
|
});
|
|
166
210
|
wrapper.dispatchEvent(mouseOutEvent);
|
|
167
|
-
|
|
211
|
+
|
|
168
212
|
const mouseLeaveEvent = new MouseEvent("mouseleave", {
|
|
169
213
|
bubbles: false,
|
|
170
214
|
cancelable: false,
|
|
@@ -175,7 +219,7 @@ function setupDynamicDropdowns() {
|
|
|
175
219
|
}
|
|
176
220
|
} else if (e.key === "ArrowUp") {
|
|
177
221
|
e.preventDefault();
|
|
178
|
-
|
|
222
|
+
|
|
179
223
|
if (!isDropdownOpen()) {
|
|
180
224
|
// Trigger programmatic mouseenter to open dropdown
|
|
181
225
|
const mouseEnterEvent = new MouseEvent("mouseenter", {
|
|
@@ -185,7 +229,7 @@ function setupDynamicDropdowns() {
|
|
|
185
229
|
relatedTarget: null
|
|
186
230
|
});
|
|
187
231
|
wrapper.dispatchEvent(mouseEnterEvent);
|
|
188
|
-
|
|
232
|
+
|
|
189
233
|
// Focus last menu item after a brief delay
|
|
190
234
|
if (menuItems.length > 0) {
|
|
191
235
|
setTimeout(() => {
|
|
@@ -196,7 +240,7 @@ function setupDynamicDropdowns() {
|
|
|
196
240
|
}
|
|
197
241
|
} else if (e.key === "Escape") {
|
|
198
242
|
e.preventDefault();
|
|
199
|
-
|
|
243
|
+
|
|
200
244
|
if (isDropdownOpen()) {
|
|
201
245
|
// Trigger mouse leave to close dropdown
|
|
202
246
|
const mouseOutEvent = new MouseEvent("mouseout", {
|
|
@@ -206,7 +250,7 @@ function setupDynamicDropdowns() {
|
|
|
206
250
|
relatedTarget: document.body
|
|
207
251
|
});
|
|
208
252
|
wrapper.dispatchEvent(mouseOutEvent);
|
|
209
|
-
|
|
253
|
+
|
|
210
254
|
const mouseLeaveEvent = new MouseEvent("mouseleave", {
|
|
211
255
|
bubbles: false,
|
|
212
256
|
cancelable: false,
|
|
@@ -216,10 +260,12 @@ function setupDynamicDropdowns() {
|
|
|
216
260
|
wrapper.dispatchEvent(mouseLeaveEvent);
|
|
217
261
|
}
|
|
218
262
|
}
|
|
219
|
-
}
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
addHandler(toggle, "keydown", toggleKeydownHandler);
|
|
220
266
|
|
|
221
267
|
// Handle navigation within open dropdown
|
|
222
|
-
|
|
268
|
+
const dropdownKeydownHandler = function (e) {
|
|
223
269
|
if (!isDropdownOpen()) return;
|
|
224
270
|
if (!wrapper.contains(document.activeElement)) return;
|
|
225
271
|
|
|
@@ -231,10 +277,7 @@ function setupDynamicDropdowns() {
|
|
|
231
277
|
}
|
|
232
278
|
} else if (e.key === "ArrowUp") {
|
|
233
279
|
e.preventDefault();
|
|
234
|
-
if (currentMenuItemIndex
|
|
235
|
-
currentMenuItemIndex--;
|
|
236
|
-
menuItems[currentMenuItemIndex].focus();
|
|
237
|
-
} else if (currentMenuItemIndex === 0) {
|
|
280
|
+
if (currentMenuItemIndex === 0) {
|
|
238
281
|
// On first item, trigger mouse leave to close dropdown
|
|
239
282
|
const mouseOutEvent = new MouseEvent("mouseout", {
|
|
240
283
|
bubbles: true,
|
|
@@ -243,7 +286,7 @@ function setupDynamicDropdowns() {
|
|
|
243
286
|
relatedTarget: document.body
|
|
244
287
|
});
|
|
245
288
|
wrapper.dispatchEvent(mouseOutEvent);
|
|
246
|
-
|
|
289
|
+
|
|
247
290
|
const mouseLeaveEvent = new MouseEvent("mouseleave", {
|
|
248
291
|
bubbles: false,
|
|
249
292
|
cancelable: false,
|
|
@@ -251,10 +294,13 @@ function setupDynamicDropdowns() {
|
|
|
251
294
|
relatedTarget: document.body
|
|
252
295
|
});
|
|
253
296
|
wrapper.dispatchEvent(mouseLeaveEvent);
|
|
254
|
-
|
|
297
|
+
|
|
255
298
|
// Focus back to toggle
|
|
256
299
|
toggle.focus();
|
|
257
300
|
currentMenuItemIndex = -1;
|
|
301
|
+
} else if (currentMenuItemIndex > 0) {
|
|
302
|
+
currentMenuItemIndex--;
|
|
303
|
+
menuItems[currentMenuItemIndex].focus();
|
|
258
304
|
} else {
|
|
259
305
|
// Go back to toggle
|
|
260
306
|
toggle.focus();
|
|
@@ -262,7 +308,7 @@ function setupDynamicDropdowns() {
|
|
|
262
308
|
}
|
|
263
309
|
} else if (e.key === "Escape") {
|
|
264
310
|
e.preventDefault();
|
|
265
|
-
|
|
311
|
+
|
|
266
312
|
// Trigger mouse leave to close dropdown
|
|
267
313
|
const mouseOutEvent = new MouseEvent("mouseout", {
|
|
268
314
|
bubbles: true,
|
|
@@ -271,7 +317,7 @@ function setupDynamicDropdowns() {
|
|
|
271
317
|
relatedTarget: document.body
|
|
272
318
|
});
|
|
273
319
|
wrapper.dispatchEvent(mouseOutEvent);
|
|
274
|
-
|
|
320
|
+
|
|
275
321
|
const mouseLeaveEvent = new MouseEvent("mouseleave", {
|
|
276
322
|
bubbles: false,
|
|
277
323
|
cancelable: false,
|
|
@@ -280,7 +326,9 @@ function setupDynamicDropdowns() {
|
|
|
280
326
|
});
|
|
281
327
|
wrapper.dispatchEvent(mouseLeaveEvent);
|
|
282
328
|
}
|
|
283
|
-
}
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
addHandler(document, "keydown", dropdownKeydownHandler);
|
|
284
332
|
|
|
285
333
|
allDropdowns.push({
|
|
286
334
|
wrapper,
|
|
@@ -294,20 +342,22 @@ function setupDynamicDropdowns() {
|
|
|
294
342
|
});
|
|
295
343
|
});
|
|
296
344
|
|
|
297
|
-
|
|
345
|
+
const focusinHandler = function (e) {
|
|
298
346
|
allDropdowns.forEach((dropdown) => {
|
|
299
347
|
if (dropdown.isOpen() && !dropdown.wrapper.contains(e.target)) {
|
|
300
348
|
dropdown.closeDropdown();
|
|
301
349
|
}
|
|
302
350
|
});
|
|
303
|
-
}
|
|
351
|
+
};
|
|
304
352
|
|
|
305
|
-
|
|
353
|
+
addHandler(document, "focusin", focusinHandler);
|
|
354
|
+
|
|
355
|
+
addDesktopArrowNavigation(addHandler);
|
|
306
356
|
}
|
|
307
357
|
|
|
308
358
|
// Desktop left/right arrow navigation
|
|
309
|
-
function addDesktopArrowNavigation() {
|
|
310
|
-
|
|
359
|
+
function addDesktopArrowNavigation(addHandler) {
|
|
360
|
+
const keydownHandler = function (e) {
|
|
311
361
|
if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return;
|
|
312
362
|
|
|
313
363
|
const menu = document.querySelector('[data-hs-nav="menu"]');
|
|
@@ -376,11 +426,13 @@ function addDesktopArrowNavigation() {
|
|
|
376
426
|
focusableElements[nextIndex].focus();
|
|
377
427
|
}
|
|
378
428
|
}
|
|
379
|
-
}
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
addHandler(document, "keydown", keydownHandler);
|
|
380
432
|
}
|
|
381
433
|
|
|
382
434
|
// Menu button system with modal-like functionality
|
|
383
|
-
function setupMenuButton() {
|
|
435
|
+
function setupMenuButton(addHandler, cleanupState) {
|
|
384
436
|
const menuButtons = document.querySelectorAll('[data-hs-nav="menubtn"]');
|
|
385
437
|
const menu = document.querySelector('[data-hs-nav="menu"]');
|
|
386
438
|
|
|
@@ -393,7 +445,6 @@ function setupMenuButton() {
|
|
|
393
445
|
menu.setAttribute("aria-modal", "true");
|
|
394
446
|
|
|
395
447
|
let isMenuOpen = false;
|
|
396
|
-
let focusTrapHandler = null;
|
|
397
448
|
|
|
398
449
|
function shouldPreventMenu() {
|
|
399
450
|
const menuHideElement = document.querySelector(".menu_hide");
|
|
@@ -408,7 +459,7 @@ function setupMenuButton() {
|
|
|
408
459
|
|
|
409
460
|
if (!navbarWrapper) return;
|
|
410
461
|
|
|
411
|
-
focusTrapHandler = (e) => {
|
|
462
|
+
const focusTrapHandler = (e) => {
|
|
412
463
|
if (e.key === 'Tab') {
|
|
413
464
|
const focusableElements = navbarWrapper.querySelectorAll(
|
|
414
465
|
'a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
@@ -434,12 +485,13 @@ function setupMenuButton() {
|
|
|
434
485
|
};
|
|
435
486
|
|
|
436
487
|
document.addEventListener('keydown', focusTrapHandler);
|
|
488
|
+
cleanupState.focusTrapHandler = focusTrapHandler;
|
|
437
489
|
}
|
|
438
490
|
|
|
439
491
|
function removeFocusTrap() {
|
|
440
|
-
if (focusTrapHandler) {
|
|
441
|
-
document.removeEventListener('keydown', focusTrapHandler);
|
|
442
|
-
focusTrapHandler = null;
|
|
492
|
+
if (cleanupState.focusTrapHandler) {
|
|
493
|
+
document.removeEventListener('keydown', cleanupState.focusTrapHandler);
|
|
494
|
+
cleanupState.focusTrapHandler = null;
|
|
443
495
|
}
|
|
444
496
|
}
|
|
445
497
|
|
|
@@ -487,7 +539,7 @@ function setupMenuButton() {
|
|
|
487
539
|
if (activeMenuButton) {
|
|
488
540
|
activeMenuButton.focus();
|
|
489
541
|
}
|
|
490
|
-
|
|
542
|
+
|
|
491
543
|
menuButtons.forEach(btn => {
|
|
492
544
|
btn.setAttribute("aria-expanded", "false");
|
|
493
545
|
btn.setAttribute("aria-label", "Open navigation menu");
|
|
@@ -506,7 +558,7 @@ function setupMenuButton() {
|
|
|
506
558
|
|
|
507
559
|
function toggleMenu() {
|
|
508
560
|
if (shouldPreventMenu()) return;
|
|
509
|
-
|
|
561
|
+
|
|
510
562
|
if (isMenuOpen) {
|
|
511
563
|
closeMenu();
|
|
512
564
|
} else {
|
|
@@ -519,7 +571,7 @@ function setupMenuButton() {
|
|
|
519
571
|
menuButton.setAttribute("aria-controls", menuId);
|
|
520
572
|
menuButton.setAttribute("aria-label", "Open navigation menu");
|
|
521
573
|
|
|
522
|
-
|
|
574
|
+
const keydownHandler = function (e) {
|
|
523
575
|
if (e.key === "Enter" || e.key === " ") {
|
|
524
576
|
e.preventDefault();
|
|
525
577
|
const config = menuButton.getAttribute("data-hs-config");
|
|
@@ -531,9 +583,11 @@ function setupMenuButton() {
|
|
|
531
583
|
toggleMenu();
|
|
532
584
|
}
|
|
533
585
|
}
|
|
534
|
-
}
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
addHandler(menuButton, "keydown", keydownHandler);
|
|
535
589
|
|
|
536
|
-
|
|
590
|
+
const clickHandler = function (e) {
|
|
537
591
|
if (!e.isTrusted) return;
|
|
538
592
|
const config = menuButton.getAttribute("data-hs-config");
|
|
539
593
|
if (config === "close" && isMenuOpen) {
|
|
@@ -543,24 +597,25 @@ function setupMenuButton() {
|
|
|
543
597
|
} else if (!config) {
|
|
544
598
|
toggleMenu();
|
|
545
599
|
}
|
|
546
|
-
}
|
|
547
|
-
});
|
|
600
|
+
};
|
|
548
601
|
|
|
602
|
+
addHandler(menuButton, "click", clickHandler);
|
|
603
|
+
});
|
|
549
604
|
}
|
|
550
605
|
|
|
551
606
|
|
|
552
|
-
function setupMenuDisplayObserver() {
|
|
607
|
+
function setupMenuDisplayObserver(addObserver) {
|
|
553
608
|
function handleDisplayChange() {
|
|
554
609
|
const menuHideElement = document.querySelector(".menu_hide");
|
|
555
610
|
if (!menuHideElement) return;
|
|
556
|
-
|
|
611
|
+
|
|
557
612
|
const computedStyle = window.getComputedStyle(menuHideElement);
|
|
558
613
|
const isMenuVisible = computedStyle.display !== "none";
|
|
559
|
-
|
|
614
|
+
|
|
560
615
|
// Get menu button to check if menu is open
|
|
561
616
|
const menuButton = document.querySelector('[data-hs-nav="menubtn"]');
|
|
562
617
|
const isMenuOpen = menuButton && menuButton.getAttribute("aria-expanded") === "true";
|
|
563
|
-
|
|
618
|
+
|
|
564
619
|
const shouldShowModal = isMenuVisible && isMenuOpen;
|
|
565
620
|
|
|
566
621
|
// Toggle modal effects only when menu is visible AND menu is open
|
|
@@ -571,6 +626,7 @@ function setupMenuDisplayObserver() {
|
|
|
571
626
|
const menuHideElement = document.querySelector(".menu_hide");
|
|
572
627
|
if (menuHideElement) {
|
|
573
628
|
displayObserver.observe(menuHideElement);
|
|
629
|
+
addObserver(displayObserver);
|
|
574
630
|
// Initial check
|
|
575
631
|
handleDisplayChange();
|
|
576
632
|
}
|
|
@@ -586,7 +642,7 @@ function sanitizeForID(text) {
|
|
|
586
642
|
}
|
|
587
643
|
|
|
588
644
|
// Menu ARIA setup
|
|
589
|
-
function setupMenuARIA() {
|
|
645
|
+
function setupMenuARIA(addHandler) {
|
|
590
646
|
const menuContainer = document.querySelector('[data-hs-nav="menu"]');
|
|
591
647
|
if (!menuContainer) return;
|
|
592
648
|
|
|
@@ -610,13 +666,14 @@ function setupMenuARIA() {
|
|
|
610
666
|
dropdownList.id = listId;
|
|
611
667
|
dropdownList.setAttribute("aria-hidden", "true");
|
|
612
668
|
|
|
613
|
-
|
|
669
|
+
const clickHandler = function () {
|
|
614
670
|
const isExpanded = button.getAttribute("aria-expanded") === "true";
|
|
615
671
|
const newState = !isExpanded;
|
|
616
672
|
button.setAttribute("aria-expanded", newState);
|
|
617
673
|
dropdownList.setAttribute("aria-hidden", !newState);
|
|
674
|
+
};
|
|
618
675
|
|
|
619
|
-
|
|
676
|
+
addHandler(button, "click", clickHandler);
|
|
620
677
|
}
|
|
621
678
|
});
|
|
622
679
|
|
|
@@ -627,11 +684,11 @@ function setupMenuARIA() {
|
|
|
627
684
|
link.id = linkId;
|
|
628
685
|
});
|
|
629
686
|
|
|
630
|
-
setupMenuArrowNavigation(menuContainer);
|
|
687
|
+
setupMenuArrowNavigation(menuContainer, addHandler);
|
|
631
688
|
}
|
|
632
689
|
|
|
633
690
|
// Menu arrow navigation
|
|
634
|
-
function setupMenuArrowNavigation(menuContainer) {
|
|
691
|
+
function setupMenuArrowNavigation(menuContainer, addHandler) {
|
|
635
692
|
function getFocusableElements() {
|
|
636
693
|
const allElements = menuContainer.querySelectorAll("button, a");
|
|
637
694
|
return Array.from(allElements).filter((el) => {
|
|
@@ -649,7 +706,7 @@ function setupMenuArrowNavigation(menuContainer) {
|
|
|
649
706
|
|
|
650
707
|
let currentFocusIndex = -1;
|
|
651
708
|
|
|
652
|
-
|
|
709
|
+
const keydownHandler = function (e) {
|
|
653
710
|
const focusableElements = getFocusableElements();
|
|
654
711
|
if (focusableElements.length === 0) return;
|
|
655
712
|
|
|
@@ -713,5 +770,7 @@ function setupMenuArrowNavigation(menuContainer) {
|
|
|
713
770
|
menuButton.focus();
|
|
714
771
|
}
|
|
715
772
|
}
|
|
716
|
-
}
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
addHandler(menuContainer, "keydown", keydownHandler);
|
|
717
776
|
}
|
|
@@ -2,6 +2,11 @@ const API_NAME = "hsmain";
|
|
|
2
2
|
|
|
3
3
|
export async function init() {
|
|
4
4
|
const api = window[API_NAME];
|
|
5
|
+
|
|
6
|
+
// Store event handlers for cleanup
|
|
7
|
+
let clickHandler = null;
|
|
8
|
+
let keydownHandler = null;
|
|
9
|
+
|
|
5
10
|
api.afterWebflowReady(() => {
|
|
6
11
|
if (typeof $ !== "undefined") {
|
|
7
12
|
$(document).off("click.wf-scroll");
|
|
@@ -52,16 +57,6 @@ export async function init() {
|
|
|
52
57
|
});
|
|
53
58
|
}
|
|
54
59
|
|
|
55
|
-
// Handle anchor link clicks and keyboard activation
|
|
56
|
-
function handleAnchorClicks() {
|
|
57
|
-
document.addEventListener("click", handleAnchorActivation);
|
|
58
|
-
document.addEventListener("keydown", function (e) {
|
|
59
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
60
|
-
handleAnchorActivation(e);
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
60
|
function handleAnchorActivation(e) {
|
|
66
61
|
const link = e.target.closest('a[href^="#"]');
|
|
67
62
|
if (!link) return;
|
|
@@ -82,6 +77,19 @@ export async function init() {
|
|
|
82
77
|
}
|
|
83
78
|
}
|
|
84
79
|
|
|
80
|
+
// Handle anchor link clicks and keyboard activation
|
|
81
|
+
function handleAnchorClicks() {
|
|
82
|
+
clickHandler = handleAnchorActivation;
|
|
83
|
+
keydownHandler = function (e) {
|
|
84
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
85
|
+
handleAnchorActivation(e);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
document.addEventListener("click", clickHandler);
|
|
90
|
+
document.addEventListener("keydown", keydownHandler);
|
|
91
|
+
}
|
|
92
|
+
|
|
85
93
|
// Initialize anchor link handling
|
|
86
94
|
handleAnchorClicks();
|
|
87
95
|
|
|
@@ -93,5 +101,20 @@ export async function init() {
|
|
|
93
101
|
|
|
94
102
|
return {
|
|
95
103
|
result: "autoInit-smooth-scroll initialized",
|
|
104
|
+
destroy: () => {
|
|
105
|
+
// Remove event listeners
|
|
106
|
+
if (clickHandler) {
|
|
107
|
+
document.removeEventListener("click", clickHandler);
|
|
108
|
+
clickHandler = null;
|
|
109
|
+
}
|
|
110
|
+
if (keydownHandler) {
|
|
111
|
+
document.removeEventListener("keydown", keydownHandler);
|
|
112
|
+
keydownHandler = null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Re-enable CSS smooth scrolling
|
|
116
|
+
document.documentElement.style.scrollBehavior = "";
|
|
117
|
+
document.body.style.scrollBehavior = "";
|
|
118
|
+
}
|
|
96
119
|
};
|
|
97
120
|
}
|