@hortonstudio/main 1.6.5 → 1.6.6
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/navbar.js +181 -157
- package/index.js +1 -1
- package/package.json +1 -1
package/autoInit/navbar.js
CHANGED
|
@@ -68,183 +68,129 @@ function setupDynamicDropdowns() {
|
|
|
68
68
|
}
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
-
let isOpen = false;
|
|
72
71
|
let currentMenuItemIndex = -1;
|
|
73
72
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
closeAllDropdowns(wrapper);
|
|
77
|
-
|
|
78
|
-
// Set ARIA states FIRST, before focus changes
|
|
79
|
-
isOpen = true;
|
|
80
|
-
toggle.setAttribute("aria-expanded", "true");
|
|
81
|
-
dropdownList.setAttribute("aria-hidden", "false");
|
|
82
|
-
menuItems.forEach((item) => {
|
|
83
|
-
item.setAttribute("tabindex", "0");
|
|
84
|
-
});
|
|
73
|
+
// Set initial ARIA states
|
|
74
|
+
updateARIAStates();
|
|
85
75
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
view: window,
|
|
90
|
-
});
|
|
91
|
-
wrapper.dispatchEvent(clickEvent);
|
|
76
|
+
// Check if dropdown is open by looking for is-active class on wrapper
|
|
77
|
+
function isDropdownOpen() {
|
|
78
|
+
return wrapper.classList.contains('is-active');
|
|
92
79
|
}
|
|
93
80
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
// Update ARIA states FIRST to help screen readers understand content is hidden
|
|
99
|
-
isOpen = false;
|
|
100
|
-
currentMenuItemIndex = -1;
|
|
101
|
-
toggle.setAttribute("aria-expanded", "false");
|
|
102
|
-
dropdownList.setAttribute("aria-hidden", "true");
|
|
103
|
-
menuItems.forEach((item) => {
|
|
104
|
-
item.setAttribute("tabindex", "-1");
|
|
105
|
-
});
|
|
81
|
+
// Update ARIA states based on current visual state
|
|
82
|
+
function updateARIAStates() {
|
|
83
|
+
const isOpen = isDropdownOpen();
|
|
84
|
+
const wasOpen = toggle.getAttribute("aria-expanded") === "true";
|
|
106
85
|
|
|
107
|
-
//
|
|
108
|
-
if (
|
|
109
|
-
// This helps reset virtual cursor position
|
|
110
|
-
dropdownList.focus();
|
|
86
|
+
// If dropdown is closing (was open, now closed), focus the toggle first
|
|
87
|
+
if (wasOpen && !isOpen && dropdownList.contains(document.activeElement)) {
|
|
111
88
|
toggle.focus();
|
|
112
89
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
90
|
+
|
|
91
|
+
toggle.setAttribute("aria-expanded", isOpen ? "true" : "false");
|
|
92
|
+
dropdownList.setAttribute("aria-hidden", isOpen ? "false" : "true");
|
|
93
|
+
menuItems.forEach((item) => {
|
|
94
|
+
item.setAttribute("tabindex", isOpen ? "0" : "-1");
|
|
118
95
|
});
|
|
119
|
-
wrapper.dispatchEvent(clickEvent);
|
|
120
|
-
}
|
|
121
96
|
|
|
122
|
-
wrapper.addEventListener("mouseenter", () => {
|
|
123
97
|
if (!isOpen) {
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
wrapper.addEventListener("mouseleave", () => {
|
|
129
|
-
if (isOpen) {
|
|
130
|
-
closeDropdown();
|
|
98
|
+
currentMenuItemIndex = -1;
|
|
131
99
|
}
|
|
132
|
-
}
|
|
100
|
+
}
|
|
133
101
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
102
|
+
// Monitor for class changes and update ARIA states
|
|
103
|
+
function monitorDropdownState() {
|
|
104
|
+
const observer = new MutationObserver(() => {
|
|
105
|
+
updateARIAStates();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
observer.observe(wrapper, {
|
|
109
|
+
attributes: true,
|
|
110
|
+
attributeFilter: ['class']
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Initialize monitoring
|
|
115
|
+
monitorDropdownState();
|
|
137
116
|
|
|
138
|
-
|
|
139
|
-
e.preventDefault();
|
|
140
|
-
if (document.activeElement === toggle) {
|
|
141
|
-
currentMenuItemIndex = 0;
|
|
142
|
-
menuItems[currentMenuItemIndex].focus();
|
|
143
|
-
} else {
|
|
144
|
-
if (currentMenuItemIndex === menuItems.length - 1) {
|
|
145
|
-
const nextElement =
|
|
146
|
-
wrapper.nextElementSibling &&
|
|
147
|
-
wrapper.nextElementSibling.querySelector("a, button");
|
|
148
|
-
if (nextElement) {
|
|
149
|
-
closeDropdown();
|
|
150
|
-
nextElement.focus();
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
if (currentMenuItemIndex < menuItems.length - 1) {
|
|
155
|
-
currentMenuItemIndex = currentMenuItemIndex + 1;
|
|
156
|
-
menuItems[currentMenuItemIndex].focus();
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
} else if (e.key === "ArrowUp") {
|
|
160
|
-
e.preventDefault();
|
|
161
|
-
if (document.activeElement === toggle) {
|
|
162
|
-
currentMenuItemIndex = menuItems.length - 1;
|
|
163
|
-
menuItems[currentMenuItemIndex].focus();
|
|
164
|
-
} else {
|
|
165
|
-
if (currentMenuItemIndex === 0) {
|
|
166
|
-
const prevElement =
|
|
167
|
-
wrapper.previousElementSibling &&
|
|
168
|
-
wrapper.previousElementSibling.querySelector("a, button");
|
|
169
|
-
if (prevElement) {
|
|
170
|
-
closeDropdown();
|
|
171
|
-
prevElement.focus();
|
|
172
|
-
return;
|
|
173
|
-
} else {
|
|
174
|
-
closeDropdown();
|
|
175
|
-
toggle.focus();
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
if (currentMenuItemIndex > 0) {
|
|
180
|
-
currentMenuItemIndex = currentMenuItemIndex - 1;
|
|
181
|
-
menuItems[currentMenuItemIndex].focus();
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
} else if (e.key === "Tab") {
|
|
185
|
-
if (e.shiftKey) {
|
|
186
|
-
if (document.activeElement === menuItems[0]) {
|
|
187
|
-
e.preventDefault();
|
|
188
|
-
closeDropdown();
|
|
189
|
-
toggle.focus();
|
|
190
|
-
}
|
|
191
|
-
} else {
|
|
192
|
-
if (document.activeElement === menuItems[menuItems.length - 1]) {
|
|
193
|
-
e.preventDefault();
|
|
194
|
-
const nextElement =
|
|
195
|
-
wrapper.nextElementSibling &&
|
|
196
|
-
wrapper.nextElementSibling.querySelector("a, button");
|
|
197
|
-
closeDropdown();
|
|
198
|
-
if (nextElement) {
|
|
199
|
-
nextElement.focus();
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
} else if (e.key === "Escape") {
|
|
204
|
-
e.preventDefault();
|
|
205
|
-
closeDropdown();
|
|
206
|
-
toggle.focus();
|
|
207
|
-
} else if (e.key === "Home") {
|
|
208
|
-
e.preventDefault();
|
|
209
|
-
currentMenuItemIndex = 0;
|
|
210
|
-
menuItems[0].focus();
|
|
211
|
-
} else if (e.key === "End") {
|
|
212
|
-
e.preventDefault();
|
|
213
|
-
currentMenuItemIndex = menuItems.length - 1;
|
|
214
|
-
menuItems[menuItems.length - 1].focus();
|
|
215
|
-
} else if (e.key === " ") {
|
|
216
|
-
e.preventDefault();
|
|
217
|
-
}
|
|
218
|
-
});
|
|
117
|
+
// Hover interactions now handled entirely by native Webflow
|
|
219
118
|
|
|
119
|
+
// Add keyboard interactions that trigger programmatic mouse events
|
|
220
120
|
toggle.addEventListener("keydown", function (e) {
|
|
221
121
|
if (e.key === "ArrowDown") {
|
|
222
122
|
e.preventDefault();
|
|
223
|
-
|
|
224
|
-
if (
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
123
|
+
|
|
124
|
+
if (!isDropdownOpen()) {
|
|
125
|
+
// Trigger programmatic mouseenter to open dropdown
|
|
126
|
+
const mouseEnterEvent = new MouseEvent("mouseenter", {
|
|
127
|
+
bubbles: false,
|
|
128
|
+
cancelable: false,
|
|
129
|
+
view: window,
|
|
130
|
+
relatedTarget: null
|
|
131
|
+
});
|
|
132
|
+
wrapper.dispatchEvent(mouseEnterEvent);
|
|
133
|
+
|
|
134
|
+
// Focus first menu item after a brief delay
|
|
135
|
+
if (menuItems.length > 0) {
|
|
136
|
+
setTimeout(() => {
|
|
137
|
+
currentMenuItemIndex = 0;
|
|
138
|
+
menuItems[0].focus();
|
|
139
|
+
}, 100);
|
|
140
|
+
}
|
|
229
141
|
}
|
|
230
142
|
} else if (e.key === " ") {
|
|
231
143
|
e.preventDefault();
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
144
|
+
|
|
145
|
+
if (!isDropdownOpen()) {
|
|
146
|
+
// Trigger programmatic mouseenter to open dropdown
|
|
147
|
+
const mouseEnterEvent = new MouseEvent("mouseenter", {
|
|
148
|
+
bubbles: false,
|
|
149
|
+
cancelable: false,
|
|
150
|
+
view: window,
|
|
151
|
+
relatedTarget: null
|
|
152
|
+
});
|
|
153
|
+
wrapper.dispatchEvent(mouseEnterEvent);
|
|
154
|
+
|
|
155
|
+
// Focus first menu item after a brief delay
|
|
236
156
|
if (menuItems.length > 0) {
|
|
237
|
-
|
|
238
|
-
|
|
157
|
+
setTimeout(() => {
|
|
158
|
+
currentMenuItemIndex = 0;
|
|
159
|
+
menuItems[0].focus();
|
|
160
|
+
}, 100);
|
|
239
161
|
}
|
|
162
|
+
} else {
|
|
163
|
+
// Trigger mouse leave to close dropdown
|
|
164
|
+
const mouseOutEvent = new MouseEvent("mouseout", {
|
|
165
|
+
bubbles: true,
|
|
166
|
+
cancelable: false,
|
|
167
|
+
view: window,
|
|
168
|
+
relatedTarget: document.body
|
|
169
|
+
});
|
|
170
|
+
wrapper.dispatchEvent(mouseOutEvent);
|
|
171
|
+
|
|
172
|
+
const mouseLeaveEvent = new MouseEvent("mouseleave", {
|
|
173
|
+
bubbles: false,
|
|
174
|
+
cancelable: false,
|
|
175
|
+
view: window,
|
|
176
|
+
relatedTarget: document.body
|
|
177
|
+
});
|
|
178
|
+
wrapper.dispatchEvent(mouseLeaveEvent);
|
|
240
179
|
}
|
|
241
180
|
} else if (e.key === "ArrowUp") {
|
|
242
181
|
e.preventDefault();
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
182
|
+
|
|
183
|
+
if (!isDropdownOpen()) {
|
|
184
|
+
// Trigger programmatic mouseenter to open dropdown
|
|
185
|
+
const mouseEnterEvent = new MouseEvent("mouseenter", {
|
|
186
|
+
bubbles: false,
|
|
187
|
+
cancelable: false,
|
|
188
|
+
view: window,
|
|
189
|
+
relatedTarget: null
|
|
190
|
+
});
|
|
191
|
+
wrapper.dispatchEvent(mouseEnterEvent);
|
|
192
|
+
|
|
193
|
+
// Focus last menu item after a brief delay
|
|
248
194
|
if (menuItems.length > 0) {
|
|
249
195
|
setTimeout(() => {
|
|
250
196
|
currentMenuItemIndex = menuItems.length - 1;
|
|
@@ -254,21 +200,99 @@ function setupDynamicDropdowns() {
|
|
|
254
200
|
}
|
|
255
201
|
} else if (e.key === "Escape") {
|
|
256
202
|
e.preventDefault();
|
|
257
|
-
|
|
203
|
+
|
|
204
|
+
if (isDropdownOpen()) {
|
|
205
|
+
// Trigger mouse leave to close dropdown
|
|
206
|
+
const mouseOutEvent = new MouseEvent("mouseout", {
|
|
207
|
+
bubbles: true,
|
|
208
|
+
cancelable: false,
|
|
209
|
+
view: window,
|
|
210
|
+
relatedTarget: document.body
|
|
211
|
+
});
|
|
212
|
+
wrapper.dispatchEvent(mouseOutEvent);
|
|
213
|
+
|
|
214
|
+
const mouseLeaveEvent = new MouseEvent("mouseleave", {
|
|
215
|
+
bubbles: false,
|
|
216
|
+
cancelable: false,
|
|
217
|
+
view: window,
|
|
218
|
+
relatedTarget: document.body
|
|
219
|
+
});
|
|
220
|
+
wrapper.dispatchEvent(mouseLeaveEvent);
|
|
221
|
+
}
|
|
258
222
|
}
|
|
259
223
|
});
|
|
260
224
|
|
|
225
|
+
// Handle navigation within open dropdown
|
|
226
|
+
document.addEventListener("keydown", function (e) {
|
|
227
|
+
if (!isDropdownOpen()) return;
|
|
228
|
+
if (!wrapper.contains(document.activeElement)) return;
|
|
261
229
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
230
|
+
if (e.key === "ArrowDown") {
|
|
231
|
+
e.preventDefault();
|
|
232
|
+
if (currentMenuItemIndex < menuItems.length - 1) {
|
|
233
|
+
currentMenuItemIndex++;
|
|
234
|
+
menuItems[currentMenuItemIndex].focus();
|
|
235
|
+
}
|
|
236
|
+
} else if (e.key === "ArrowUp") {
|
|
237
|
+
e.preventDefault();
|
|
238
|
+
if (currentMenuItemIndex > 0) {
|
|
239
|
+
currentMenuItemIndex--;
|
|
240
|
+
menuItems[currentMenuItemIndex].focus();
|
|
241
|
+
} else if (currentMenuItemIndex === 0) {
|
|
242
|
+
// On first item, trigger mouse leave to close dropdown
|
|
243
|
+
const mouseOutEvent = new MouseEvent("mouseout", {
|
|
244
|
+
bubbles: true,
|
|
245
|
+
cancelable: false,
|
|
246
|
+
view: window,
|
|
247
|
+
relatedTarget: document.body
|
|
248
|
+
});
|
|
249
|
+
wrapper.dispatchEvent(mouseOutEvent);
|
|
250
|
+
|
|
251
|
+
const mouseLeaveEvent = new MouseEvent("mouseleave", {
|
|
252
|
+
bubbles: false,
|
|
253
|
+
cancelable: false,
|
|
254
|
+
view: window,
|
|
255
|
+
relatedTarget: document.body
|
|
256
|
+
});
|
|
257
|
+
wrapper.dispatchEvent(mouseLeaveEvent);
|
|
258
|
+
|
|
259
|
+
// Focus back to toggle
|
|
260
|
+
toggle.focus();
|
|
261
|
+
currentMenuItemIndex = -1;
|
|
262
|
+
} else {
|
|
263
|
+
// Go back to toggle
|
|
264
|
+
toggle.focus();
|
|
265
|
+
currentMenuItemIndex = -1;
|
|
266
|
+
}
|
|
267
|
+
} else if (e.key === "Escape") {
|
|
268
|
+
e.preventDefault();
|
|
269
|
+
|
|
270
|
+
// Trigger mouse leave to close dropdown
|
|
271
|
+
const mouseOutEvent = new MouseEvent("mouseout", {
|
|
272
|
+
bubbles: true,
|
|
273
|
+
cancelable: false,
|
|
274
|
+
view: window,
|
|
275
|
+
relatedTarget: document.body
|
|
276
|
+
});
|
|
277
|
+
wrapper.dispatchEvent(mouseOutEvent);
|
|
278
|
+
|
|
279
|
+
const mouseLeaveEvent = new MouseEvent("mouseleave", {
|
|
280
|
+
bubbles: false,
|
|
281
|
+
cancelable: false,
|
|
282
|
+
view: window,
|
|
283
|
+
relatedTarget: document.body
|
|
284
|
+
});
|
|
285
|
+
wrapper.dispatchEvent(mouseLeaveEvent);
|
|
265
286
|
}
|
|
266
287
|
});
|
|
267
288
|
|
|
268
289
|
allDropdowns.push({
|
|
269
290
|
wrapper,
|
|
270
|
-
isOpen:
|
|
271
|
-
closeDropdown
|
|
291
|
+
isOpen: isDropdownOpen,
|
|
292
|
+
closeDropdown: () => {
|
|
293
|
+
// closeDropdown now handled by native Webflow interactions
|
|
294
|
+
// This is kept for API compatibility but does nothing
|
|
295
|
+
},
|
|
272
296
|
toggle,
|
|
273
297
|
dropdownList,
|
|
274
298
|
});
|
package/index.js
CHANGED