@hortonstudio/main 1.2.35 → 1.4.0
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/.claude/settings.local.json +22 -1
- package/TEMP-before-after-attributes.md +158 -0
- package/animations/hero.js +741 -611
- package/animations/text.js +505 -317
- package/animations/transition.js +36 -21
- package/autoInit/accessibility.js +7 -67
- package/autoInit/counter.js +338 -0
- package/autoInit/custom-values.js +266 -0
- package/autoInit/form.js +471 -0
- package/autoInit/modal.js +43 -38
- package/autoInit/navbar.js +484 -371
- package/autoInit/smooth-scroll.js +86 -84
- package/index.js +140 -88
- package/package.json +1 -1
- package/utils/before-after.js +279 -146
- package/utils/scroll-progress.js +26 -21
- package/utils/toc.js +73 -66
- package/CLAUDE.md +0 -45
- package/debug-version.html +0 -37
package/utils/before-after.js
CHANGED
@@ -1,17 +1,15 @@
|
|
1
1
|
export function init() {
|
2
2
|
const config = {
|
3
|
-
defaultMode:
|
3
|
+
defaultMode: "split",
|
4
4
|
sliderPosition: 50,
|
5
5
|
touchSensitivity: 1,
|
6
6
|
keyboardStep: 5,
|
7
7
|
autoPlay: false,
|
8
|
-
autoPlayInterval: 5000
|
8
|
+
autoPlayInterval: 5000,
|
9
9
|
};
|
10
10
|
|
11
11
|
let instances = [];
|
12
12
|
let currentSlideIndex = {};
|
13
|
-
let isDragging = false;
|
14
|
-
let dragInstance = null;
|
15
13
|
|
16
14
|
function updateConfig(newConfig) {
|
17
15
|
Object.assign(config, newConfig);
|
@@ -20,7 +18,7 @@ export function init() {
|
|
20
18
|
function initInstance(wrapper) {
|
21
19
|
const instanceId = instances.length;
|
22
20
|
const items = Array.from(wrapper.children);
|
23
|
-
|
21
|
+
|
24
22
|
if (items.length === 0) return;
|
25
23
|
|
26
24
|
const instance = {
|
@@ -30,7 +28,11 @@ export function init() {
|
|
30
28
|
currentIndex: 0,
|
31
29
|
mode: config.defaultMode,
|
32
30
|
sliderPosition: config.sliderPosition,
|
33
|
-
previousSliderPosition: config.sliderPosition
|
31
|
+
previousSliderPosition: config.sliderPosition,
|
32
|
+
isDragging: false,
|
33
|
+
dragInstance: null,
|
34
|
+
// Cache DOM queries
|
35
|
+
cachedElements: new Map(),
|
34
36
|
};
|
35
37
|
|
36
38
|
instances.push(instance);
|
@@ -38,7 +40,7 @@ export function init() {
|
|
38
40
|
|
39
41
|
setupInstance(instance);
|
40
42
|
showSlide(instance, 0);
|
41
|
-
|
43
|
+
|
42
44
|
return instance;
|
43
45
|
}
|
44
46
|
|
@@ -46,14 +48,14 @@ export function init() {
|
|
46
48
|
const { wrapper, items } = instance;
|
47
49
|
|
48
50
|
items.forEach((item, index) => {
|
49
|
-
item.style.display = index === 0 ?
|
50
|
-
|
51
|
+
item.style.display = index === 0 ? "block" : "none";
|
52
|
+
|
51
53
|
// Set default clip path for after image
|
52
54
|
const afterImage = item.querySelector('[data-hs-ba="image-after"]');
|
53
55
|
if (afterImage) {
|
54
56
|
afterImage.style.clipPath = `polygon(${config.sliderPosition}% 0%, 100% 0%, 100% 100%, ${config.sliderPosition}% 100%)`;
|
55
57
|
}
|
56
|
-
|
58
|
+
|
57
59
|
setupItemInteractions(instance, item, index);
|
58
60
|
});
|
59
61
|
|
@@ -65,75 +67,80 @@ export function init() {
|
|
65
67
|
const leftArrow = item.querySelector('[data-hs-ba="left"]');
|
66
68
|
const rightArrow = item.querySelector('[data-hs-ba="right"]');
|
67
69
|
const slider = item.querySelector('[data-hs-ba="slider"]');
|
68
|
-
const
|
70
|
+
const paginationContainer = item.querySelector('[data-hs-ba="pagination"]');
|
69
71
|
|
70
|
-
modeButtons.forEach(button => {
|
71
|
-
const mode = button.getAttribute(
|
72
|
-
button.addEventListener(
|
72
|
+
modeButtons.forEach((button) => {
|
73
|
+
const mode = button.getAttribute("data-hs-ba").replace("mode-", "");
|
74
|
+
button.addEventListener("click", (e) => {
|
73
75
|
e.preventDefault();
|
74
|
-
|
76
|
+
|
75
77
|
// If clicking split mode when already in split mode, reset to default position
|
76
|
-
if (mode ===
|
78
|
+
if (mode === "split" && instance.mode === "split") {
|
77
79
|
instance.sliderPosition = config.sliderPosition;
|
78
80
|
}
|
79
|
-
|
81
|
+
|
80
82
|
setMode(instance, itemIndex, mode);
|
81
83
|
});
|
82
84
|
});
|
83
85
|
|
84
86
|
if (leftArrow) {
|
85
|
-
leftArrow.addEventListener(
|
87
|
+
leftArrow.addEventListener("click", (e) => {
|
86
88
|
e.preventDefault();
|
87
89
|
navigateSlide(instance, -1);
|
88
90
|
});
|
89
91
|
}
|
90
92
|
|
91
93
|
if (rightArrow) {
|
92
|
-
rightArrow.addEventListener(
|
94
|
+
rightArrow.addEventListener("click", (e) => {
|
93
95
|
e.preventDefault();
|
94
96
|
navigateSlide(instance, 1);
|
95
97
|
});
|
96
98
|
}
|
97
99
|
|
98
100
|
if (slider) {
|
99
|
-
slider.style.cursor =
|
101
|
+
slider.style.cursor = "grab";
|
100
102
|
setupSliderDragging(instance, slider, itemIndex);
|
101
103
|
}
|
102
104
|
|
103
|
-
if (
|
104
|
-
setupPagination(instance,
|
105
|
+
if (paginationContainer) {
|
106
|
+
setupPagination(instance, paginationContainer);
|
105
107
|
}
|
106
108
|
}
|
107
109
|
|
108
110
|
function setMode(instance, itemIndex, mode) {
|
109
111
|
const item = instance.items[itemIndex];
|
110
112
|
const afterImage = item.querySelector('[data-hs-ba="image-after"]');
|
111
|
-
const slider = item.querySelector('.ba-slider');
|
112
113
|
const sliderHandle = item.querySelector('[data-hs-ba="slider"]');
|
113
114
|
const modeButtons = item.querySelectorAll('[data-hs-ba^="mode-"]');
|
114
115
|
|
115
|
-
modeButtons.forEach(btn => {
|
116
|
-
const btnMode = btn.getAttribute(
|
116
|
+
modeButtons.forEach((btn) => {
|
117
|
+
const btnMode = btn.getAttribute("data-hs-ba").replace("mode-", "");
|
117
118
|
if (btnMode === mode) {
|
118
|
-
btn.classList.
|
119
|
+
btn.classList.add("is-active");
|
119
120
|
} else {
|
120
|
-
btn.classList.
|
121
|
+
btn.classList.remove("is-active");
|
121
122
|
}
|
122
123
|
});
|
123
124
|
|
124
125
|
switch (mode) {
|
125
|
-
case
|
126
|
-
if (afterImage)
|
127
|
-
|
126
|
+
case "before":
|
127
|
+
if (afterImage)
|
128
|
+
afterImage.style.clipPath =
|
129
|
+
"polygon(100% 0%, 100% 0%, 100% 100%, 100% 100%)";
|
130
|
+
if (sliderHandle) sliderHandle.style.display = "none";
|
128
131
|
break;
|
129
|
-
case
|
130
|
-
if (afterImage)
|
131
|
-
|
132
|
+
case "after":
|
133
|
+
if (afterImage)
|
134
|
+
afterImage.style.clipPath =
|
135
|
+
"polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)";
|
136
|
+
if (sliderHandle) sliderHandle.style.display = "none";
|
132
137
|
break;
|
133
|
-
case
|
134
|
-
if (afterImage)
|
135
|
-
|
136
|
-
if (
|
138
|
+
case "split":
|
139
|
+
if (afterImage)
|
140
|
+
afterImage.style.clipPath = `polygon(${instance.sliderPosition}% 0%, 100% 0%, 100% 100%, ${instance.sliderPosition}% 100%)`;
|
141
|
+
if (sliderHandle)
|
142
|
+
sliderHandle.style.left = `${instance.sliderPosition}%`;
|
143
|
+
if (sliderHandle) sliderHandle.style.display = "flex";
|
137
144
|
break;
|
138
145
|
}
|
139
146
|
|
@@ -142,65 +149,74 @@ export function init() {
|
|
142
149
|
|
143
150
|
function setupSliderDragging(instance, slider, itemIndex) {
|
144
151
|
const item = instance.items[itemIndex];
|
145
|
-
|
146
|
-
|
152
|
+
// Find the image wrapper using the data attribute
|
153
|
+
const imageWrap = item.querySelector('[data-hs-ba="image-wrapper"]');
|
154
|
+
|
147
155
|
if (!imageWrap) return;
|
148
156
|
|
149
157
|
function startDrag(e) {
|
150
|
-
isDragging = true;
|
151
|
-
dragInstance = { instance, itemIndex, imageWrap, slider };
|
152
|
-
document.body.style.userSelect =
|
153
|
-
document.body.style.cursor =
|
154
|
-
slider.style.cursor =
|
158
|
+
instance.isDragging = true;
|
159
|
+
instance.dragInstance = { instance, itemIndex, imageWrap, slider };
|
160
|
+
document.body.style.userSelect = "none";
|
161
|
+
document.body.style.cursor = "grabbing";
|
162
|
+
slider.style.cursor = "grabbing";
|
155
163
|
e.preventDefault();
|
156
164
|
}
|
157
165
|
|
158
166
|
function handleDrag(clientX) {
|
159
|
-
if (!isDragging || !dragInstance) return;
|
167
|
+
if (!instance.isDragging || !instance.dragInstance) return;
|
160
168
|
|
161
|
-
const rect = dragInstance.imageWrap.getBoundingClientRect();
|
169
|
+
const rect = instance.dragInstance.imageWrap.getBoundingClientRect();
|
162
170
|
const x = clientX - rect.left;
|
163
171
|
const percentage = Math.max(0, Math.min(100, (x / rect.width) * 100));
|
164
|
-
|
172
|
+
|
165
173
|
// Move the slider element directly
|
166
|
-
dragInstance.slider.style.left = `${percentage}%`;
|
167
|
-
|
174
|
+
instance.dragInstance.slider.style.left = `${percentage}%`;
|
175
|
+
|
168
176
|
// Update the clip path
|
169
|
-
updateSliderPosition(
|
170
|
-
|
177
|
+
updateSliderPosition(
|
178
|
+
instance.dragInstance.instance,
|
179
|
+
instance.dragInstance.itemIndex,
|
180
|
+
percentage,
|
181
|
+
);
|
182
|
+
instance.dragInstance.instance.sliderPosition = percentage;
|
171
183
|
}
|
172
184
|
|
173
185
|
function endDrag() {
|
174
|
-
isDragging = false;
|
175
|
-
if (dragInstance && dragInstance.slider) {
|
176
|
-
dragInstance.slider.style.cursor =
|
186
|
+
instance.isDragging = false;
|
187
|
+
if (instance.dragInstance && instance.dragInstance.slider) {
|
188
|
+
instance.dragInstance.slider.style.cursor = "grab";
|
177
189
|
}
|
178
|
-
dragInstance = null;
|
179
|
-
document.body.style.userSelect =
|
180
|
-
document.body.style.cursor =
|
190
|
+
instance.dragInstance = null;
|
191
|
+
document.body.style.userSelect = "";
|
192
|
+
document.body.style.cursor = "";
|
181
193
|
}
|
182
194
|
|
183
|
-
slider.addEventListener(
|
184
|
-
slider.addEventListener(
|
195
|
+
slider.addEventListener("mousedown", startDrag);
|
196
|
+
slider.addEventListener("touchstart", startDrag, { passive: false });
|
185
197
|
|
186
|
-
document.addEventListener(
|
187
|
-
document.addEventListener(
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
198
|
+
document.addEventListener("mousemove", (e) => handleDrag(e.clientX));
|
199
|
+
document.addEventListener(
|
200
|
+
"touchmove",
|
201
|
+
(e) => {
|
202
|
+
if (instance.isDragging) {
|
203
|
+
e.preventDefault();
|
204
|
+
handleDrag(e.touches[0].clientX);
|
205
|
+
}
|
206
|
+
},
|
207
|
+
{ passive: false },
|
208
|
+
);
|
209
|
+
|
210
|
+
document.addEventListener("mouseup", endDrag);
|
211
|
+
document.addEventListener("touchend", endDrag);
|
193
212
|
|
194
|
-
|
195
|
-
|
213
|
+
imageWrap.addEventListener("click", (e) => {
|
214
|
+
if (instance.mode !== "split") return;
|
196
215
|
|
197
|
-
imageWrap.addEventListener('click', (e) => {
|
198
|
-
if (instance.mode !== 'split') return;
|
199
|
-
|
200
216
|
const rect = imageWrap.getBoundingClientRect();
|
201
217
|
const x = e.clientX - rect.left;
|
202
218
|
const percentage = (x / rect.width) * 100;
|
203
|
-
|
219
|
+
|
204
220
|
slider.style.left = `${percentage}%`;
|
205
221
|
updateSliderPosition(instance, itemIndex, percentage);
|
206
222
|
instance.sliderPosition = percentage;
|
@@ -208,8 +224,28 @@ export function init() {
|
|
208
224
|
}
|
209
225
|
|
210
226
|
function updateSliderPosition(instance, itemIndex, percentage) {
|
227
|
+
// Validate input
|
228
|
+
if (typeof percentage !== "number" || isNaN(percentage)) {
|
229
|
+
console.error(
|
230
|
+
"Invalid percentage value for slider position:",
|
231
|
+
percentage,
|
232
|
+
);
|
233
|
+
return;
|
234
|
+
}
|
235
|
+
|
236
|
+
percentage = Math.max(0, Math.min(100, percentage));
|
237
|
+
|
211
238
|
const item = instance.items[itemIndex];
|
212
|
-
const
|
239
|
+
const cacheKey = `afterImage-${itemIndex}`;
|
240
|
+
|
241
|
+
// Use cached element if available
|
242
|
+
let afterImage = instance.cachedElements.get(cacheKey);
|
243
|
+
if (!afterImage) {
|
244
|
+
afterImage = item.querySelector('[data-hs-ba="image-after"]');
|
245
|
+
if (afterImage) {
|
246
|
+
instance.cachedElements.set(cacheKey, afterImage);
|
247
|
+
}
|
248
|
+
}
|
213
249
|
|
214
250
|
if (afterImage) {
|
215
251
|
afterImage.style.clipPath = `polygon(${percentage}% 0%, 100% 0%, 100% 100%, ${percentage}% 100%)`;
|
@@ -235,26 +271,25 @@ export function init() {
|
|
235
271
|
function showSlide(instance, index) {
|
236
272
|
if (index === instance.currentIndex) return;
|
237
273
|
|
238
|
-
//
|
239
|
-
instance.sliderPosition = config.sliderPosition;
|
274
|
+
// Keep current slider position when switching items (don't reset)
|
240
275
|
|
241
276
|
instance.items.forEach((item, i) => {
|
242
|
-
item.style.display = i === index ?
|
243
|
-
|
277
|
+
item.style.display = i === index ? "block" : "none";
|
278
|
+
|
244
279
|
// Update clip path for the new active item
|
245
280
|
if (i === index) {
|
246
281
|
const afterImage = item.querySelector('[data-hs-ba="image-after"]');
|
247
282
|
const sliderHandle = item.querySelector('[data-hs-ba="slider"]');
|
248
|
-
|
283
|
+
|
249
284
|
if (afterImage) {
|
250
|
-
// Apply
|
251
|
-
if (instance.mode ===
|
285
|
+
// Apply current slider position to new item
|
286
|
+
if (instance.mode === "split") {
|
252
287
|
afterImage.style.clipPath = `polygon(${instance.sliderPosition}% 0%, 100% 0%, 100% 100%, ${instance.sliderPosition}% 100%)`;
|
253
288
|
}
|
254
289
|
}
|
255
|
-
|
256
|
-
if (sliderHandle && instance.mode ===
|
257
|
-
// Position slider at
|
290
|
+
|
291
|
+
if (sliderHandle && instance.mode === "split") {
|
292
|
+
// Position slider at current position
|
258
293
|
sliderHandle.style.left = `${instance.sliderPosition}%`;
|
259
294
|
}
|
260
295
|
}
|
@@ -267,105 +302,156 @@ export function init() {
|
|
267
302
|
setMode(instance, index, instance.mode);
|
268
303
|
}
|
269
304
|
|
270
|
-
function setupPagination(instance,
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
305
|
+
function setupPagination(instance, paginationContainer) {
|
306
|
+
// Find existing dots as templates
|
307
|
+
const templateDots = Array.from(paginationContainer.children);
|
308
|
+
const templateDot = templateDots[0]; // Use first child as template
|
309
|
+
|
310
|
+
if (!templateDot) return; // No template found
|
311
|
+
|
312
|
+
// Clear existing dots
|
313
|
+
paginationContainer.innerHTML = "";
|
314
|
+
|
315
|
+
// Create dots for each item
|
316
|
+
instance.items.forEach((item, index) => {
|
317
|
+
const dot = templateDot.cloneNode(true);
|
318
|
+
|
319
|
+
// Add accessibility attributes
|
320
|
+
dot.setAttribute("role", "button");
|
321
|
+
dot.setAttribute("tabindex", "0");
|
322
|
+
dot.setAttribute("aria-label", `Go to slide ${index + 1}`);
|
323
|
+
dot.setAttribute("aria-current", index === 0 ? "true" : "false");
|
324
|
+
|
325
|
+
// Set initial active state
|
326
|
+
if (index === 0) {
|
327
|
+
dot.classList.add("is-active");
|
328
|
+
} else {
|
329
|
+
dot.classList.remove("is-active");
|
330
|
+
}
|
331
|
+
|
332
|
+
// Add interaction styles
|
333
|
+
dot.style.cursor = "pointer";
|
334
|
+
|
335
|
+
// Add click handler
|
336
|
+
dot.addEventListener("click", () => {
|
275
337
|
showSlide(instance, index);
|
276
338
|
});
|
277
|
-
|
278
|
-
|
279
|
-
dot.
|
280
|
-
|
281
|
-
dot.setAttribute('aria-label', `Go to slide ${index + 1}`);
|
282
|
-
|
283
|
-
dot.addEventListener('keydown', (e) => {
|
284
|
-
if (e.key === 'Enter' || e.key === ' ') {
|
339
|
+
|
340
|
+
// Add keyboard handler
|
341
|
+
dot.addEventListener("keydown", (e) => {
|
342
|
+
if (e.key === "Enter" || e.key === " ") {
|
285
343
|
e.preventDefault();
|
286
344
|
showSlide(instance, index);
|
287
345
|
}
|
288
346
|
});
|
347
|
+
|
348
|
+
// Append to container
|
349
|
+
paginationContainer.appendChild(dot);
|
289
350
|
});
|
290
351
|
}
|
291
352
|
|
292
353
|
function updatePagination(instance) {
|
293
|
-
instance.items.forEach((item
|
294
|
-
const
|
295
|
-
|
354
|
+
instance.items.forEach((item) => {
|
355
|
+
const paginationContainer = item.querySelector(
|
356
|
+
'[data-hs-ba="pagination"]',
|
357
|
+
);
|
358
|
+
if (!paginationContainer) return;
|
296
359
|
|
297
|
-
const dots =
|
360
|
+
const dots = Array.from(paginationContainer.children);
|
298
361
|
dots.forEach((dot, dotIndex) => {
|
299
362
|
if (dotIndex === instance.currentIndex) {
|
300
|
-
dot.classList.add(
|
301
|
-
dot.setAttribute(
|
363
|
+
dot.classList.add("is-active");
|
364
|
+
dot.setAttribute("aria-current", "true");
|
302
365
|
} else {
|
303
|
-
dot.classList.remove(
|
304
|
-
dot.
|
366
|
+
dot.classList.remove("is-active");
|
367
|
+
dot.setAttribute("aria-current", "false");
|
305
368
|
}
|
306
369
|
});
|
307
370
|
});
|
308
371
|
}
|
309
372
|
|
310
373
|
function setupKeyboardNavigation(instance) {
|
311
|
-
|
374
|
+
// Main wrapper: Left/Right arrows switch between items
|
375
|
+
instance.wrapper.addEventListener("keydown", (e) => {
|
376
|
+
// Only respond when the main wrapper itself is focused
|
377
|
+
if (e.target !== instance.wrapper) return;
|
378
|
+
|
312
379
|
switch (e.key) {
|
313
|
-
case
|
380
|
+
case "ArrowLeft":
|
314
381
|
e.preventDefault();
|
315
|
-
|
316
|
-
const newPos = Math.max(0, instance.sliderPosition - config.keyboardStep);
|
317
|
-
updateSliderPosition(instance, instance.currentIndex, newPos);
|
318
|
-
instance.sliderPosition = newPos;
|
319
|
-
} else {
|
320
|
-
navigateSlide(instance, -1);
|
321
|
-
}
|
382
|
+
navigateSlide(instance, -1);
|
322
383
|
break;
|
323
|
-
case
|
384
|
+
case "ArrowRight":
|
324
385
|
e.preventDefault();
|
325
|
-
|
326
|
-
const newPos = Math.min(100, instance.sliderPosition + config.keyboardStep);
|
327
|
-
updateSliderPosition(instance, instance.currentIndex, newPos);
|
328
|
-
instance.sliderPosition = newPos;
|
329
|
-
} else {
|
330
|
-
navigateSlide(instance, 1);
|
331
|
-
}
|
386
|
+
navigateSlide(instance, 1);
|
332
387
|
break;
|
333
|
-
case
|
388
|
+
case "ArrowUp":
|
334
389
|
e.preventDefault();
|
335
390
|
navigateSlide(instance, -1);
|
336
391
|
break;
|
337
|
-
case
|
392
|
+
case "ArrowDown":
|
338
393
|
e.preventDefault();
|
339
394
|
navigateSlide(instance, 1);
|
340
395
|
break;
|
341
|
-
case '1':
|
342
|
-
setMode(instance, instance.currentIndex, 'before');
|
343
|
-
break;
|
344
|
-
case '2':
|
345
|
-
setMode(instance, instance.currentIndex, 'split');
|
346
|
-
break;
|
347
|
-
case '3':
|
348
|
-
setMode(instance, instance.currentIndex, 'after');
|
349
|
-
break;
|
350
396
|
}
|
351
397
|
});
|
352
398
|
|
353
|
-
|
354
|
-
instance.
|
355
|
-
|
356
|
-
|
399
|
+
// Individual items: Left/Right arrows move slider
|
400
|
+
instance.items.forEach((item, itemIndex) => {
|
401
|
+
const imageWrapper = item.querySelector('[data-hs-ba="image-wrapper"]');
|
402
|
+
if (!imageWrapper) return;
|
403
|
+
|
404
|
+
imageWrapper.addEventListener("keydown", (e) => {
|
405
|
+
// Only respond when the image wrapper itself is focused
|
406
|
+
if (e.target !== imageWrapper) return;
|
407
|
+
// Only work in split mode
|
408
|
+
if (instance.mode !== "split") return;
|
409
|
+
|
410
|
+
switch (e.key) {
|
411
|
+
case "ArrowLeft":
|
412
|
+
e.preventDefault();
|
413
|
+
const newPosLeft = Math.max(
|
414
|
+
0,
|
415
|
+
instance.sliderPosition - config.keyboardStep,
|
416
|
+
);
|
417
|
+
updateSliderPosition(instance, instance.currentIndex, newPosLeft);
|
418
|
+
instance.sliderPosition = newPosLeft;
|
419
|
+
const sliderLeft = item.querySelector('[data-hs-ba="slider"]');
|
420
|
+
if (sliderLeft) sliderLeft.style.left = `${newPosLeft}%`;
|
421
|
+
break;
|
422
|
+
case "ArrowRight":
|
423
|
+
e.preventDefault();
|
424
|
+
const newPosRight = Math.min(
|
425
|
+
100,
|
426
|
+
instance.sliderPosition + config.keyboardStep,
|
427
|
+
);
|
428
|
+
updateSliderPosition(instance, instance.currentIndex, newPosRight);
|
429
|
+
instance.sliderPosition = newPosRight;
|
430
|
+
const sliderRight = item.querySelector('[data-hs-ba="slider"]');
|
431
|
+
if (sliderRight) sliderRight.style.left = `${newPosRight}%`;
|
432
|
+
break;
|
433
|
+
}
|
434
|
+
});
|
357
435
|
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
436
|
+
// Make image wrapper focusable and accessible
|
437
|
+
imageWrapper.setAttribute("tabindex", "0");
|
438
|
+
imageWrapper.setAttribute("role", "img");
|
439
|
+
imageWrapper.setAttribute(
|
440
|
+
"aria-label",
|
441
|
+
`Before and after comparison image, use arrow keys to adjust slider`,
|
442
|
+
);
|
363
443
|
});
|
364
444
|
|
365
|
-
|
445
|
+
// Set up main wrapper accessibility
|
446
|
+
instance.wrapper.setAttribute("tabindex", "0");
|
447
|
+
instance.wrapper.setAttribute("role", "application");
|
448
|
+
instance.wrapper.setAttribute(
|
449
|
+
"aria-label",
|
450
|
+
"Before and after image comparison, use arrow keys to navigate items",
|
451
|
+
);
|
366
452
|
}
|
367
453
|
|
368
|
-
if (typeof window !==
|
454
|
+
if (typeof window !== "undefined") {
|
369
455
|
window.hsmain = window.hsmain || {};
|
370
456
|
window.hsmain.utilBeforeAfter = {
|
371
457
|
init,
|
@@ -379,9 +465,56 @@ export function init() {
|
|
379
465
|
setMode: (instanceId, mode) => {
|
380
466
|
const instance = instances[instanceId];
|
381
467
|
if (instance) setMode(instance, instance.currentIndex, mode);
|
382
|
-
}
|
468
|
+
},
|
469
|
+
};
|
470
|
+
}
|
471
|
+
|
472
|
+
// Initialize - prevent duplicate initialization
|
473
|
+
const wrappers = document.querySelectorAll(
|
474
|
+
'[data-hs-ba="wrapper"]:not([data-initialized])',
|
475
|
+
);
|
476
|
+
|
477
|
+
if (wrappers.length === 0) {
|
478
|
+
return {
|
479
|
+
result: "before-after already initialized",
|
480
|
+
destroy: () => {},
|
383
481
|
};
|
384
482
|
}
|
385
483
|
|
386
|
-
|
387
|
-
|
484
|
+
wrappers.forEach((wrapper) => {
|
485
|
+
wrapper.setAttribute("data-initialized", "true");
|
486
|
+
initInstance(wrapper);
|
487
|
+
});
|
488
|
+
|
489
|
+
// Return destroy function and result
|
490
|
+
return {
|
491
|
+
result: "before-after initialized",
|
492
|
+
destroy: () => {
|
493
|
+
// Clean up all instances
|
494
|
+
instances.forEach((instance) => {
|
495
|
+
// Remove event listeners from wrapper
|
496
|
+
const newWrapper = instance.wrapper.cloneNode(true);
|
497
|
+
instance.wrapper.parentNode.replaceChild(newWrapper, instance.wrapper);
|
498
|
+
|
499
|
+
// Clear cached elements
|
500
|
+
instance.cachedElements.clear();
|
501
|
+
});
|
502
|
+
|
503
|
+
// Remove initialized markers
|
504
|
+
document
|
505
|
+
.querySelectorAll('[data-hs-ba="wrapper"][data-initialized]')
|
506
|
+
.forEach((wrapper) => {
|
507
|
+
wrapper.removeAttribute("data-initialized");
|
508
|
+
});
|
509
|
+
|
510
|
+
// Reset module state
|
511
|
+
instances = [];
|
512
|
+
currentSlideIndex = {};
|
513
|
+
|
514
|
+
// Clean up window object
|
515
|
+
if (window.hsmain && window.hsmain.utilBeforeAfter) {
|
516
|
+
delete window.hsmain.utilBeforeAfter;
|
517
|
+
}
|
518
|
+
},
|
519
|
+
};
|
520
|
+
}
|