@hortonstudio/main 1.2.27 → 1.2.29
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 +2 -1
- package/animations/hero.js +83 -32
- package/autoInit/modal.js +75 -0
- package/index.js +5 -3
- package/package.json +1 -1
- package/utils/navbar.js +558 -131
package/animations/hero.js
CHANGED
|
@@ -45,11 +45,11 @@ const config = {
|
|
|
45
45
|
},
|
|
46
46
|
appear: {
|
|
47
47
|
y: 50,
|
|
48
|
-
duration: 1
|
|
48
|
+
duration: 1,
|
|
49
49
|
ease: "power3.out"
|
|
50
50
|
},
|
|
51
51
|
navStagger: {
|
|
52
|
-
duration: 1
|
|
52
|
+
duration: 1,
|
|
53
53
|
stagger: 0.1,
|
|
54
54
|
ease: "power3.out"
|
|
55
55
|
},
|
|
@@ -409,7 +409,30 @@ export async function init() {
|
|
|
409
409
|
|
|
410
410
|
if (navElement) {
|
|
411
411
|
heroTimeline.to(navElement,
|
|
412
|
-
{
|
|
412
|
+
{
|
|
413
|
+
opacity: 1,
|
|
414
|
+
y: 0,
|
|
415
|
+
duration: config.nav.duration,
|
|
416
|
+
ease: config.nav.ease,
|
|
417
|
+
onComplete: () => {
|
|
418
|
+
// If no advanced nav, restore interactions here
|
|
419
|
+
if (!hasAdvancedNav) {
|
|
420
|
+
const allFocusableElements = document.querySelectorAll('[data-original-tabindex]');
|
|
421
|
+
allFocusableElements.forEach(el => {
|
|
422
|
+
el.style.pointerEvents = '';
|
|
423
|
+
const originalTabindex = el.getAttribute('data-original-tabindex');
|
|
424
|
+
if (originalTabindex === '0') {
|
|
425
|
+
el.removeAttribute('tabindex');
|
|
426
|
+
} else {
|
|
427
|
+
el.setAttribute('tabindex', originalTabindex);
|
|
428
|
+
}
|
|
429
|
+
el.removeAttribute('data-original-tabindex');
|
|
430
|
+
});
|
|
431
|
+
// Restore nav pointer events
|
|
432
|
+
navElement.style.pointerEvents = '';
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
},
|
|
413
436
|
timing.nav
|
|
414
437
|
);
|
|
415
438
|
}
|
|
@@ -453,7 +476,29 @@ export async function init() {
|
|
|
453
476
|
|
|
454
477
|
if (hasAdvancedNav && navButton.length > 0) {
|
|
455
478
|
heroTimeline.to(navButton,
|
|
456
|
-
{
|
|
479
|
+
{
|
|
480
|
+
opacity: 1,
|
|
481
|
+
duration: config.nav.duration,
|
|
482
|
+
ease: config.nav.ease,
|
|
483
|
+
onComplete: () => {
|
|
484
|
+
// Restore page-wide tabbing and interactions after navbar animations complete
|
|
485
|
+
const allFocusableElements = document.querySelectorAll('[data-original-tabindex]');
|
|
486
|
+
allFocusableElements.forEach(el => {
|
|
487
|
+
el.style.pointerEvents = '';
|
|
488
|
+
const originalTabindex = el.getAttribute('data-original-tabindex');
|
|
489
|
+
if (originalTabindex === '0') {
|
|
490
|
+
el.removeAttribute('tabindex');
|
|
491
|
+
} else {
|
|
492
|
+
el.setAttribute('tabindex', originalTabindex);
|
|
493
|
+
}
|
|
494
|
+
el.removeAttribute('data-original-tabindex');
|
|
495
|
+
});
|
|
496
|
+
// Restore nav pointer events
|
|
497
|
+
if (navElement) {
|
|
498
|
+
navElement.style.pointerEvents = '';
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
},
|
|
457
502
|
timing.navButton
|
|
458
503
|
);
|
|
459
504
|
}
|
|
@@ -539,43 +584,49 @@ export async function init() {
|
|
|
539
584
|
duration: config.appear.duration,
|
|
540
585
|
ease: config.appear.ease,
|
|
541
586
|
onComplete: () => {
|
|
542
|
-
//
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
el.
|
|
549
|
-
|
|
550
|
-
|
|
587
|
+
// Check if interactions haven't been restored yet
|
|
588
|
+
const stillDisabled = document.querySelector('[data-original-tabindex]');
|
|
589
|
+
if (stillDisabled) {
|
|
590
|
+
const allFocusableElements = document.querySelectorAll('[data-original-tabindex]');
|
|
591
|
+
allFocusableElements.forEach(el => {
|
|
592
|
+
el.style.pointerEvents = '';
|
|
593
|
+
const originalTabindex = el.getAttribute('data-original-tabindex');
|
|
594
|
+
if (originalTabindex === '0') {
|
|
595
|
+
el.removeAttribute('tabindex');
|
|
596
|
+
} else {
|
|
597
|
+
el.setAttribute('tabindex', originalTabindex);
|
|
598
|
+
}
|
|
599
|
+
el.removeAttribute('data-original-tabindex');
|
|
600
|
+
});
|
|
601
|
+
// Restore nav pointer events
|
|
602
|
+
if (navElement) {
|
|
603
|
+
navElement.style.pointerEvents = '';
|
|
551
604
|
}
|
|
552
|
-
el.removeAttribute('data-original-tabindex');
|
|
553
|
-
});
|
|
554
|
-
// Restore nav pointer events
|
|
555
|
-
if (navElement) {
|
|
556
|
-
navElement.style.pointerEvents = '';
|
|
557
605
|
}
|
|
558
606
|
}
|
|
559
607
|
},
|
|
560
608
|
timing.appear
|
|
561
609
|
);
|
|
562
610
|
} else {
|
|
563
|
-
// If no appear elements,
|
|
611
|
+
// If no appear elements, check if interactions need restoring when timeline completes
|
|
564
612
|
heroTimeline.call(() => {
|
|
565
|
-
const
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
el.
|
|
571
|
-
|
|
572
|
-
|
|
613
|
+
const stillDisabled = document.querySelector('[data-original-tabindex]');
|
|
614
|
+
if (stillDisabled) {
|
|
615
|
+
const allFocusableElements = document.querySelectorAll('[data-original-tabindex]');
|
|
616
|
+
allFocusableElements.forEach(el => {
|
|
617
|
+
el.style.pointerEvents = '';
|
|
618
|
+
const originalTabindex = el.getAttribute('data-original-tabindex');
|
|
619
|
+
if (originalTabindex === '0') {
|
|
620
|
+
el.removeAttribute('tabindex');
|
|
621
|
+
} else {
|
|
622
|
+
el.setAttribute('tabindex', originalTabindex);
|
|
623
|
+
}
|
|
624
|
+
el.removeAttribute('data-original-tabindex');
|
|
625
|
+
});
|
|
626
|
+
// Restore nav pointer events
|
|
627
|
+
if (navElement) {
|
|
628
|
+
navElement.style.pointerEvents = '';
|
|
573
629
|
}
|
|
574
|
-
el.removeAttribute('data-original-tabindex');
|
|
575
|
-
});
|
|
576
|
-
// Restore nav pointer events
|
|
577
|
-
if (navElement) {
|
|
578
|
-
navElement.style.pointerEvents = '';
|
|
579
630
|
}
|
|
580
631
|
});
|
|
581
632
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
function initModal() {
|
|
2
|
+
const config = {
|
|
3
|
+
transitionDuration: 0.3,
|
|
4
|
+
blurOpacity: 0.5
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
function openModal(element) {
|
|
8
|
+
document.body.classList.add('u-overflow-clip');
|
|
9
|
+
|
|
10
|
+
// Add blur to all other modals
|
|
11
|
+
document.querySelectorAll('[data-hs-modal]').forEach(modal => {
|
|
12
|
+
if (modal !== element) {
|
|
13
|
+
modal.style.display = 'block';
|
|
14
|
+
modal.style.opacity = config.blurOpacity;
|
|
15
|
+
modal.style.transition = `opacity ${config.transitionDuration}s ease`;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function closeModal(element) {
|
|
21
|
+
document.body.classList.remove('u-overflow-clip');
|
|
22
|
+
|
|
23
|
+
// Remove blur from all other modals
|
|
24
|
+
document.querySelectorAll('[data-hs-modal]').forEach(modal => {
|
|
25
|
+
if (modal !== element) {
|
|
26
|
+
modal.style.display = 'none';
|
|
27
|
+
modal.style.opacity = '0';
|
|
28
|
+
modal.style.transition = `opacity ${config.transitionDuration}s ease`;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function toggleModal(element) {
|
|
34
|
+
element.x = ((element.x || 0) + 1) % 2;
|
|
35
|
+
|
|
36
|
+
if (element.x) {
|
|
37
|
+
openModal(element);
|
|
38
|
+
} else {
|
|
39
|
+
closeModal(element);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Initialize openclose functionality
|
|
44
|
+
document.querySelectorAll('[data-hs-modal="openclose"]').forEach(trigger => {
|
|
45
|
+
trigger.addEventListener('click', function() {
|
|
46
|
+
toggleModal(this);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Initialize open functionality
|
|
51
|
+
document.querySelectorAll('[data-hs-modal="open"]').forEach(trigger => {
|
|
52
|
+
trigger.addEventListener('click', function() {
|
|
53
|
+
openModal(this);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Initialize close functionality
|
|
58
|
+
document.querySelectorAll('[data-hs-modal="close"]').forEach(trigger => {
|
|
59
|
+
trigger.addEventListener('click', function() {
|
|
60
|
+
closeModal(this);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return { result: 'modal initialized' };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function init() {
|
|
68
|
+
if (document.readyState === 'loading') {
|
|
69
|
+
document.addEventListener('DOMContentLoaded', initModal);
|
|
70
|
+
} else {
|
|
71
|
+
initModal();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return { result: 'modal initialized' };
|
|
75
|
+
}
|
package/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Version:1.2.
|
|
1
|
+
// Version:1.2.29
|
|
2
2
|
|
|
3
3
|
const API_NAME = 'hsmain';
|
|
4
4
|
|
|
@@ -22,7 +22,8 @@ const initializeHsMain = async () => {
|
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
const autoInitModules = {
|
|
25
|
-
'smooth-scroll': true
|
|
25
|
+
'smooth-scroll': true,
|
|
26
|
+
'modal': true
|
|
26
27
|
};
|
|
27
28
|
|
|
28
29
|
const allDataAttributes = { ...animationModules, ...utilityModules };
|
|
@@ -47,7 +48,8 @@ const initializeHsMain = async () => {
|
|
|
47
48
|
'data-hs-util-toc': () => import('./utils/toc.js'),
|
|
48
49
|
'data-hs-util-progress': () => import('./utils/scroll-progress.js'),
|
|
49
50
|
'data-hs-util-navbar': () => import('./utils/navbar.js'),
|
|
50
|
-
'smooth-scroll': () => import('./autoInit/smooth-scroll.js')
|
|
51
|
+
'smooth-scroll': () => import('./autoInit/smooth-scroll.js'),
|
|
52
|
+
'modal': () => import('./autoInit/modal.js')
|
|
51
53
|
};
|
|
52
54
|
|
|
53
55
|
let scripts = [...document.querySelectorAll(`script[type="module"][src="${import.meta.url}"]`)];
|
package/package.json
CHANGED
package/utils/navbar.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
export const init = () => {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
setupDynamicDropdowns();
|
|
3
|
+
setupMobileMenuButton();
|
|
4
|
+
setupMobileMenuARIA();
|
|
5
|
+
return { result: 'navbar initialized' };
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
// Desktop dropdown system
|
|
9
|
+
function setupDynamicDropdowns() {
|
|
10
|
+
const dropdownWrappers = document.querySelectorAll('[data-hs-nav="dropdown"]');
|
|
6
11
|
const allDropdowns = [];
|
|
7
12
|
|
|
8
|
-
// Function to close all dropdowns except the specified one
|
|
9
13
|
const closeAllDropdowns = (exceptWrapper = null) => {
|
|
10
14
|
allDropdowns.forEach(dropdown => {
|
|
11
15
|
if (dropdown.wrapper !== exceptWrapper && dropdown.isOpen) {
|
|
@@ -15,193 +19,250 @@ export const init = () => {
|
|
|
15
19
|
};
|
|
16
20
|
|
|
17
21
|
dropdownWrappers.forEach(wrapper => {
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
22
|
+
const toggle = wrapper.querySelector('a');
|
|
23
|
+
if (!toggle) return;
|
|
24
|
+
|
|
25
|
+
const allElements = wrapper.querySelectorAll('*');
|
|
26
|
+
let dropdownList = null;
|
|
27
|
+
|
|
28
|
+
for (const element of allElements) {
|
|
29
|
+
const links = element.querySelectorAll('a');
|
|
30
|
+
if (links.length >= 2 && !element.contains(toggle)) {
|
|
31
|
+
dropdownList = element;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!dropdownList) return;
|
|
37
|
+
|
|
38
|
+
const toggleText = toggle.textContent?.trim() || 'dropdown';
|
|
39
|
+
const sanitizedText = sanitizeForID(toggleText);
|
|
40
|
+
const toggleId = `navbar-dropdown-${sanitizedText}-toggle`;
|
|
41
|
+
const listId = `navbar-dropdown-${sanitizedText}-list`;
|
|
42
|
+
|
|
43
|
+
toggle.id = toggleId;
|
|
44
|
+
toggle.setAttribute('aria-haspopup', 'menu');
|
|
45
|
+
toggle.setAttribute('aria-expanded', 'false');
|
|
46
|
+
toggle.setAttribute('aria-controls', listId);
|
|
47
|
+
|
|
48
|
+
dropdownList.id = listId;
|
|
49
|
+
dropdownList.setAttribute('role', 'menu');
|
|
50
|
+
dropdownList.setAttribute('aria-hidden', 'true');
|
|
51
|
+
|
|
52
|
+
const menuItems = dropdownList.querySelectorAll('a');
|
|
53
|
+
menuItems.forEach(item => {
|
|
54
|
+
item.setAttribute('role', 'menuitem');
|
|
55
|
+
item.setAttribute('tabindex', '-1');
|
|
56
|
+
});
|
|
57
|
+
|
|
34
58
|
let isOpen = false;
|
|
35
|
-
let
|
|
59
|
+
let currentMenuItemIndex = -1;
|
|
36
60
|
|
|
37
|
-
// Open animation
|
|
38
61
|
function openDropdown() {
|
|
39
62
|
if (isOpen) return;
|
|
40
|
-
|
|
41
|
-
// Kill any existing timeline
|
|
42
|
-
if (currentTimeline) {
|
|
43
|
-
currentTimeline.kill();
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Close all other dropdowns first
|
|
47
63
|
closeAllDropdowns(wrapper);
|
|
48
|
-
|
|
49
64
|
isOpen = true;
|
|
50
|
-
|
|
51
|
-
// Update ARIA states
|
|
52
65
|
toggle.setAttribute('aria-expanded', 'true');
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
.to(arrow, {
|
|
64
|
-
rotation: 90,
|
|
65
|
-
scale: 1.2,
|
|
66
|
-
x: 4,
|
|
67
|
-
color: 'var(--swatch--brand)',
|
|
68
|
-
duration: animationDuration,
|
|
69
|
-
ease: 'ease'
|
|
70
|
-
}, 0)
|
|
71
|
-
.to(text, {
|
|
72
|
-
scale: 1.1,
|
|
73
|
-
color: 'var(--swatch--brand)',
|
|
74
|
-
duration: animationDuration,
|
|
75
|
-
ease: 'ease'
|
|
76
|
-
}, 0);
|
|
66
|
+
dropdownList.setAttribute('aria-hidden', 'false');
|
|
67
|
+
menuItems.forEach(item => {
|
|
68
|
+
item.setAttribute('tabindex', '0');
|
|
69
|
+
});
|
|
70
|
+
const clickEvent = new MouseEvent('click', {
|
|
71
|
+
bubbles: true,
|
|
72
|
+
cancelable: true,
|
|
73
|
+
view: window
|
|
74
|
+
});
|
|
75
|
+
wrapper.dispatchEvent(clickEvent);
|
|
77
76
|
}
|
|
78
77
|
|
|
79
|
-
// Close animation
|
|
80
78
|
function closeDropdown() {
|
|
81
79
|
if (!isOpen) return;
|
|
82
|
-
|
|
83
|
-
// Kill any existing timeline
|
|
84
|
-
if (currentTimeline) {
|
|
85
|
-
currentTimeline.kill();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Check if focus should be restored to toggle
|
|
89
|
-
const shouldRestoreFocus = list.contains(document.activeElement);
|
|
90
|
-
|
|
80
|
+
const shouldRestoreFocus = dropdownList.contains(document.activeElement);
|
|
91
81
|
isOpen = false;
|
|
92
82
|
currentMenuItemIndex = -1;
|
|
93
|
-
|
|
94
|
-
// Update ARIA states
|
|
95
|
-
toggle.setAttribute('aria-expanded', 'false');
|
|
96
|
-
list.setAttribute('aria-hidden', 'true');
|
|
97
|
-
|
|
98
|
-
// Temporarily remove role="menu" to help screen readers understand menu is closed
|
|
99
|
-
const originalRole = list.getAttribute('role');
|
|
100
|
-
list.removeAttribute('role');
|
|
101
|
-
|
|
102
|
-
// GSAP animation
|
|
103
|
-
currentTimeline = gsap.timeline();
|
|
104
|
-
currentTimeline.to(contain, {
|
|
105
|
-
yPercent: -110,
|
|
106
|
-
duration: animationDuration,
|
|
107
|
-
ease: 'ease'
|
|
108
|
-
}, 0)
|
|
109
|
-
.to(arrow, {
|
|
110
|
-
rotation: 0,
|
|
111
|
-
scale: 1,
|
|
112
|
-
x: 0,
|
|
113
|
-
color: '', // back to default color
|
|
114
|
-
duration: animationDuration,
|
|
115
|
-
ease: 'ease'
|
|
116
|
-
}, 0)
|
|
117
|
-
.to(text, {
|
|
118
|
-
scale: 1,
|
|
119
|
-
color: '', // back to default color
|
|
120
|
-
duration: animationDuration,
|
|
121
|
-
ease: 'ease'
|
|
122
|
-
}, 0)
|
|
123
|
-
.set(list, { display: 'none' })
|
|
124
|
-
.call(() => {
|
|
125
|
-
// Restore role after animation completes
|
|
126
|
-
list.setAttribute('role', originalRole || 'menu');
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
// Restore focus to toggle only if focus was inside dropdown
|
|
130
83
|
if (shouldRestoreFocus) {
|
|
131
|
-
|
|
132
|
-
setTimeout(() => {
|
|
133
|
-
toggle.focus();
|
|
134
|
-
}, 50);
|
|
84
|
+
toggle.focus();
|
|
135
85
|
}
|
|
86
|
+
toggle.setAttribute('aria-expanded', 'false');
|
|
87
|
+
dropdownList.setAttribute('aria-hidden', 'true');
|
|
88
|
+
menuItems.forEach(item => {
|
|
89
|
+
item.setAttribute('tabindex', '-1');
|
|
90
|
+
});
|
|
91
|
+
const clickEvent = new MouseEvent('click', {
|
|
92
|
+
bubbles: true,
|
|
93
|
+
cancelable: true,
|
|
94
|
+
view: window
|
|
95
|
+
});
|
|
96
|
+
wrapper.dispatchEvent(clickEvent);
|
|
136
97
|
}
|
|
137
98
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
99
|
+
wrapper.addEventListener('mouseenter', () => {
|
|
100
|
+
if (!isOpen) {
|
|
101
|
+
const clickEvent = new MouseEvent('click', {
|
|
102
|
+
bubbles: true,
|
|
103
|
+
cancelable: true,
|
|
104
|
+
view: window
|
|
105
|
+
});
|
|
106
|
+
wrapper.dispatchEvent(clickEvent);
|
|
107
|
+
closeAllDropdowns(wrapper);
|
|
108
|
+
isOpen = true;
|
|
109
|
+
toggle.setAttribute('aria-expanded', 'true');
|
|
110
|
+
dropdownList.setAttribute('aria-hidden', 'false');
|
|
111
|
+
menuItems.forEach(item => {
|
|
112
|
+
item.setAttribute('tabindex', '0');
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
});
|
|
141
116
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
117
|
+
wrapper.addEventListener('mouseleave', () => {
|
|
118
|
+
if (isOpen) {
|
|
119
|
+
if (dropdownList.contains(document.activeElement)) {
|
|
120
|
+
toggle.focus();
|
|
121
|
+
}
|
|
122
|
+
const clickEvent = new MouseEvent('click', {
|
|
123
|
+
bubbles: true,
|
|
124
|
+
cancelable: true,
|
|
125
|
+
view: window
|
|
126
|
+
});
|
|
127
|
+
wrapper.dispatchEvent(clickEvent);
|
|
128
|
+
isOpen = false;
|
|
129
|
+
toggle.setAttribute('aria-expanded', 'false');
|
|
130
|
+
dropdownList.setAttribute('aria-hidden', 'true');
|
|
131
|
+
menuItems.forEach(item => {
|
|
132
|
+
item.setAttribute('tabindex', '-1');
|
|
133
|
+
});
|
|
134
|
+
currentMenuItemIndex = -1;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
145
137
|
|
|
146
|
-
|
|
147
|
-
list.addEventListener('keydown', function(e) {
|
|
138
|
+
document.addEventListener('keydown', function(e) {
|
|
148
139
|
if (!isOpen) return;
|
|
140
|
+
if (!wrapper.contains(document.activeElement)) return;
|
|
149
141
|
|
|
150
142
|
if (e.key === 'ArrowDown') {
|
|
151
143
|
e.preventDefault();
|
|
152
|
-
|
|
153
|
-
|
|
144
|
+
if (document.activeElement === toggle) {
|
|
145
|
+
currentMenuItemIndex = 0;
|
|
146
|
+
menuItems[currentMenuItemIndex].focus();
|
|
147
|
+
} else {
|
|
148
|
+
if (currentMenuItemIndex === menuItems.length - 1) {
|
|
149
|
+
const nextElement = wrapper.nextElementSibling?.querySelector('a, button') ||
|
|
150
|
+
document.querySelector('.navbar_cartsearch_wrap a, .navbar_cartsearch_wrap button');
|
|
151
|
+
if (nextElement) {
|
|
152
|
+
closeDropdown();
|
|
153
|
+
nextElement.focus();
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
currentMenuItemIndex = (currentMenuItemIndex + 1) % menuItems.length;
|
|
158
|
+
menuItems[currentMenuItemIndex].focus();
|
|
159
|
+
}
|
|
154
160
|
} else if (e.key === 'ArrowUp') {
|
|
155
161
|
e.preventDefault();
|
|
156
|
-
|
|
157
|
-
|
|
162
|
+
if (document.activeElement === toggle) {
|
|
163
|
+
currentMenuItemIndex = menuItems.length - 1;
|
|
164
|
+
menuItems[currentMenuItemIndex].focus();
|
|
165
|
+
} else {
|
|
166
|
+
if (currentMenuItemIndex === 0) {
|
|
167
|
+
const prevElement = wrapper.previousElementSibling?.querySelector('a, button');
|
|
168
|
+
if (prevElement) {
|
|
169
|
+
closeDropdown();
|
|
170
|
+
prevElement.focus();
|
|
171
|
+
return;
|
|
172
|
+
} else {
|
|
173
|
+
closeDropdown();
|
|
174
|
+
toggle.focus();
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
currentMenuItemIndex = currentMenuItemIndex <= 0 ? menuItems.length - 1 : currentMenuItemIndex - 1;
|
|
179
|
+
menuItems[currentMenuItemIndex].focus();
|
|
180
|
+
}
|
|
181
|
+
} else if (e.key === 'Tab') {
|
|
182
|
+
if (e.shiftKey) {
|
|
183
|
+
if (document.activeElement === menuItems[0]) {
|
|
184
|
+
e.preventDefault();
|
|
185
|
+
closeDropdown();
|
|
186
|
+
toggle.focus();
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
if (document.activeElement === menuItems[menuItems.length - 1]) {
|
|
190
|
+
e.preventDefault();
|
|
191
|
+
const nextElement = wrapper.nextElementSibling?.querySelector('a, button') ||
|
|
192
|
+
document.querySelector('.navbar_cartsearch_wrap a, .navbar_cartsearch_wrap button');
|
|
193
|
+
closeDropdown();
|
|
194
|
+
if (nextElement) {
|
|
195
|
+
setTimeout(() => {
|
|
196
|
+
nextElement.focus();
|
|
197
|
+
}, 10);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
158
201
|
} else if (e.key === 'Escape') {
|
|
159
202
|
e.preventDefault();
|
|
160
203
|
closeDropdown();
|
|
161
204
|
toggle.focus();
|
|
205
|
+
} else if (e.key === 'Home') {
|
|
206
|
+
e.preventDefault();
|
|
207
|
+
currentMenuItemIndex = 0;
|
|
208
|
+
menuItems[0].focus();
|
|
209
|
+
} else if (e.key === 'End') {
|
|
210
|
+
e.preventDefault();
|
|
211
|
+
currentMenuItemIndex = menuItems.length - 1;
|
|
212
|
+
menuItems[menuItems.length - 1].focus();
|
|
213
|
+
} else if (e.key === ' ') {
|
|
214
|
+
e.preventDefault();
|
|
162
215
|
}
|
|
163
216
|
});
|
|
164
217
|
|
|
165
|
-
// Keyboard events for toggle
|
|
166
218
|
toggle.addEventListener('keydown', function(e) {
|
|
167
219
|
if (e.key === 'ArrowDown') {
|
|
168
220
|
e.preventDefault();
|
|
169
221
|
openDropdown();
|
|
170
|
-
// Focus first menu item after opening
|
|
171
222
|
if (menuItems.length > 0) {
|
|
172
223
|
currentMenuItemIndex = 0;
|
|
173
|
-
setTimeout(() => menuItems[0].focus(),
|
|
224
|
+
setTimeout(() => menuItems[0].focus(), 100);
|
|
174
225
|
}
|
|
175
226
|
} else if (e.key === ' ') {
|
|
176
227
|
e.preventDefault();
|
|
177
|
-
// Simple toggle: if closed open, if open close
|
|
178
228
|
if (isOpen) {
|
|
179
229
|
closeDropdown();
|
|
180
230
|
} else {
|
|
181
231
|
openDropdown();
|
|
232
|
+
if (menuItems.length > 0) {
|
|
233
|
+
currentMenuItemIndex = 0;
|
|
234
|
+
setTimeout(() => menuItems[0].focus(), 100);
|
|
235
|
+
}
|
|
182
236
|
}
|
|
183
|
-
} else if (e.key === 'ArrowUp'
|
|
237
|
+
} else if (e.key === 'ArrowUp') {
|
|
238
|
+
e.preventDefault();
|
|
239
|
+
if (isOpen) {
|
|
240
|
+
currentMenuItemIndex = menuItems.length - 1;
|
|
241
|
+
menuItems[currentMenuItemIndex].focus();
|
|
242
|
+
} else {
|
|
243
|
+
closeDropdown();
|
|
244
|
+
}
|
|
245
|
+
} else if (e.key === 'Escape') {
|
|
184
246
|
e.preventDefault();
|
|
185
247
|
closeDropdown();
|
|
186
248
|
}
|
|
187
249
|
});
|
|
188
250
|
|
|
189
|
-
// Close dropdown when clicking outside
|
|
190
251
|
document.addEventListener('click', function(e) {
|
|
191
252
|
if (!wrapper.contains(e.target) && isOpen) {
|
|
192
253
|
closeDropdown();
|
|
193
254
|
}
|
|
194
255
|
});
|
|
195
256
|
|
|
196
|
-
// Add this dropdown instance to the global array
|
|
197
257
|
allDropdowns.push({
|
|
198
258
|
wrapper,
|
|
199
259
|
isOpen: () => isOpen,
|
|
200
|
-
closeDropdown
|
|
260
|
+
closeDropdown,
|
|
261
|
+
toggle,
|
|
262
|
+
dropdownList
|
|
201
263
|
});
|
|
202
264
|
});
|
|
203
265
|
|
|
204
|
-
// Global focus management - close dropdown when tab focus moves outside
|
|
205
266
|
document.addEventListener('focusin', function(e) {
|
|
206
267
|
allDropdowns.forEach(dropdown => {
|
|
207
268
|
if (dropdown.isOpen() && !dropdown.wrapper.contains(e.target)) {
|
|
@@ -210,5 +271,371 @@ export const init = () => {
|
|
|
210
271
|
});
|
|
211
272
|
});
|
|
212
273
|
|
|
213
|
-
|
|
214
|
-
}
|
|
274
|
+
addDesktopArrowNavigation();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Desktop left/right arrow navigation
|
|
278
|
+
function addDesktopArrowNavigation() {
|
|
279
|
+
document.addEventListener('keydown', function(e) {
|
|
280
|
+
if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return;
|
|
281
|
+
|
|
282
|
+
const mobileMenu = document.querySelector('[data-hs-nav="menu"]');
|
|
283
|
+
if (mobileMenu && mobileMenu.contains(document.activeElement)) return;
|
|
284
|
+
|
|
285
|
+
const navbar = document.querySelector('[data-hs-nav="wrapper"]') ||
|
|
286
|
+
document.querySelector('.navbar_component') ||
|
|
287
|
+
document.querySelector('nav[role="navigation"]') ||
|
|
288
|
+
document.querySelector('nav');
|
|
289
|
+
|
|
290
|
+
if (!navbar || !navbar.contains(document.activeElement)) return;
|
|
291
|
+
|
|
292
|
+
const openDropdownList = navbar.querySelector('[aria-hidden="false"][role="menu"]');
|
|
293
|
+
if (openDropdownList && openDropdownList.contains(document.activeElement)) return;
|
|
294
|
+
|
|
295
|
+
e.preventDefault();
|
|
296
|
+
|
|
297
|
+
const allNavbarElements = navbar.querySelectorAll('a, button');
|
|
298
|
+
const focusableElements = Array.from(allNavbarElements).filter(el => {
|
|
299
|
+
if (el.getAttribute('tabindex') === '-1') return false;
|
|
300
|
+
|
|
301
|
+
const isInDropdownList = el.closest('[role="menu"]');
|
|
302
|
+
if (isInDropdownList) return false;
|
|
303
|
+
|
|
304
|
+
const isInMobileMenu = el.closest('[data-hs-nav="menu"]');
|
|
305
|
+
if (isInMobileMenu) return false;
|
|
306
|
+
|
|
307
|
+
const computedStyle = window.getComputedStyle(el);
|
|
308
|
+
const isHidden = computedStyle.display === 'none' ||
|
|
309
|
+
computedStyle.visibility === 'hidden' ||
|
|
310
|
+
computedStyle.opacity === '0' ||
|
|
311
|
+
el.offsetWidth === 0 ||
|
|
312
|
+
el.offsetHeight === 0;
|
|
313
|
+
if (isHidden) return false;
|
|
314
|
+
|
|
315
|
+
let parent = el.parentElement;
|
|
316
|
+
while (parent && parent !== navbar) {
|
|
317
|
+
const parentStyle = window.getComputedStyle(parent);
|
|
318
|
+
const parentHidden = parentStyle.display === 'none' ||
|
|
319
|
+
parentStyle.visibility === 'hidden' ||
|
|
320
|
+
parent.offsetWidth === 0 ||
|
|
321
|
+
parent.offsetHeight === 0;
|
|
322
|
+
if (parentHidden) return false;
|
|
323
|
+
parent = parent.parentElement;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return true;
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
const currentIndex = focusableElements.indexOf(document.activeElement);
|
|
330
|
+
if (currentIndex === -1) return;
|
|
331
|
+
|
|
332
|
+
let nextIndex;
|
|
333
|
+
if (e.key === 'ArrowRight') {
|
|
334
|
+
nextIndex = (currentIndex + 1) % focusableElements.length;
|
|
335
|
+
} else {
|
|
336
|
+
nextIndex = currentIndex === 0 ? focusableElements.length - 1 : currentIndex - 1;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
focusableElements[nextIndex].focus();
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Mobile menu button system
|
|
344
|
+
function setupMobileMenuButton() {
|
|
345
|
+
const menuButton = document.querySelector('[data-hs-nav="menubtn"]');
|
|
346
|
+
const mobileMenu = document.querySelector('[data-hs-nav="menu"]');
|
|
347
|
+
|
|
348
|
+
if (!menuButton || !mobileMenu) return;
|
|
349
|
+
|
|
350
|
+
const menuId = `mobile-menu-${Date.now()}`;
|
|
351
|
+
|
|
352
|
+
menuButton.setAttribute('aria-expanded', 'false');
|
|
353
|
+
menuButton.setAttribute('aria-controls', menuId);
|
|
354
|
+
menuButton.setAttribute('aria-label', 'Open navigation menu');
|
|
355
|
+
|
|
356
|
+
mobileMenu.id = menuId;
|
|
357
|
+
mobileMenu.setAttribute('role', 'dialog');
|
|
358
|
+
mobileMenu.setAttribute('aria-modal', 'true');
|
|
359
|
+
mobileMenu.inert = true;
|
|
360
|
+
|
|
361
|
+
let isMenuOpen = false;
|
|
362
|
+
|
|
363
|
+
function openMenu() {
|
|
364
|
+
if (isMenuOpen) return;
|
|
365
|
+
isMenuOpen = true;
|
|
366
|
+
menuButton.setAttribute('aria-expanded', 'true');
|
|
367
|
+
menuButton.setAttribute('aria-label', 'Close navigation menu');
|
|
368
|
+
mobileMenu.inert = false;
|
|
369
|
+
|
|
370
|
+
// Prevent tabbing outside navbar using tabindex management
|
|
371
|
+
const navbarWrapper = document.querySelector('[data-hs-nav="wrapper"]') ||
|
|
372
|
+
document.querySelector('.navbar_component') ||
|
|
373
|
+
document.querySelector('nav[role="navigation"]') ||
|
|
374
|
+
document.querySelector('nav');
|
|
375
|
+
|
|
376
|
+
const allFocusableElements = document.querySelectorAll('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])');
|
|
377
|
+
allFocusableElements.forEach(el => {
|
|
378
|
+
if (navbarWrapper && !navbarWrapper.contains(el)) {
|
|
379
|
+
el.setAttribute('data-mobile-menu-tabindex', el.getAttribute('tabindex') || '0');
|
|
380
|
+
el.setAttribute('tabindex', '-1');
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
const clickEvent = new MouseEvent('click', {
|
|
385
|
+
bubbles: true,
|
|
386
|
+
cancelable: true,
|
|
387
|
+
view: window
|
|
388
|
+
});
|
|
389
|
+
menuButton.dispatchEvent(clickEvent);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function closeMenu() {
|
|
393
|
+
if (!isMenuOpen) return;
|
|
394
|
+
isMenuOpen = false;
|
|
395
|
+
if (mobileMenu.contains(document.activeElement)) {
|
|
396
|
+
menuButton.focus();
|
|
397
|
+
}
|
|
398
|
+
menuButton.setAttribute('aria-expanded', 'false');
|
|
399
|
+
menuButton.setAttribute('aria-label', 'Open navigation menu');
|
|
400
|
+
mobileMenu.inert = true;
|
|
401
|
+
|
|
402
|
+
// Restore tabbing to entire page using tabindex management
|
|
403
|
+
const elementsToRestore = document.querySelectorAll('[data-mobile-menu-tabindex]');
|
|
404
|
+
elementsToRestore.forEach(el => {
|
|
405
|
+
const originalTabindex = el.getAttribute('data-mobile-menu-tabindex');
|
|
406
|
+
if (originalTabindex === '0') {
|
|
407
|
+
el.removeAttribute('tabindex');
|
|
408
|
+
} else {
|
|
409
|
+
el.setAttribute('tabindex', originalTabindex);
|
|
410
|
+
}
|
|
411
|
+
el.removeAttribute('data-mobile-menu-tabindex');
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
const clickEvent = new MouseEvent('click', {
|
|
415
|
+
bubbles: true,
|
|
416
|
+
cancelable: true,
|
|
417
|
+
view: window
|
|
418
|
+
});
|
|
419
|
+
menuButton.dispatchEvent(clickEvent);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function toggleMenu() {
|
|
423
|
+
if (isMenuOpen) {
|
|
424
|
+
closeMenu();
|
|
425
|
+
} else {
|
|
426
|
+
openMenu();
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
menuButton.addEventListener('keydown', function(e) {
|
|
431
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
432
|
+
e.preventDefault();
|
|
433
|
+
toggleMenu();
|
|
434
|
+
} else if (e.key === 'ArrowDown') {
|
|
435
|
+
e.preventDefault();
|
|
436
|
+
if (!isMenuOpen) {
|
|
437
|
+
openMenu();
|
|
438
|
+
}
|
|
439
|
+
const firstElement = mobileMenu.querySelector('button, a');
|
|
440
|
+
if (firstElement) {
|
|
441
|
+
firstElement.focus();
|
|
442
|
+
}
|
|
443
|
+
} else if (e.key === 'ArrowUp') {
|
|
444
|
+
e.preventDefault();
|
|
445
|
+
if (isMenuOpen) {
|
|
446
|
+
closeMenu();
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
menuButton.addEventListener('click', function(e) {
|
|
452
|
+
if (!e.isTrusted) return;
|
|
453
|
+
if (isMenuOpen && mobileMenu.contains(document.activeElement)) {
|
|
454
|
+
menuButton.focus();
|
|
455
|
+
}
|
|
456
|
+
isMenuOpen = !isMenuOpen;
|
|
457
|
+
menuButton.setAttribute('aria-expanded', isMenuOpen);
|
|
458
|
+
menuButton.setAttribute('aria-label',
|
|
459
|
+
isMenuOpen ? 'Close navigation menu' : 'Open navigation menu'
|
|
460
|
+
);
|
|
461
|
+
mobileMenu.inert = !isMenuOpen;
|
|
462
|
+
|
|
463
|
+
// Handle tabindex management for external clicks
|
|
464
|
+
if (isMenuOpen) {
|
|
465
|
+
const navbarWrapper = document.querySelector('[data-hs-nav="wrapper"]') ||
|
|
466
|
+
document.querySelector('.navbar_component') ||
|
|
467
|
+
document.querySelector('nav[role="navigation"]') ||
|
|
468
|
+
document.querySelector('nav');
|
|
469
|
+
|
|
470
|
+
const allFocusableElements = document.querySelectorAll('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])');
|
|
471
|
+
allFocusableElements.forEach(el => {
|
|
472
|
+
if (navbarWrapper && !navbarWrapper.contains(el)) {
|
|
473
|
+
el.setAttribute('data-mobile-menu-tabindex', el.getAttribute('tabindex') || '0');
|
|
474
|
+
el.setAttribute('tabindex', '-1');
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
} else {
|
|
478
|
+
const elementsToRestore = document.querySelectorAll('[data-mobile-menu-tabindex]');
|
|
479
|
+
elementsToRestore.forEach(el => {
|
|
480
|
+
const originalTabindex = el.getAttribute('data-mobile-menu-tabindex');
|
|
481
|
+
if (originalTabindex === '0') {
|
|
482
|
+
el.removeAttribute('tabindex');
|
|
483
|
+
} else {
|
|
484
|
+
el.setAttribute('tabindex', originalTabindex);
|
|
485
|
+
}
|
|
486
|
+
el.removeAttribute('data-mobile-menu-tabindex');
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function sanitizeForID(text) {
|
|
493
|
+
return text
|
|
494
|
+
.toLowerCase()
|
|
495
|
+
.replace(/[^a-z0-9\s]/g, '')
|
|
496
|
+
.replace(/\s+/g, '-')
|
|
497
|
+
.replace(/^-+|-+$/g, '')
|
|
498
|
+
.substring(0, 50);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Mobile menu ARIA setup
|
|
502
|
+
function setupMobileMenuARIA() {
|
|
503
|
+
const menuContainer = document.querySelector('[data-hs-nav="menu"]');
|
|
504
|
+
if (!menuContainer) return;
|
|
505
|
+
|
|
506
|
+
const buttons = menuContainer.querySelectorAll('button');
|
|
507
|
+
const links = menuContainer.querySelectorAll('a');
|
|
508
|
+
|
|
509
|
+
buttons.forEach(button => {
|
|
510
|
+
const buttonText = button.textContent?.trim();
|
|
511
|
+
if (!buttonText) return;
|
|
512
|
+
|
|
513
|
+
const sanitizedText = sanitizeForID(buttonText);
|
|
514
|
+
const buttonId = `navbar-mobile-${sanitizedText}-toggle`;
|
|
515
|
+
const listId = `navbar-mobile-${sanitizedText}-list`;
|
|
516
|
+
|
|
517
|
+
button.id = buttonId;
|
|
518
|
+
button.setAttribute('aria-expanded', 'false');
|
|
519
|
+
button.setAttribute('aria-controls', listId);
|
|
520
|
+
|
|
521
|
+
let dropdownList = button.nextElementSibling;
|
|
522
|
+
|
|
523
|
+
if (!dropdownList || !dropdownList.querySelector('a')) {
|
|
524
|
+
dropdownList = button.parentElement?.nextElementSibling;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (!dropdownList || !dropdownList.querySelector('a')) {
|
|
528
|
+
const parent = button.closest('[data-hs-nav="menu"]');
|
|
529
|
+
const allListElements = parent?.querySelectorAll('div, ul, nav');
|
|
530
|
+
dropdownList = Array.from(allListElements || []).find(el =>
|
|
531
|
+
el.querySelectorAll('a').length > 1 &&
|
|
532
|
+
!el.contains(button)
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if (dropdownList && dropdownList.querySelector('a')) {
|
|
537
|
+
dropdownList.id = listId;
|
|
538
|
+
dropdownList.inert = true;
|
|
539
|
+
|
|
540
|
+
button.addEventListener('click', function() {
|
|
541
|
+
const isExpanded = button.getAttribute('aria-expanded') === 'true';
|
|
542
|
+
const newState = !isExpanded;
|
|
543
|
+
button.setAttribute('aria-expanded', newState);
|
|
544
|
+
dropdownList.inert = !newState;
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
links.forEach(link => {
|
|
550
|
+
const linkText = link.textContent?.trim();
|
|
551
|
+
if (!linkText) return;
|
|
552
|
+
|
|
553
|
+
const sanitizedText = sanitizeForID(linkText);
|
|
554
|
+
const linkId = `navbar-mobile-${sanitizedText}-link`;
|
|
555
|
+
link.id = linkId;
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
setupMobileMenuArrowNavigation(menuContainer);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Mobile menu arrow navigation
|
|
562
|
+
function setupMobileMenuArrowNavigation(menuContainer) {
|
|
563
|
+
function getFocusableElements() {
|
|
564
|
+
const allElements = menuContainer.querySelectorAll('button, a');
|
|
565
|
+
return Array.from(allElements).filter(el => {
|
|
566
|
+
let current = el;
|
|
567
|
+
while (current && current !== menuContainer) {
|
|
568
|
+
if (current.inert === true) {
|
|
569
|
+
return false;
|
|
570
|
+
}
|
|
571
|
+
current = current.parentElement;
|
|
572
|
+
}
|
|
573
|
+
return true;
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
let currentFocusIndex = -1;
|
|
578
|
+
|
|
579
|
+
menuContainer.addEventListener('keydown', function(e) {
|
|
580
|
+
const focusableElements = getFocusableElements();
|
|
581
|
+
if (focusableElements.length === 0) return;
|
|
582
|
+
|
|
583
|
+
const activeElement = document.activeElement;
|
|
584
|
+
currentFocusIndex = focusableElements.indexOf(activeElement);
|
|
585
|
+
|
|
586
|
+
if (e.key === 'ArrowDown') {
|
|
587
|
+
e.preventDefault();
|
|
588
|
+
if (currentFocusIndex >= focusableElements.length - 1) {
|
|
589
|
+
currentFocusIndex = 0;
|
|
590
|
+
} else {
|
|
591
|
+
currentFocusIndex = currentFocusIndex + 1;
|
|
592
|
+
}
|
|
593
|
+
focusableElements[currentFocusIndex].focus();
|
|
594
|
+
} else if (e.key === 'ArrowUp') {
|
|
595
|
+
e.preventDefault();
|
|
596
|
+
if (currentFocusIndex <= 0) {
|
|
597
|
+
const mobileMenuButton = document.querySelector('[data-hs-nav="menubtn"]');
|
|
598
|
+
if (mobileMenuButton) {
|
|
599
|
+
mobileMenuButton.focus();
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
currentFocusIndex = currentFocusIndex - 1;
|
|
604
|
+
focusableElements[currentFocusIndex].focus();
|
|
605
|
+
} else if (e.key === 'ArrowRight') {
|
|
606
|
+
e.preventDefault();
|
|
607
|
+
if (activeElement.tagName === 'BUTTON' && activeElement.hasAttribute('aria-controls')) {
|
|
608
|
+
const isExpanded = activeElement.getAttribute('aria-expanded') === 'true';
|
|
609
|
+
if (!isExpanded) {
|
|
610
|
+
activeElement.click();
|
|
611
|
+
}
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
} else if (e.key === 'ArrowLeft') {
|
|
615
|
+
e.preventDefault();
|
|
616
|
+
if (activeElement.tagName === 'BUTTON' && activeElement.hasAttribute('aria-controls')) {
|
|
617
|
+
const isExpanded = activeElement.getAttribute('aria-expanded') === 'true';
|
|
618
|
+
if (isExpanded) {
|
|
619
|
+
activeElement.click();
|
|
620
|
+
}
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
} else if (e.key === 'Home') {
|
|
624
|
+
e.preventDefault();
|
|
625
|
+
currentFocusIndex = 0;
|
|
626
|
+
focusableElements[0].focus();
|
|
627
|
+
} else if (e.key === 'End') {
|
|
628
|
+
e.preventDefault();
|
|
629
|
+
currentFocusIndex = focusableElements.length - 1;
|
|
630
|
+
focusableElements[focusableElements.length - 1].focus();
|
|
631
|
+
} else if (e.key === ' ' && activeElement.tagName === 'A') {
|
|
632
|
+
e.preventDefault();
|
|
633
|
+
} else if (e.key === 'Escape') {
|
|
634
|
+
const mobileMenuButton = document.querySelector('[data-hs-nav="menubtn"]');
|
|
635
|
+
if (mobileMenuButton) {
|
|
636
|
+
mobileMenuButton.click();
|
|
637
|
+
mobileMenuButton.focus();
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
});
|
|
641
|
+
}
|