@hortonstudio/main 1.6.5 → 1.6.7
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/form.js +58 -0
- package/autoInit/navbar.js +187 -167
- package/index.js +3 -1
- package/package.json +1 -1
- package/utils/slider.js +95 -0
package/autoInit/form.js
CHANGED
|
@@ -571,6 +571,12 @@ export function init() {
|
|
|
571
571
|
return isValid;
|
|
572
572
|
};
|
|
573
573
|
|
|
574
|
+
// Helper function to parse comma-separated config values
|
|
575
|
+
const parseFormConfig = (configString) => {
|
|
576
|
+
if (!configString) return [];
|
|
577
|
+
return configString.split(',').map(config => config.trim());
|
|
578
|
+
};
|
|
579
|
+
|
|
574
580
|
const handleFormSubmit = (event) => {
|
|
575
581
|
const form = event.target;
|
|
576
582
|
|
|
@@ -596,12 +602,63 @@ export function init() {
|
|
|
596
602
|
removeError(input);
|
|
597
603
|
});
|
|
598
604
|
|
|
605
|
+
// Handle text replacement if this form has replace fields
|
|
606
|
+
const replaceFieldElements = form.querySelectorAll('input[data-hs-form^="replace-field-"], textarea[data-hs-form^="replace-field-"], select[data-hs-form^="replace-field-"]');
|
|
607
|
+
if (replaceFieldElements.length > 0) {
|
|
608
|
+
replaceFieldElements.forEach(field => {
|
|
609
|
+
const dataHsForm = field.getAttribute('data-hs-form');
|
|
610
|
+
const suffix = dataHsForm.replace('replace-field-', '');
|
|
611
|
+
const value = field.value;
|
|
612
|
+
|
|
613
|
+
// Find all matching text elements
|
|
614
|
+
const textElements = document.querySelectorAll(`[data-hs-form="replace-text-${suffix}"]`);
|
|
615
|
+
|
|
616
|
+
textElements.forEach(element => {
|
|
617
|
+
element.textContent = value;
|
|
618
|
+
});
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Handle form configuration
|
|
623
|
+
const formWrapper = form.closest('[data-hs-form="wrapper"]');
|
|
624
|
+
let shouldPreventSubmit = false;
|
|
625
|
+
|
|
626
|
+
if (formWrapper && formWrapper.hasAttribute('data-hs-config')) {
|
|
627
|
+
const configString = formWrapper.getAttribute('data-hs-config');
|
|
628
|
+
const configs = parseFormConfig(configString);
|
|
629
|
+
|
|
630
|
+
// Check for prevent-submit config
|
|
631
|
+
if (configs.includes('prevent-submit')) {
|
|
632
|
+
shouldPreventSubmit = true;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Check for click-trigger configs
|
|
636
|
+
configs.forEach(config => {
|
|
637
|
+
if (config.startsWith('click-trigger-')) {
|
|
638
|
+
const trigger = document.querySelector(`[data-hs-form="trigger"][data-hs-config*="${config}"]`);
|
|
639
|
+
if (trigger) {
|
|
640
|
+
setTimeout(() => {
|
|
641
|
+
trigger.click();
|
|
642
|
+
}, 100);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
|
|
599
648
|
// Trigger final animation if it exists
|
|
600
649
|
const finalAnimElement = form.querySelector(config.selectors.finalAnim);
|
|
601
650
|
if (finalAnimElement) {
|
|
602
651
|
finalAnimElement.click();
|
|
603
652
|
}
|
|
604
653
|
|
|
654
|
+
// Prevent submission if configured to do so
|
|
655
|
+
if (shouldPreventSubmit) {
|
|
656
|
+
event.preventDefault();
|
|
657
|
+
event.stopPropagation();
|
|
658
|
+
event.stopImmediatePropagation();
|
|
659
|
+
return false;
|
|
660
|
+
}
|
|
661
|
+
|
|
605
662
|
// Don't prevent default - let the form submit naturally with its action/method
|
|
606
663
|
}
|
|
607
664
|
};
|
|
@@ -739,6 +796,7 @@ export function init() {
|
|
|
739
796
|
window.removeEventListener('resize', handleResize);
|
|
740
797
|
};
|
|
741
798
|
|
|
799
|
+
|
|
742
800
|
// Initialize the form validation system
|
|
743
801
|
const initializeFormValidation = () => {
|
|
744
802
|
try {
|
package/autoInit/navbar.js
CHANGED
|
@@ -27,16 +27,12 @@ function setupDynamicDropdowns() {
|
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
dropdownWrappers.forEach((wrapper) => {
|
|
30
|
-
const toggle = wrapper.querySelector("
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (links.length >= 2 && !element.contains(toggle)) {
|
|
37
|
-
dropdownList = element;
|
|
38
|
-
break;
|
|
39
|
-
}
|
|
30
|
+
const toggle = wrapper.querySelector('[data-hs-nav="dropdown-toggle"]');
|
|
31
|
+
const dropdownList = wrapper.querySelector('[data-hs-nav="dropdown-list"]');
|
|
32
|
+
|
|
33
|
+
if (!toggle || !dropdownList) {
|
|
34
|
+
console.warn("Dropdown wrapper missing required elements:", wrapper);
|
|
35
|
+
return;
|
|
40
36
|
}
|
|
41
37
|
|
|
42
38
|
const toggleText = toggle.textContent?.trim() || "dropdown";
|
|
@@ -68,183 +64,129 @@ function setupDynamicDropdowns() {
|
|
|
68
64
|
}
|
|
69
65
|
});
|
|
70
66
|
|
|
71
|
-
let isOpen = false;
|
|
72
67
|
let currentMenuItemIndex = -1;
|
|
73
68
|
|
|
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
|
-
});
|
|
69
|
+
// Set initial ARIA states
|
|
70
|
+
updateARIAStates();
|
|
85
71
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
view: window,
|
|
90
|
-
});
|
|
91
|
-
wrapper.dispatchEvent(clickEvent);
|
|
72
|
+
// Check if dropdown is open by looking for is-active class on wrapper
|
|
73
|
+
function isDropdownOpen() {
|
|
74
|
+
return wrapper.classList.contains('is-active');
|
|
92
75
|
}
|
|
93
76
|
|
|
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
|
-
});
|
|
77
|
+
// Update ARIA states based on current visual state
|
|
78
|
+
function updateARIAStates() {
|
|
79
|
+
const isOpen = isDropdownOpen();
|
|
80
|
+
const wasOpen = toggle.getAttribute("aria-expanded") === "true";
|
|
106
81
|
|
|
107
|
-
//
|
|
108
|
-
if (
|
|
109
|
-
// This helps reset virtual cursor position
|
|
110
|
-
dropdownList.focus();
|
|
82
|
+
// If dropdown is closing (was open, now closed), focus the toggle first
|
|
83
|
+
if (wasOpen && !isOpen && dropdownList.contains(document.activeElement)) {
|
|
111
84
|
toggle.focus();
|
|
112
85
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
86
|
+
|
|
87
|
+
toggle.setAttribute("aria-expanded", isOpen ? "true" : "false");
|
|
88
|
+
dropdownList.setAttribute("aria-hidden", isOpen ? "false" : "true");
|
|
89
|
+
menuItems.forEach((item) => {
|
|
90
|
+
item.setAttribute("tabindex", isOpen ? "0" : "-1");
|
|
118
91
|
});
|
|
119
|
-
wrapper.dispatchEvent(clickEvent);
|
|
120
|
-
}
|
|
121
92
|
|
|
122
|
-
wrapper.addEventListener("mouseenter", () => {
|
|
123
93
|
if (!isOpen) {
|
|
124
|
-
|
|
94
|
+
currentMenuItemIndex = -1;
|
|
125
95
|
}
|
|
126
|
-
}
|
|
96
|
+
}
|
|
127
97
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
98
|
+
// Monitor for class changes and update ARIA states
|
|
99
|
+
function monitorDropdownState() {
|
|
100
|
+
const observer = new MutationObserver(() => {
|
|
101
|
+
updateARIAStates();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
observer.observe(wrapper, {
|
|
105
|
+
attributes: true,
|
|
106
|
+
attributeFilter: ['class']
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Initialize monitoring
|
|
111
|
+
monitorDropdownState();
|
|
133
112
|
|
|
134
|
-
|
|
135
|
-
if (!isOpen) return;
|
|
136
|
-
if (!wrapper.contains(document.activeElement)) return;
|
|
137
|
-
|
|
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
|
-
});
|
|
113
|
+
// Hover interactions now handled entirely by native Webflow
|
|
219
114
|
|
|
115
|
+
// Add keyboard interactions that trigger programmatic mouse events
|
|
220
116
|
toggle.addEventListener("keydown", function (e) {
|
|
221
117
|
if (e.key === "ArrowDown") {
|
|
222
118
|
e.preventDefault();
|
|
223
|
-
|
|
224
|
-
if (
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
119
|
+
|
|
120
|
+
if (!isDropdownOpen()) {
|
|
121
|
+
// Trigger programmatic mouseenter to open dropdown
|
|
122
|
+
const mouseEnterEvent = new MouseEvent("mouseenter", {
|
|
123
|
+
bubbles: false,
|
|
124
|
+
cancelable: false,
|
|
125
|
+
view: window,
|
|
126
|
+
relatedTarget: null
|
|
127
|
+
});
|
|
128
|
+
wrapper.dispatchEvent(mouseEnterEvent);
|
|
129
|
+
|
|
130
|
+
// Focus first menu item after a brief delay
|
|
131
|
+
if (menuItems.length > 0) {
|
|
132
|
+
setTimeout(() => {
|
|
133
|
+
currentMenuItemIndex = 0;
|
|
134
|
+
menuItems[0].focus();
|
|
135
|
+
}, 100);
|
|
136
|
+
}
|
|
229
137
|
}
|
|
230
138
|
} else if (e.key === " ") {
|
|
231
139
|
e.preventDefault();
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
140
|
+
|
|
141
|
+
if (!isDropdownOpen()) {
|
|
142
|
+
// Trigger programmatic mouseenter to open dropdown
|
|
143
|
+
const mouseEnterEvent = new MouseEvent("mouseenter", {
|
|
144
|
+
bubbles: false,
|
|
145
|
+
cancelable: false,
|
|
146
|
+
view: window,
|
|
147
|
+
relatedTarget: null
|
|
148
|
+
});
|
|
149
|
+
wrapper.dispatchEvent(mouseEnterEvent);
|
|
150
|
+
|
|
151
|
+
// Focus first menu item after a brief delay
|
|
236
152
|
if (menuItems.length > 0) {
|
|
237
|
-
|
|
238
|
-
|
|
153
|
+
setTimeout(() => {
|
|
154
|
+
currentMenuItemIndex = 0;
|
|
155
|
+
menuItems[0].focus();
|
|
156
|
+
}, 100);
|
|
239
157
|
}
|
|
158
|
+
} else {
|
|
159
|
+
// Trigger mouse leave to close dropdown
|
|
160
|
+
const mouseOutEvent = new MouseEvent("mouseout", {
|
|
161
|
+
bubbles: true,
|
|
162
|
+
cancelable: false,
|
|
163
|
+
view: window,
|
|
164
|
+
relatedTarget: document.body
|
|
165
|
+
});
|
|
166
|
+
wrapper.dispatchEvent(mouseOutEvent);
|
|
167
|
+
|
|
168
|
+
const mouseLeaveEvent = new MouseEvent("mouseleave", {
|
|
169
|
+
bubbles: false,
|
|
170
|
+
cancelable: false,
|
|
171
|
+
view: window,
|
|
172
|
+
relatedTarget: document.body
|
|
173
|
+
});
|
|
174
|
+
wrapper.dispatchEvent(mouseLeaveEvent);
|
|
240
175
|
}
|
|
241
176
|
} else if (e.key === "ArrowUp") {
|
|
242
177
|
e.preventDefault();
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
178
|
+
|
|
179
|
+
if (!isDropdownOpen()) {
|
|
180
|
+
// Trigger programmatic mouseenter to open dropdown
|
|
181
|
+
const mouseEnterEvent = new MouseEvent("mouseenter", {
|
|
182
|
+
bubbles: false,
|
|
183
|
+
cancelable: false,
|
|
184
|
+
view: window,
|
|
185
|
+
relatedTarget: null
|
|
186
|
+
});
|
|
187
|
+
wrapper.dispatchEvent(mouseEnterEvent);
|
|
188
|
+
|
|
189
|
+
// Focus last menu item after a brief delay
|
|
248
190
|
if (menuItems.length > 0) {
|
|
249
191
|
setTimeout(() => {
|
|
250
192
|
currentMenuItemIndex = menuItems.length - 1;
|
|
@@ -254,21 +196,99 @@ function setupDynamicDropdowns() {
|
|
|
254
196
|
}
|
|
255
197
|
} else if (e.key === "Escape") {
|
|
256
198
|
e.preventDefault();
|
|
257
|
-
|
|
199
|
+
|
|
200
|
+
if (isDropdownOpen()) {
|
|
201
|
+
// Trigger mouse leave to close dropdown
|
|
202
|
+
const mouseOutEvent = new MouseEvent("mouseout", {
|
|
203
|
+
bubbles: true,
|
|
204
|
+
cancelable: false,
|
|
205
|
+
view: window,
|
|
206
|
+
relatedTarget: document.body
|
|
207
|
+
});
|
|
208
|
+
wrapper.dispatchEvent(mouseOutEvent);
|
|
209
|
+
|
|
210
|
+
const mouseLeaveEvent = new MouseEvent("mouseleave", {
|
|
211
|
+
bubbles: false,
|
|
212
|
+
cancelable: false,
|
|
213
|
+
view: window,
|
|
214
|
+
relatedTarget: document.body
|
|
215
|
+
});
|
|
216
|
+
wrapper.dispatchEvent(mouseLeaveEvent);
|
|
217
|
+
}
|
|
258
218
|
}
|
|
259
219
|
});
|
|
260
220
|
|
|
221
|
+
// Handle navigation within open dropdown
|
|
222
|
+
document.addEventListener("keydown", function (e) {
|
|
223
|
+
if (!isDropdownOpen()) return;
|
|
224
|
+
if (!wrapper.contains(document.activeElement)) return;
|
|
261
225
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
226
|
+
if (e.key === "ArrowDown") {
|
|
227
|
+
e.preventDefault();
|
|
228
|
+
if (currentMenuItemIndex < menuItems.length - 1) {
|
|
229
|
+
currentMenuItemIndex++;
|
|
230
|
+
menuItems[currentMenuItemIndex].focus();
|
|
231
|
+
}
|
|
232
|
+
} else if (e.key === "ArrowUp") {
|
|
233
|
+
e.preventDefault();
|
|
234
|
+
if (currentMenuItemIndex > 0) {
|
|
235
|
+
currentMenuItemIndex--;
|
|
236
|
+
menuItems[currentMenuItemIndex].focus();
|
|
237
|
+
} else if (currentMenuItemIndex === 0) {
|
|
238
|
+
// On first item, trigger mouse leave to close dropdown
|
|
239
|
+
const mouseOutEvent = new MouseEvent("mouseout", {
|
|
240
|
+
bubbles: true,
|
|
241
|
+
cancelable: false,
|
|
242
|
+
view: window,
|
|
243
|
+
relatedTarget: document.body
|
|
244
|
+
});
|
|
245
|
+
wrapper.dispatchEvent(mouseOutEvent);
|
|
246
|
+
|
|
247
|
+
const mouseLeaveEvent = new MouseEvent("mouseleave", {
|
|
248
|
+
bubbles: false,
|
|
249
|
+
cancelable: false,
|
|
250
|
+
view: window,
|
|
251
|
+
relatedTarget: document.body
|
|
252
|
+
});
|
|
253
|
+
wrapper.dispatchEvent(mouseLeaveEvent);
|
|
254
|
+
|
|
255
|
+
// Focus back to toggle
|
|
256
|
+
toggle.focus();
|
|
257
|
+
currentMenuItemIndex = -1;
|
|
258
|
+
} else {
|
|
259
|
+
// Go back to toggle
|
|
260
|
+
toggle.focus();
|
|
261
|
+
currentMenuItemIndex = -1;
|
|
262
|
+
}
|
|
263
|
+
} else if (e.key === "Escape") {
|
|
264
|
+
e.preventDefault();
|
|
265
|
+
|
|
266
|
+
// Trigger mouse leave to close dropdown
|
|
267
|
+
const mouseOutEvent = new MouseEvent("mouseout", {
|
|
268
|
+
bubbles: true,
|
|
269
|
+
cancelable: false,
|
|
270
|
+
view: window,
|
|
271
|
+
relatedTarget: document.body
|
|
272
|
+
});
|
|
273
|
+
wrapper.dispatchEvent(mouseOutEvent);
|
|
274
|
+
|
|
275
|
+
const mouseLeaveEvent = new MouseEvent("mouseleave", {
|
|
276
|
+
bubbles: false,
|
|
277
|
+
cancelable: false,
|
|
278
|
+
view: window,
|
|
279
|
+
relatedTarget: document.body
|
|
280
|
+
});
|
|
281
|
+
wrapper.dispatchEvent(mouseLeaveEvent);
|
|
265
282
|
}
|
|
266
283
|
});
|
|
267
284
|
|
|
268
285
|
allDropdowns.push({
|
|
269
286
|
wrapper,
|
|
270
|
-
isOpen:
|
|
271
|
-
closeDropdown
|
|
287
|
+
isOpen: isDropdownOpen,
|
|
288
|
+
closeDropdown: () => {
|
|
289
|
+
// closeDropdown now handled by native Webflow interactions
|
|
290
|
+
// This is kept for API compatibility but does nothing
|
|
291
|
+
},
|
|
272
292
|
toggle,
|
|
273
293
|
dropdownList,
|
|
274
294
|
});
|
package/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Version:1.6.
|
|
1
|
+
// Version:1.6.7
|
|
2
2
|
const API_NAME = "hsmain";
|
|
3
3
|
|
|
4
4
|
const initializeHsMain = async () => {
|
|
@@ -16,6 +16,7 @@ const initializeHsMain = async () => {
|
|
|
16
16
|
|
|
17
17
|
const utilityModules = {
|
|
18
18
|
"data-hs-util-ba": true,
|
|
19
|
+
"data-hs-util-slider": true,
|
|
19
20
|
};
|
|
20
21
|
|
|
21
22
|
const autoInitModules = {
|
|
@@ -38,6 +39,7 @@ const initializeHsMain = async () => {
|
|
|
38
39
|
const moduleMap = {
|
|
39
40
|
transition: () => import("./autoInit/transition.js"),
|
|
40
41
|
"data-hs-util-ba": () => import("./utils/before-after.js"),
|
|
42
|
+
"data-hs-util-slider": () => import("./utils/slider.js"),
|
|
41
43
|
"smooth-scroll": () => import("./autoInit/smooth-scroll.js"),
|
|
42
44
|
navbar: () => import("./autoInit/navbar.js"),
|
|
43
45
|
accessibility: () => import("./autoInit/accessibility.js"),
|
package/package.json
CHANGED
package/utils/slider.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
export const init = () => {
|
|
2
|
+
// Null checks for DOM elements
|
|
3
|
+
const wrapper = document.querySelector('[data-hs-slider="wrapper"]');
|
|
4
|
+
const nextBtn = document.querySelector('[data-hs-slider="next"]');
|
|
5
|
+
const prevBtn = document.querySelector('[data-hs-slider="previous"]');
|
|
6
|
+
|
|
7
|
+
// Early return if required elements don't exist
|
|
8
|
+
if (!wrapper || !nextBtn || !prevBtn) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Validate slides exist and have length
|
|
13
|
+
const slides = wrapper.children;
|
|
14
|
+
if (!slides || slides.length === 0) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Check if gsap is available globally
|
|
19
|
+
if (typeof gsap === 'undefined') {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const totalSlides = slides.length;
|
|
24
|
+
let currentIndex = 0;
|
|
25
|
+
let isAnimating = false;
|
|
26
|
+
|
|
27
|
+
const firstClone = slides[0].cloneNode(true);
|
|
28
|
+
const lastClone = slides[slides.length - 1].cloneNode(true);
|
|
29
|
+
|
|
30
|
+
wrapper.appendChild(firstClone);
|
|
31
|
+
wrapper.insertBefore(lastClone, slides[0]);
|
|
32
|
+
|
|
33
|
+
currentIndex = 1;
|
|
34
|
+
gsap.set(wrapper, { xPercent: -100 });
|
|
35
|
+
|
|
36
|
+
nextBtn.addEventListener('click', () => {
|
|
37
|
+
if (isAnimating) return;
|
|
38
|
+
isAnimating = true;
|
|
39
|
+
|
|
40
|
+
if (currentIndex === totalSlides) {
|
|
41
|
+
currentIndex++;
|
|
42
|
+
gsap.to(wrapper, {
|
|
43
|
+
xPercent: -currentIndex * 100,
|
|
44
|
+
duration: 0.5,
|
|
45
|
+
ease: "power2.inOut",
|
|
46
|
+
onComplete: () => {
|
|
47
|
+
gsap.set(wrapper, { xPercent: -100 });
|
|
48
|
+
currentIndex = 1;
|
|
49
|
+
isAnimating = false;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
} else {
|
|
53
|
+
currentIndex++;
|
|
54
|
+
gsap.to(wrapper, {
|
|
55
|
+
xPercent: -currentIndex * 100,
|
|
56
|
+
duration: 0.5,
|
|
57
|
+
ease: "power2.inOut",
|
|
58
|
+
onComplete: () => {
|
|
59
|
+
isAnimating = false;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
prevBtn.addEventListener('click', () => {
|
|
66
|
+
if (isAnimating) return;
|
|
67
|
+
isAnimating = true;
|
|
68
|
+
|
|
69
|
+
if (currentIndex === 1) {
|
|
70
|
+
currentIndex--;
|
|
71
|
+
gsap.to(wrapper, {
|
|
72
|
+
xPercent: -currentIndex * 100,
|
|
73
|
+
duration: 0.5,
|
|
74
|
+
ease: "power2.inOut",
|
|
75
|
+
onComplete: () => {
|
|
76
|
+
gsap.set(wrapper, { xPercent: -totalSlides * 100 });
|
|
77
|
+
currentIndex = totalSlides;
|
|
78
|
+
isAnimating = false;
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
} else {
|
|
82
|
+
currentIndex--;
|
|
83
|
+
gsap.to(wrapper, {
|
|
84
|
+
xPercent: -currentIndex * 100,
|
|
85
|
+
duration: 0.5,
|
|
86
|
+
ease: "power2.inOut",
|
|
87
|
+
onComplete: () => {
|
|
88
|
+
isAnimating = false;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const version = "1.0.0";
|