@hortonstudio/main 1.6.4 → 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 +182 -152
- package/index.js +1 -2
- package/package.json +1 -1
package/autoInit/navbar.js
CHANGED
|
@@ -68,177 +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
|
-
|
|
76
|
+
// Check if dropdown is open by looking for is-active class on wrapper
|
|
77
|
+
function isDropdownOpen() {
|
|
78
|
+
return wrapper.classList.contains('is-active');
|
|
89
79
|
}
|
|
90
80
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
// Update ARIA states FIRST to help screen readers understand content is hidden
|
|
96
|
-
isOpen = false;
|
|
97
|
-
currentMenuItemIndex = -1;
|
|
98
|
-
toggle.setAttribute("aria-expanded", "false");
|
|
99
|
-
dropdownList.setAttribute("aria-hidden", "true");
|
|
100
|
-
menuItems.forEach((item) => {
|
|
101
|
-
item.setAttribute("tabindex", "-1");
|
|
102
|
-
});
|
|
81
|
+
// Update ARIA states based on current visual state
|
|
82
|
+
function updateARIAStates() {
|
|
83
|
+
const isOpen = isDropdownOpen();
|
|
84
|
+
const wasOpen = toggle.getAttribute("aria-expanded") === "true";
|
|
103
85
|
|
|
104
|
-
//
|
|
105
|
-
if (
|
|
106
|
-
// This helps reset virtual cursor position
|
|
107
|
-
dropdownList.focus();
|
|
86
|
+
// If dropdown is closing (was open, now closed), focus the toggle first
|
|
87
|
+
if (wasOpen && !isOpen && dropdownList.contains(document.activeElement)) {
|
|
108
88
|
toggle.focus();
|
|
109
89
|
}
|
|
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");
|
|
95
|
+
});
|
|
110
96
|
|
|
111
|
-
// Trigger Webflow interaction (reverse)
|
|
112
|
-
const wfIx = Webflow.require("ix3");
|
|
113
|
-
wfIx.emit("navbar_1_dropdown");
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
wrapper.addEventListener("mouseenter", () => {
|
|
117
97
|
if (!isOpen) {
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
wrapper.addEventListener("mouseleave", () => {
|
|
123
|
-
if (isOpen) {
|
|
124
|
-
closeDropdown();
|
|
98
|
+
currentMenuItemIndex = -1;
|
|
125
99
|
}
|
|
126
|
-
}
|
|
100
|
+
}
|
|
127
101
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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();
|
|
131
116
|
|
|
132
|
-
|
|
133
|
-
e.preventDefault();
|
|
134
|
-
if (document.activeElement === toggle) {
|
|
135
|
-
currentMenuItemIndex = 0;
|
|
136
|
-
menuItems[currentMenuItemIndex].focus();
|
|
137
|
-
} else {
|
|
138
|
-
if (currentMenuItemIndex === menuItems.length - 1) {
|
|
139
|
-
const nextElement =
|
|
140
|
-
wrapper.nextElementSibling &&
|
|
141
|
-
wrapper.nextElementSibling.querySelector("a, button");
|
|
142
|
-
if (nextElement) {
|
|
143
|
-
closeDropdown();
|
|
144
|
-
nextElement.focus();
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
if (currentMenuItemIndex < menuItems.length - 1) {
|
|
149
|
-
currentMenuItemIndex = currentMenuItemIndex + 1;
|
|
150
|
-
menuItems[currentMenuItemIndex].focus();
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
} else if (e.key === "ArrowUp") {
|
|
154
|
-
e.preventDefault();
|
|
155
|
-
if (document.activeElement === toggle) {
|
|
156
|
-
currentMenuItemIndex = menuItems.length - 1;
|
|
157
|
-
menuItems[currentMenuItemIndex].focus();
|
|
158
|
-
} else {
|
|
159
|
-
if (currentMenuItemIndex === 0) {
|
|
160
|
-
const prevElement =
|
|
161
|
-
wrapper.previousElementSibling &&
|
|
162
|
-
wrapper.previousElementSibling.querySelector("a, button");
|
|
163
|
-
if (prevElement) {
|
|
164
|
-
closeDropdown();
|
|
165
|
-
prevElement.focus();
|
|
166
|
-
return;
|
|
167
|
-
} else {
|
|
168
|
-
closeDropdown();
|
|
169
|
-
toggle.focus();
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
if (currentMenuItemIndex > 0) {
|
|
174
|
-
currentMenuItemIndex = currentMenuItemIndex - 1;
|
|
175
|
-
menuItems[currentMenuItemIndex].focus();
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
} else if (e.key === "Tab") {
|
|
179
|
-
if (e.shiftKey) {
|
|
180
|
-
if (document.activeElement === menuItems[0]) {
|
|
181
|
-
e.preventDefault();
|
|
182
|
-
closeDropdown();
|
|
183
|
-
toggle.focus();
|
|
184
|
-
}
|
|
185
|
-
} else {
|
|
186
|
-
if (document.activeElement === menuItems[menuItems.length - 1]) {
|
|
187
|
-
e.preventDefault();
|
|
188
|
-
const nextElement =
|
|
189
|
-
wrapper.nextElementSibling &&
|
|
190
|
-
wrapper.nextElementSibling.querySelector("a, button");
|
|
191
|
-
closeDropdown();
|
|
192
|
-
if (nextElement) {
|
|
193
|
-
nextElement.focus();
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
} else if (e.key === "Escape") {
|
|
198
|
-
e.preventDefault();
|
|
199
|
-
closeDropdown();
|
|
200
|
-
toggle.focus();
|
|
201
|
-
} else if (e.key === "Home") {
|
|
202
|
-
e.preventDefault();
|
|
203
|
-
currentMenuItemIndex = 0;
|
|
204
|
-
menuItems[0].focus();
|
|
205
|
-
} else if (e.key === "End") {
|
|
206
|
-
e.preventDefault();
|
|
207
|
-
currentMenuItemIndex = menuItems.length - 1;
|
|
208
|
-
menuItems[menuItems.length - 1].focus();
|
|
209
|
-
} else if (e.key === " ") {
|
|
210
|
-
e.preventDefault();
|
|
211
|
-
}
|
|
212
|
-
});
|
|
117
|
+
// Hover interactions now handled entirely by native Webflow
|
|
213
118
|
|
|
119
|
+
// Add keyboard interactions that trigger programmatic mouse events
|
|
214
120
|
toggle.addEventListener("keydown", function (e) {
|
|
215
121
|
if (e.key === "ArrowDown") {
|
|
216
122
|
e.preventDefault();
|
|
217
|
-
|
|
218
|
-
if (
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
+
}
|
|
223
141
|
}
|
|
224
142
|
} else if (e.key === " ") {
|
|
225
143
|
e.preventDefault();
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
|
230
156
|
if (menuItems.length > 0) {
|
|
231
|
-
|
|
232
|
-
|
|
157
|
+
setTimeout(() => {
|
|
158
|
+
currentMenuItemIndex = 0;
|
|
159
|
+
menuItems[0].focus();
|
|
160
|
+
}, 100);
|
|
233
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);
|
|
234
179
|
}
|
|
235
180
|
} else if (e.key === "ArrowUp") {
|
|
236
181
|
e.preventDefault();
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
|
242
194
|
if (menuItems.length > 0) {
|
|
243
195
|
setTimeout(() => {
|
|
244
196
|
currentMenuItemIndex = menuItems.length - 1;
|
|
@@ -248,21 +200,99 @@ function setupDynamicDropdowns() {
|
|
|
248
200
|
}
|
|
249
201
|
} else if (e.key === "Escape") {
|
|
250
202
|
e.preventDefault();
|
|
251
|
-
|
|
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
|
+
}
|
|
252
222
|
}
|
|
253
223
|
});
|
|
254
224
|
|
|
225
|
+
// Handle navigation within open dropdown
|
|
226
|
+
document.addEventListener("keydown", function (e) {
|
|
227
|
+
if (!isDropdownOpen()) return;
|
|
228
|
+
if (!wrapper.contains(document.activeElement)) return;
|
|
255
229
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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);
|
|
259
286
|
}
|
|
260
287
|
});
|
|
261
288
|
|
|
262
289
|
allDropdowns.push({
|
|
263
290
|
wrapper,
|
|
264
|
-
isOpen:
|
|
265
|
-
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
|
+
},
|
|
266
296
|
toggle,
|
|
267
297
|
dropdownList,
|
|
268
298
|
});
|
package/index.js
CHANGED