@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.
@@ -68,183 +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
- const clickEvent = new MouseEvent("click", {
87
- bubbles: true,
88
- cancelable: true,
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
- function closeDropdown() {
95
- if (!isOpen) return;
96
- const shouldRestoreFocus = dropdownList.contains(document.activeElement);
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
- // Force screen reader virtual cursor refresh by briefly focusing dropdown container then restoring
108
- if (shouldRestoreFocus) {
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
- const clickEvent = new MouseEvent("click", {
115
- bubbles: true,
116
- cancelable: true,
117
- view: window,
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
- openDropdown();
125
- }
126
- });
127
-
128
- wrapper.addEventListener("mouseleave", () => {
129
- if (isOpen) {
130
- closeDropdown();
98
+ currentMenuItemIndex = -1;
131
99
  }
132
- });
100
+ }
133
101
 
134
- document.addEventListener("keydown", function (e) {
135
- if (!isOpen) return;
136
- 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();
137
116
 
138
- if (e.key === "ArrowDown") {
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
- openDropdown();
224
- if (menuItems.length > 0) {
225
- setTimeout(() => {
226
- currentMenuItemIndex = 0;
227
- menuItems[0].focus();
228
- }, 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
+ }
229
141
  }
230
142
  } else if (e.key === " ") {
231
143
  e.preventDefault();
232
- if (isOpen) {
233
- closeDropdown();
234
- } else {
235
- 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
236
156
  if (menuItems.length > 0) {
237
- currentMenuItemIndex = 0;
238
- menuItems[0].focus();
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
- if (isOpen) {
244
- currentMenuItemIndex = menuItems.length - 1;
245
- menuItems[currentMenuItemIndex].focus();
246
- } else {
247
- 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
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
- 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
+ }
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
- document.addEventListener("click", function (e) {
263
- if (!wrapper.contains(e.target) && isOpen) {
264
- 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);
265
286
  }
266
287
  });
267
288
 
268
289
  allDropdowns.push({
269
290
  wrapper,
270
- isOpen: () => 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
@@ -1,4 +1,4 @@
1
- // Version:1.6.5
1
+ // Version:1.6.6
2
2
  const API_NAME = "hsmain";
3
3
 
4
4
  const initializeHsMain = async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hortonstudio/main",
3
- "version": "1.6.5",
3
+ "version": "1.6.6",
4
4
  "description": "Animation and utility library for client websites",
5
5
  "main": "index.js",
6
6
  "type": "module",