lazer-slider 1.0.2 → 1.0.3
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/dist/index.cjs +607 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +166 -2
- package/dist/index.d.ts +166 -2
- package/dist/index.js +600 -5
- package/dist/index.js.map +1 -1
- package/package.json +26 -6
package/dist/index.js
CHANGED
|
@@ -1,8 +1,603 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
// src/core/easing.ts
|
|
2
|
+
var easeOutExpo = (t) => t === 1 ? 1 : 1 - Math.pow(2, -10 * t);
|
|
3
|
+
var easeOutCubic = (t) => 1 - Math.pow(1 - t, 3);
|
|
4
|
+
var easeInOutCubic = (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
|
|
5
|
+
var easeOutQuad = (t) => 1 - (1 - t) * (1 - t);
|
|
6
|
+
var linear = (t) => t;
|
|
7
|
+
|
|
8
|
+
// src/core/accessibility.ts
|
|
9
|
+
var generateSliderId = () => `slider-${Math.random().toString(36).substring(2, 9)}`;
|
|
10
|
+
var initAria = (settings) => {
|
|
11
|
+
const { feed, prevSlideButton, nextSlideButton, thumbs, slides } = settings;
|
|
12
|
+
if (!feed.id) {
|
|
13
|
+
feed.id = generateSliderId();
|
|
14
|
+
}
|
|
15
|
+
feed.setAttribute("role", "region");
|
|
16
|
+
feed.setAttribute("aria-label", "Carousel");
|
|
17
|
+
feed.setAttribute("aria-roledescription", "carousel");
|
|
18
|
+
feed.removeAttribute("tabindex");
|
|
19
|
+
slides.forEach((slide, index) => {
|
|
20
|
+
slide.setAttribute("role", "group");
|
|
21
|
+
slide.setAttribute("aria-roledescription", "slide");
|
|
22
|
+
slide.setAttribute("aria-label", `Slide ${index + 1} of ${slides.length}`);
|
|
23
|
+
});
|
|
24
|
+
if (prevSlideButton) {
|
|
25
|
+
prevSlideButton.setAttribute("aria-label", "Previous slide");
|
|
26
|
+
prevSlideButton.setAttribute("aria-controls", feed.id);
|
|
27
|
+
prevSlideButton.setAttribute("tabindex", "0");
|
|
28
|
+
if (prevSlideButton.tagName !== "BUTTON") {
|
|
29
|
+
prevSlideButton.setAttribute("role", "button");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (nextSlideButton) {
|
|
33
|
+
nextSlideButton.setAttribute("aria-label", "Next slide");
|
|
34
|
+
nextSlideButton.setAttribute("aria-controls", feed.id);
|
|
35
|
+
nextSlideButton.setAttribute("tabindex", "0");
|
|
36
|
+
if (nextSlideButton.tagName !== "BUTTON") {
|
|
37
|
+
nextSlideButton.setAttribute("role", "button");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (thumbs?.length) {
|
|
41
|
+
thumbs.forEach((thumb, index) => {
|
|
42
|
+
if (thumb.tagName !== "BUTTON") {
|
|
43
|
+
thumb.setAttribute("role", "button");
|
|
44
|
+
}
|
|
45
|
+
thumb.setAttribute("aria-label", `Go to slide ${index + 1}`);
|
|
46
|
+
thumb.setAttribute("tabindex", "0");
|
|
47
|
+
thumb.setAttribute("aria-controls", feed.id);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
var toggleControlVisibility = (button, shouldShow, feedElement) => {
|
|
52
|
+
if (!button) return;
|
|
53
|
+
if (!shouldShow && button === document.activeElement && feedElement) {
|
|
54
|
+
feedElement.focus();
|
|
55
|
+
}
|
|
56
|
+
const transition = "opacity 0.3s ease";
|
|
57
|
+
const opacity = shouldShow ? "1" : "0";
|
|
58
|
+
Object.assign(button.style, {
|
|
59
|
+
opacity,
|
|
60
|
+
transition,
|
|
61
|
+
pointerEvents: shouldShow ? "auto" : "none"
|
|
62
|
+
});
|
|
63
|
+
if (!shouldShow) {
|
|
64
|
+
button.setAttribute("aria-hidden", "true");
|
|
65
|
+
button.setAttribute("tabindex", "-1");
|
|
66
|
+
} else {
|
|
67
|
+
button.removeAttribute("aria-hidden");
|
|
68
|
+
button.setAttribute("tabindex", "0");
|
|
69
|
+
}
|
|
70
|
+
if (!shouldShow) {
|
|
71
|
+
setTimeout(() => {
|
|
72
|
+
if (button.style.opacity === "0") {
|
|
73
|
+
button.style.visibility = "hidden";
|
|
74
|
+
}
|
|
75
|
+
}, 300);
|
|
76
|
+
} else {
|
|
77
|
+
button.style.visibility = "visible";
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
var updateActiveThumb = (thumbs, currentIndex, activeClass = "active") => {
|
|
81
|
+
if (!thumbs?.length) return;
|
|
82
|
+
thumbs.forEach((thumb, index) => {
|
|
83
|
+
const isActive = index === currentIndex;
|
|
84
|
+
thumb.classList.toggle(activeClass, isActive);
|
|
85
|
+
thumb.setAttribute("aria-selected", isActive.toString());
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
var setupKeyboardNavigation = (feed, onPrev, onNext, abortSignal) => {
|
|
89
|
+
feed.addEventListener(
|
|
90
|
+
"keydown",
|
|
91
|
+
(event) => {
|
|
92
|
+
switch (event.key) {
|
|
93
|
+
case "ArrowLeft":
|
|
94
|
+
event.preventDefault();
|
|
95
|
+
onPrev();
|
|
96
|
+
break;
|
|
97
|
+
case "ArrowRight":
|
|
98
|
+
event.preventDefault();
|
|
99
|
+
onNext();
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
{ signal: abortSignal }
|
|
104
|
+
);
|
|
105
|
+
if (!feed.hasAttribute("tabindex")) {
|
|
106
|
+
feed.setAttribute("tabindex", "0");
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// src/core/drag.ts
|
|
111
|
+
var createDragState = () => ({
|
|
112
|
+
isDragging: false,
|
|
113
|
+
startX: 0,
|
|
114
|
+
startScrollLeft: 0,
|
|
115
|
+
velocity: 0,
|
|
116
|
+
lastX: 0,
|
|
117
|
+
lastTime: 0,
|
|
118
|
+
momentumId: null
|
|
119
|
+
});
|
|
120
|
+
var findNearestSlide = (feed, slides) => {
|
|
121
|
+
const feedRect = feed.getBoundingClientRect();
|
|
122
|
+
const feedCenter = feedRect.left + feedRect.width / 2;
|
|
123
|
+
let nearestSlide = null;
|
|
124
|
+
let minDistance = Infinity;
|
|
125
|
+
for (const slide of slides) {
|
|
126
|
+
if (slide.offsetParent === null) continue;
|
|
127
|
+
const slideRect = slide.getBoundingClientRect();
|
|
128
|
+
const slideCenter = slideRect.left + slideRect.width / 2;
|
|
129
|
+
const distance = Math.abs(feedCenter - slideCenter);
|
|
130
|
+
if (distance < minDistance) {
|
|
131
|
+
minDistance = distance;
|
|
132
|
+
nearestSlide = slide;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return nearestSlide;
|
|
136
|
+
};
|
|
137
|
+
var applyMomentum = (state, feed, slides, smoothScrollTo, onDragEnd) => {
|
|
138
|
+
const friction = 0.95;
|
|
139
|
+
const minVelocity = 0.5;
|
|
140
|
+
const animate = () => {
|
|
141
|
+
if (Math.abs(state.velocity) < minVelocity) {
|
|
142
|
+
state.momentumId = null;
|
|
143
|
+
const nearestSlide = findNearestSlide(feed, slides);
|
|
144
|
+
if (nearestSlide) {
|
|
145
|
+
smoothScrollTo(nearestSlide.offsetLeft, easeOutCubic);
|
|
146
|
+
}
|
|
147
|
+
onDragEnd?.();
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
feed.scrollLeft += state.velocity;
|
|
151
|
+
state.velocity *= friction;
|
|
152
|
+
state.momentumId = requestAnimationFrame(animate);
|
|
153
|
+
};
|
|
154
|
+
state.momentumId = requestAnimationFrame(animate);
|
|
155
|
+
};
|
|
156
|
+
var getEventX = (event) => {
|
|
157
|
+
if ("touches" in event) {
|
|
158
|
+
return event.touches[0]?.clientX ?? 0;
|
|
159
|
+
}
|
|
160
|
+
return event.clientX;
|
|
161
|
+
};
|
|
162
|
+
var setupDragToScroll = (config) => {
|
|
163
|
+
const { feed, slides, abortSignal, smoothScrollTo, onDragEnd } = config;
|
|
164
|
+
const state = createDragState();
|
|
165
|
+
const handleDragStart = (event) => {
|
|
166
|
+
if (state.momentumId !== null) {
|
|
167
|
+
cancelAnimationFrame(state.momentumId);
|
|
168
|
+
state.momentumId = null;
|
|
169
|
+
}
|
|
170
|
+
state.isDragging = true;
|
|
171
|
+
state.startX = getEventX(event);
|
|
172
|
+
state.startScrollLeft = feed.scrollLeft;
|
|
173
|
+
state.velocity = 0;
|
|
174
|
+
state.lastX = state.startX;
|
|
175
|
+
state.lastTime = performance.now();
|
|
176
|
+
feed.style.cursor = "grabbing";
|
|
177
|
+
feed.style.userSelect = "none";
|
|
178
|
+
if (event.type === "mousedown") {
|
|
179
|
+
event.preventDefault();
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
const handleDragMove = (event) => {
|
|
183
|
+
if (!state.isDragging) return;
|
|
184
|
+
const currentX = getEventX(event);
|
|
185
|
+
const currentTime = performance.now();
|
|
186
|
+
const deltaX = state.startX - currentX;
|
|
187
|
+
const deltaTime = currentTime - state.lastTime;
|
|
188
|
+
feed.scrollLeft = state.startScrollLeft + deltaX;
|
|
189
|
+
if (deltaTime > 0) {
|
|
190
|
+
state.velocity = (state.lastX - currentX) / deltaTime * 16;
|
|
191
|
+
}
|
|
192
|
+
state.lastX = currentX;
|
|
193
|
+
state.lastTime = currentTime;
|
|
194
|
+
if (event.type === "touchmove") {
|
|
195
|
+
event.preventDefault();
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
const handleDragEnd = () => {
|
|
199
|
+
if (!state.isDragging) return;
|
|
200
|
+
state.isDragging = false;
|
|
201
|
+
feed.style.cursor = "grab";
|
|
202
|
+
feed.style.userSelect = "";
|
|
203
|
+
if (Math.abs(state.velocity) > 1) {
|
|
204
|
+
applyMomentum(state, feed, slides, smoothScrollTo, onDragEnd);
|
|
205
|
+
} else {
|
|
206
|
+
const nearestSlide = findNearestSlide(feed, slides);
|
|
207
|
+
if (nearestSlide) {
|
|
208
|
+
smoothScrollTo(nearestSlide.offsetLeft, easeOutCubic);
|
|
209
|
+
}
|
|
210
|
+
onDragEnd?.();
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
feed.style.cursor = "grab";
|
|
214
|
+
feed.addEventListener("mousedown", handleDragStart, { signal: abortSignal });
|
|
215
|
+
document.addEventListener("mousemove", handleDragMove, { signal: abortSignal });
|
|
216
|
+
document.addEventListener("mouseup", handleDragEnd, { signal: abortSignal });
|
|
217
|
+
feed.addEventListener("touchstart", handleDragStart, {
|
|
218
|
+
passive: true,
|
|
219
|
+
signal: abortSignal
|
|
220
|
+
});
|
|
221
|
+
feed.addEventListener("touchmove", handleDragMove, {
|
|
222
|
+
passive: false,
|
|
223
|
+
// Need to prevent default
|
|
224
|
+
signal: abortSignal
|
|
225
|
+
});
|
|
226
|
+
feed.addEventListener("touchend", handleDragEnd, { signal: abortSignal });
|
|
227
|
+
document.addEventListener("mouseleave", handleDragEnd, { signal: abortSignal });
|
|
228
|
+
return state;
|
|
229
|
+
};
|
|
230
|
+
var cleanupDrag = (state) => {
|
|
231
|
+
if (state.momentumId !== null) {
|
|
232
|
+
cancelAnimationFrame(state.momentumId);
|
|
233
|
+
state.momentumId = null;
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// src/core/slider.ts
|
|
238
|
+
var ANIMATION = {
|
|
239
|
+
MIN_DURATION: 400,
|
|
240
|
+
MAX_DURATION: 1e3,
|
|
241
|
+
SPEED_FACTOR: 1.5,
|
|
242
|
+
SCROLL_END_DELAY: 50,
|
|
243
|
+
THUMB_UPDATE_DELAY: 500
|
|
244
|
+
};
|
|
245
|
+
var DESKTOP_BREAKPOINT = "(min-width: 64rem)";
|
|
246
|
+
var createSlider = (settings) => {
|
|
247
|
+
if (!settings.feed) {
|
|
248
|
+
throw new Error("lazer-slider: feed element is required");
|
|
249
|
+
}
|
|
250
|
+
if (!settings.slides?.length) {
|
|
251
|
+
throw new Error("lazer-slider: slides array is required and must not be empty");
|
|
252
|
+
}
|
|
253
|
+
const state = {
|
|
254
|
+
currentSlideIndex: 0,
|
|
255
|
+
isScrolling: false,
|
|
256
|
+
ticking: false,
|
|
257
|
+
cachedFeedRect: null,
|
|
258
|
+
lastWidth: 0,
|
|
259
|
+
updateThumbTimeout: null,
|
|
260
|
+
scrollEndTimeout: null,
|
|
261
|
+
abortController: new AbortController(),
|
|
262
|
+
autoplayIntervalId: null,
|
|
263
|
+
autoplayPaused: false
|
|
264
|
+
};
|
|
265
|
+
let dragState = null;
|
|
266
|
+
const easing = settings.easing ?? easeOutExpo;
|
|
267
|
+
const getFeedRect = () => {
|
|
268
|
+
const currentWidth = settings.feed.clientWidth;
|
|
269
|
+
if (!state.cachedFeedRect || state.lastWidth !== currentWidth) {
|
|
270
|
+
state.cachedFeedRect = settings.feed.getBoundingClientRect();
|
|
271
|
+
state.lastWidth = currentWidth;
|
|
272
|
+
}
|
|
273
|
+
return state.cachedFeedRect;
|
|
274
|
+
};
|
|
275
|
+
const getVisibleSlides = () => {
|
|
276
|
+
return settings.slides.filter((slide) => slide.offsetParent !== null);
|
|
277
|
+
};
|
|
278
|
+
const isDesktop = () => {
|
|
279
|
+
return window.matchMedia(DESKTOP_BREAKPOINT).matches;
|
|
280
|
+
};
|
|
281
|
+
const applySlideWidths = () => {
|
|
282
|
+
const perView = isDesktop() ? settings.desktopSlidesPerView : settings.mobileSlidesPerView;
|
|
283
|
+
if (!perView) return;
|
|
284
|
+
const gap = settings.slideGap ?? 0;
|
|
285
|
+
const totalGapWidth = gap * (perView - 1);
|
|
286
|
+
const slideWidth = `calc((100% - ${totalGapWidth}px) / ${perView})`;
|
|
287
|
+
settings.slides.forEach((slide) => {
|
|
288
|
+
slide.style.flex = `0 0 ${slideWidth}`;
|
|
289
|
+
slide.style.minWidth = slideWidth;
|
|
290
|
+
});
|
|
291
|
+
if (gap > 0) {
|
|
292
|
+
settings.feed.style.gap = `${gap}px`;
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
const updateScrollbar = () => {
|
|
296
|
+
if (!settings.scrollbarThumb) return;
|
|
297
|
+
const feedRect = getFeedRect();
|
|
298
|
+
const thumbWidth = feedRect.width / settings.feed.scrollWidth * 100;
|
|
299
|
+
settings.scrollbarThumb.style.width = `${thumbWidth}%`;
|
|
300
|
+
};
|
|
301
|
+
const updateScrollbarPosition = () => {
|
|
302
|
+
if (!settings.scrollbarThumb || !settings.scrollbarTrack) return;
|
|
303
|
+
const trackWidth = settings.scrollbarTrack.getBoundingClientRect().width;
|
|
304
|
+
const thumbWidth = settings.scrollbarThumb.getBoundingClientRect().width;
|
|
305
|
+
const totalTransform = trackWidth - thumbWidth;
|
|
306
|
+
const maxScroll = settings.feed.scrollWidth - settings.feed.clientWidth;
|
|
307
|
+
const scrollProgress = maxScroll > 0 ? settings.feed.scrollLeft / maxScroll : 0;
|
|
308
|
+
settings.scrollbarThumb.style.transform = `translateX(${totalTransform * scrollProgress}px)`;
|
|
309
|
+
};
|
|
310
|
+
const updateControlsVisibility = () => {
|
|
311
|
+
const feedRect = getFeedRect();
|
|
312
|
+
const isAtStart = settings.feed.scrollLeft <= 1;
|
|
313
|
+
const isAtEnd = settings.feed.scrollLeft + feedRect.width >= settings.feed.scrollWidth - 1;
|
|
314
|
+
const shouldHideScrollbar = settings.feed.scrollWidth <= feedRect.width;
|
|
315
|
+
if (settings.scrollbarTrack) {
|
|
316
|
+
settings.scrollbarTrack.style.display = shouldHideScrollbar ? "none" : "block";
|
|
317
|
+
}
|
|
318
|
+
if (settings.loop) {
|
|
319
|
+
toggleControlVisibility(
|
|
320
|
+
settings.prevSlideButton,
|
|
321
|
+
!shouldHideScrollbar,
|
|
322
|
+
settings.feed
|
|
323
|
+
);
|
|
324
|
+
toggleControlVisibility(
|
|
325
|
+
settings.nextSlideButton,
|
|
326
|
+
!shouldHideScrollbar,
|
|
327
|
+
settings.feed
|
|
328
|
+
);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
toggleControlVisibility(
|
|
332
|
+
settings.prevSlideButton,
|
|
333
|
+
!isAtStart && !shouldHideScrollbar,
|
|
334
|
+
settings.feed
|
|
335
|
+
);
|
|
336
|
+
toggleControlVisibility(
|
|
337
|
+
settings.nextSlideButton,
|
|
338
|
+
!isAtEnd && !shouldHideScrollbar,
|
|
339
|
+
settings.feed
|
|
340
|
+
);
|
|
341
|
+
};
|
|
342
|
+
const updateCurrentSlideIndex = () => {
|
|
343
|
+
const feedRect = getFeedRect();
|
|
344
|
+
const allVisibleSlides = getVisibleSlides();
|
|
345
|
+
const viewportVisibleSlides = settings.slides.filter((slide) => {
|
|
346
|
+
const slideRect = slide.getBoundingClientRect();
|
|
347
|
+
const tolerance = 20;
|
|
348
|
+
return slideRect.right > feedRect.left + tolerance && slideRect.left < feedRect.right - tolerance;
|
|
349
|
+
});
|
|
350
|
+
if (viewportVisibleSlides.length && viewportVisibleSlides[0]) {
|
|
351
|
+
const newIndex = allVisibleSlides.indexOf(viewportVisibleSlides[0]);
|
|
352
|
+
if (newIndex !== -1) {
|
|
353
|
+
state.currentSlideIndex = newIndex;
|
|
354
|
+
settings.onScroll?.({
|
|
355
|
+
currentScroll: settings.feed.scrollLeft,
|
|
356
|
+
currentSlideIndex: state.currentSlideIndex
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
const smoothScrollTo = (target, customEasing = easing) => {
|
|
362
|
+
const start = settings.feed.scrollLeft;
|
|
363
|
+
const distance = Math.abs(target - start);
|
|
364
|
+
const duration = Math.min(
|
|
365
|
+
ANIMATION.MAX_DURATION,
|
|
366
|
+
Math.max(ANIMATION.MIN_DURATION, distance / ANIMATION.SPEED_FACTOR)
|
|
367
|
+
);
|
|
368
|
+
const startTime = performance.now();
|
|
369
|
+
const animateScroll = (currentTime) => {
|
|
370
|
+
const elapsed = (currentTime - startTime) / duration;
|
|
371
|
+
const progress = Math.min(elapsed, 1);
|
|
372
|
+
const ease = customEasing(progress);
|
|
373
|
+
settings.feed.scrollLeft = start + (target - start) * ease;
|
|
374
|
+
if (progress < 1) {
|
|
375
|
+
requestAnimationFrame(animateScroll);
|
|
376
|
+
} else {
|
|
377
|
+
settings.feed.scrollLeft = target;
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
requestAnimationFrame(animateScroll);
|
|
381
|
+
};
|
|
382
|
+
const handleThumbClick = (thumb) => {
|
|
383
|
+
if (!settings.thumbs) return;
|
|
384
|
+
const index = settings.thumbs.indexOf(thumb);
|
|
385
|
+
if (index === -1 || !settings.slides[index]) return;
|
|
386
|
+
state.currentSlideIndex = index;
|
|
387
|
+
updateActiveThumb(settings.thumbs, index);
|
|
388
|
+
state.isScrolling = true;
|
|
389
|
+
if (state.updateThumbTimeout) {
|
|
390
|
+
clearTimeout(state.updateThumbTimeout);
|
|
391
|
+
}
|
|
392
|
+
state.updateThumbTimeout = setTimeout(() => {
|
|
393
|
+
state.isScrolling = false;
|
|
394
|
+
}, ANIMATION.THUMB_UPDATE_DELAY);
|
|
395
|
+
smoothScrollTo(settings.slides[index].offsetLeft);
|
|
396
|
+
};
|
|
397
|
+
const handleNavButtonClick = (direction) => {
|
|
398
|
+
const visibleSlides = getVisibleSlides();
|
|
399
|
+
const slidesToScroll = isDesktop() ? settings.desktopSlidesPerScroll ?? 1 : settings.mobileSlidesPerScroll ?? 1;
|
|
400
|
+
const totalSlides = visibleSlides.length;
|
|
401
|
+
updateCurrentSlideIndex();
|
|
402
|
+
if (direction === "prev") {
|
|
403
|
+
if (settings.loop && state.currentSlideIndex === 0) {
|
|
404
|
+
state.currentSlideIndex = totalSlides - 1;
|
|
405
|
+
} else {
|
|
406
|
+
state.currentSlideIndex = Math.max(0, state.currentSlideIndex - slidesToScroll);
|
|
407
|
+
}
|
|
408
|
+
} else {
|
|
409
|
+
if (settings.loop && state.currentSlideIndex >= totalSlides - 1) {
|
|
410
|
+
state.currentSlideIndex = 0;
|
|
411
|
+
} else {
|
|
412
|
+
state.currentSlideIndex = Math.min(
|
|
413
|
+
totalSlides - 1,
|
|
414
|
+
state.currentSlideIndex + slidesToScroll
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
const targetSlide = visibleSlides[state.currentSlideIndex];
|
|
419
|
+
if (!targetSlide) return;
|
|
420
|
+
settings.onScrollStart?.({
|
|
421
|
+
currentScroll: settings.feed.scrollLeft,
|
|
422
|
+
target: targetSlide,
|
|
423
|
+
direction
|
|
424
|
+
});
|
|
425
|
+
smoothScrollTo(targetSlide.offsetLeft);
|
|
426
|
+
};
|
|
427
|
+
const updateScrollPosition = () => {
|
|
428
|
+
updateScrollbarPosition();
|
|
429
|
+
updateControlsVisibility();
|
|
430
|
+
updateCurrentSlideIndex();
|
|
431
|
+
if (!state.isScrolling) {
|
|
432
|
+
updateActiveThumb(settings.thumbs, state.currentSlideIndex);
|
|
433
|
+
}
|
|
434
|
+
if (state.scrollEndTimeout) {
|
|
435
|
+
clearTimeout(state.scrollEndTimeout);
|
|
436
|
+
}
|
|
437
|
+
state.scrollEndTimeout = setTimeout(() => {
|
|
438
|
+
state.isScrolling = false;
|
|
439
|
+
settings.onScrollEnd?.({
|
|
440
|
+
currentScroll: settings.feed.scrollLeft,
|
|
441
|
+
currentSlideIndex: state.currentSlideIndex
|
|
442
|
+
});
|
|
443
|
+
}, ANIMATION.SCROLL_END_DELAY);
|
|
444
|
+
};
|
|
445
|
+
const handleFeedScroll = () => {
|
|
446
|
+
if (!state.ticking) {
|
|
447
|
+
requestAnimationFrame(() => {
|
|
448
|
+
updateScrollPosition();
|
|
449
|
+
state.ticking = false;
|
|
450
|
+
});
|
|
451
|
+
state.ticking = true;
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
const handleWindowResize = () => {
|
|
455
|
+
state.cachedFeedRect = null;
|
|
456
|
+
refresh();
|
|
457
|
+
};
|
|
458
|
+
const attachEventListeners = () => {
|
|
459
|
+
const { signal } = state.abortController;
|
|
460
|
+
window.addEventListener("resize", handleWindowResize);
|
|
461
|
+
settings.feed.addEventListener("scroll", handleFeedScroll, {
|
|
462
|
+
passive: true,
|
|
463
|
+
signal
|
|
464
|
+
});
|
|
465
|
+
if (settings.prevSlideButton) {
|
|
466
|
+
settings.prevSlideButton.addEventListener(
|
|
467
|
+
"click",
|
|
468
|
+
() => handleNavButtonClick("prev"),
|
|
469
|
+
{ signal }
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
if (settings.nextSlideButton) {
|
|
473
|
+
settings.nextSlideButton.addEventListener(
|
|
474
|
+
"click",
|
|
475
|
+
() => handleNavButtonClick("next"),
|
|
476
|
+
{ signal }
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
if (settings.thumbs?.length) {
|
|
480
|
+
settings.thumbs[0]?.classList.add("active");
|
|
481
|
+
settings.thumbs.forEach((thumb) => {
|
|
482
|
+
thumb.addEventListener("click", () => handleThumbClick(thumb), { signal });
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
setupKeyboardNavigation(
|
|
486
|
+
settings.feed,
|
|
487
|
+
() => handleNavButtonClick("prev"),
|
|
488
|
+
() => handleNavButtonClick("next"),
|
|
489
|
+
signal
|
|
490
|
+
);
|
|
491
|
+
if (settings.enableDragToScroll) {
|
|
492
|
+
dragState = setupDragToScroll({
|
|
493
|
+
feed: settings.feed,
|
|
494
|
+
slides: settings.slides,
|
|
495
|
+
abortSignal: signal,
|
|
496
|
+
smoothScrollTo,
|
|
497
|
+
onDragEnd: () => {
|
|
498
|
+
updateCurrentSlideIndex();
|
|
499
|
+
updateActiveThumb(settings.thumbs, state.currentSlideIndex);
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
if (settings.autoplay && settings.pauseOnHover !== false) {
|
|
504
|
+
settings.feed.addEventListener(
|
|
505
|
+
"mouseenter",
|
|
506
|
+
pauseAutoplay,
|
|
507
|
+
{ signal }
|
|
508
|
+
);
|
|
509
|
+
settings.feed.addEventListener(
|
|
510
|
+
"mouseleave",
|
|
511
|
+
resumeAutoplay,
|
|
512
|
+
{ signal }
|
|
513
|
+
);
|
|
514
|
+
settings.feed.addEventListener(
|
|
515
|
+
"touchstart",
|
|
516
|
+
pauseAutoplay,
|
|
517
|
+
{ passive: true, signal }
|
|
518
|
+
);
|
|
519
|
+
settings.feed.addEventListener(
|
|
520
|
+
"touchend",
|
|
521
|
+
resumeAutoplay,
|
|
522
|
+
{ signal }
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
const startAutoplay = () => {
|
|
527
|
+
if (state.autoplayIntervalId) return;
|
|
528
|
+
const interval = settings.autoplayInterval ?? 3e3;
|
|
529
|
+
state.autoplayIntervalId = setInterval(() => {
|
|
530
|
+
if (!state.autoplayPaused) {
|
|
531
|
+
handleNavButtonClick("next");
|
|
532
|
+
}
|
|
533
|
+
}, interval);
|
|
534
|
+
};
|
|
535
|
+
const stopAutoplay = () => {
|
|
536
|
+
if (state.autoplayIntervalId) {
|
|
537
|
+
clearInterval(state.autoplayIntervalId);
|
|
538
|
+
state.autoplayIntervalId = null;
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
const pauseAutoplay = () => {
|
|
542
|
+
state.autoplayPaused = true;
|
|
543
|
+
};
|
|
544
|
+
const resumeAutoplay = () => {
|
|
545
|
+
state.autoplayPaused = false;
|
|
546
|
+
};
|
|
547
|
+
const goToIndex = (index) => {
|
|
548
|
+
const visibleSlides = getVisibleSlides();
|
|
549
|
+
const safeIndex = Math.max(0, Math.min(index, visibleSlides.length - 1));
|
|
550
|
+
const targetSlide = visibleSlides[safeIndex];
|
|
551
|
+
if (!targetSlide) return;
|
|
552
|
+
state.currentSlideIndex = safeIndex;
|
|
553
|
+
updateActiveThumb(settings.thumbs, safeIndex);
|
|
554
|
+
smoothScrollTo(targetSlide.offsetLeft);
|
|
555
|
+
};
|
|
556
|
+
const refresh = () => {
|
|
557
|
+
state.cachedFeedRect = null;
|
|
558
|
+
applySlideWidths();
|
|
559
|
+
updateScrollbar();
|
|
560
|
+
updateControlsVisibility();
|
|
561
|
+
};
|
|
562
|
+
const unload = () => {
|
|
563
|
+
stopAutoplay();
|
|
564
|
+
state.abortController.abort();
|
|
565
|
+
window.removeEventListener("resize", handleWindowResize);
|
|
566
|
+
if (state.updateThumbTimeout) {
|
|
567
|
+
clearTimeout(state.updateThumbTimeout);
|
|
568
|
+
}
|
|
569
|
+
if (state.scrollEndTimeout) {
|
|
570
|
+
clearTimeout(state.scrollEndTimeout);
|
|
571
|
+
}
|
|
572
|
+
if (dragState) {
|
|
573
|
+
cleanupDrag(dragState);
|
|
574
|
+
}
|
|
575
|
+
state.cachedFeedRect = null;
|
|
576
|
+
};
|
|
577
|
+
initAria(settings);
|
|
578
|
+
applySlideWidths();
|
|
579
|
+
updateControlsVisibility();
|
|
580
|
+
attachEventListeners();
|
|
581
|
+
updateScrollbar();
|
|
582
|
+
if (settings.autoplay) {
|
|
583
|
+
startAutoplay();
|
|
584
|
+
}
|
|
585
|
+
return {
|
|
586
|
+
goToIndex,
|
|
587
|
+
refresh,
|
|
588
|
+
unload,
|
|
589
|
+
play: startAutoplay,
|
|
590
|
+
pause: stopAutoplay,
|
|
591
|
+
next: () => handleNavButtonClick("next"),
|
|
592
|
+
prev: () => handleNavButtonClick("prev")
|
|
593
|
+
};
|
|
594
|
+
};
|
|
5
595
|
export {
|
|
6
|
-
|
|
596
|
+
createSlider,
|
|
597
|
+
easeInOutCubic,
|
|
598
|
+
easeOutCubic,
|
|
599
|
+
easeOutExpo,
|
|
600
|
+
easeOutQuad,
|
|
601
|
+
linear
|
|
7
602
|
};
|
|
8
603
|
//# sourceMappingURL=index.js.map
|