@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.
@@ -68,177 +68,129 @@ function setupDynamicDropdowns() {
68
68
  }
69
69
  });
70
70
 
71
- let isOpen = false;
72
71
  let currentMenuItemIndex = -1;
73
72
 
74
- function openDropdown() {
75
- if (isOpen) return;
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
- // Trigger Webflow interaction
87
- const wfIx = Webflow.require("ix3");
88
- wfIx.emit("navbar_1_dropdown");
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
- function closeDropdown() {
92
- if (!isOpen) return;
93
- const shouldRestoreFocus = dropdownList.contains(document.activeElement);
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
- // Force screen reader virtual cursor refresh by briefly focusing dropdown container then restoring
105
- if (shouldRestoreFocus) {
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
- openDropdown();
119
- }
120
- });
121
-
122
- wrapper.addEventListener("mouseleave", () => {
123
- if (isOpen) {
124
- closeDropdown();
98
+ currentMenuItemIndex = -1;
125
99
  }
126
- });
100
+ }
127
101
 
128
- document.addEventListener("keydown", function (e) {
129
- if (!isOpen) return;
130
- if (!wrapper.contains(document.activeElement)) return;
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
- if (e.key === "ArrowDown") {
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
- openDropdown();
218
- if (menuItems.length > 0) {
219
- setTimeout(() => {
220
- currentMenuItemIndex = 0;
221
- menuItems[0].focus();
222
- }, 100);
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
- if (isOpen) {
227
- closeDropdown();
228
- } else {
229
- openDropdown();
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
- currentMenuItemIndex = 0;
232
- menuItems[0].focus();
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
- if (isOpen) {
238
- currentMenuItemIndex = menuItems.length - 1;
239
- menuItems[currentMenuItemIndex].focus();
240
- } else {
241
- openDropdown();
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
- closeDropdown();
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
- document.addEventListener("click", function (e) {
257
- if (!wrapper.contains(e.target) && isOpen) {
258
- closeDropdown();
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: () => 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
@@ -1,5 +1,4 @@
1
- // Version:1.6.4
2
-
1
+ // Version:1.6.6
3
2
  const API_NAME = "hsmain";
4
3
 
5
4
  const initializeHsMain = async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hortonstudio/main",
3
- "version": "1.6.4",
3
+ "version": "1.6.6",
4
4
  "description": "Animation and utility library for client websites",
5
5
  "main": "index.js",
6
6
  "type": "module",