lazer-slider 1.0.7 → 1.0.8
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/README.md +41 -4
- package/dist/index.cjs +59 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +59 -48
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ A lightweight, accessible slider with smooth scroll-to-snap animations, drag-to-
|
|
|
9
9
|
- **Responsive** - Separate settings for mobile and desktop (slidesPerView, slidesPerScroll)
|
|
10
10
|
- **Loop Mode** - Infinite loop navigation
|
|
11
11
|
- **Autoplay** - Automatic slide advancement with pause on hover
|
|
12
|
+
- **Marquee Mode** - Continuous smooth scrolling with seamless infinite loop
|
|
12
13
|
- **Accessible** - Full ARIA support, keyboard navigation (arrow keys)
|
|
13
14
|
- **Lightweight** - Zero dependencies, ~20KB unminified
|
|
14
15
|
- **TypeScript** - Full type definitions included
|
|
@@ -120,8 +121,13 @@ slider.unload()
|
|
|
120
121
|
| `autoplay` | `boolean` | `false` | Enable automatic slide advancement |
|
|
121
122
|
| `autoplayInterval` | `number` | `3000` | Autoplay interval in milliseconds |
|
|
122
123
|
| `pauseOnHover` | `boolean` | `true` | Pause autoplay on hover/touch |
|
|
124
|
+
| `marquee` | `boolean` | `false` | Enable marquee mode (continuous scroll, overrides autoplay/loop) |
|
|
125
|
+
| `marqueeSpeed` | `number` | `50` | Marquee scroll speed in pixels per second |
|
|
126
|
+
| `marqueeDirection` | `'left' \| 'right'` | `'left'` | Marquee scroll direction |
|
|
123
127
|
| `easing` | `EasingFunction` | `easeOutExpo` | Custom easing function |
|
|
124
128
|
|
|
129
|
+
> **Note:** When `marquee` is enabled, it takes precedence over `loop` and `autoplay` settings. Marquee mode provides continuous smooth scrolling suitable for ticker-style content displays.
|
|
130
|
+
|
|
125
131
|
### Scrollbar (Optional)
|
|
126
132
|
|
|
127
133
|
| Option | Type | Default | Description |
|
|
@@ -166,9 +172,9 @@ slider.goToIndex(2)
|
|
|
166
172
|
slider.next()
|
|
167
173
|
slider.prev()
|
|
168
174
|
|
|
169
|
-
// Autoplay control
|
|
170
|
-
slider.play() // Start autoplay
|
|
171
|
-
slider.pause() // Stop autoplay
|
|
175
|
+
// Autoplay/Marquee control
|
|
176
|
+
slider.play() // Start autoplay or marquee
|
|
177
|
+
slider.pause() // Stop autoplay or marquee
|
|
172
178
|
|
|
173
179
|
// Recalculate dimensions (call after DOM changes)
|
|
174
180
|
slider.refresh()
|
|
@@ -258,6 +264,36 @@ const slider = createSlider({
|
|
|
258
264
|
})
|
|
259
265
|
```
|
|
260
266
|
|
|
267
|
+
### Marquee Mode
|
|
268
|
+
|
|
269
|
+
Create a continuous scrolling marquee effect with seamless infinite loop. Perfect for ticker-style content, logos, or announcements.
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
const marquee = createSlider({
|
|
273
|
+
feed: document.querySelector('.marquee-feed'),
|
|
274
|
+
slides: [...document.querySelectorAll('.marquee-item')],
|
|
275
|
+
marquee: true,
|
|
276
|
+
marqueeSpeed: 80, // 80 pixels per second
|
|
277
|
+
marqueeDirection: 'left', // or 'right'
|
|
278
|
+
pauseOnHover: true // Pause on hover (default: true)
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
// Control programmatically
|
|
282
|
+
marquee.pause() // Stop marquee
|
|
283
|
+
marquee.play() // Resume marquee
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**Key Features:**
|
|
287
|
+
- Continuous smooth scrolling (not slide-by-slide)
|
|
288
|
+
- Frame-rate independent animation for consistent speed
|
|
289
|
+
- Automatic content cloning for seamless infinite loop
|
|
290
|
+
- Pause on hover/touch support
|
|
291
|
+
- Works with any number of slides
|
|
292
|
+
|
|
293
|
+
**When to use Marquee vs Autoplay:**
|
|
294
|
+
- **Marquee**: For continuous ticker-style scrolling (news tickers, logo carousels, announcements)
|
|
295
|
+
- **Autoplay**: For slide-by-slide carousel navigation (image galleries, product showcases)
|
|
296
|
+
|
|
261
297
|
### Bullets/Dots Navigation
|
|
262
298
|
|
|
263
299
|
Use the `thumbs` option to add clickable dots that navigate to specific slides. The slider automatically manages the `active` class.
|
|
@@ -438,7 +474,8 @@ import {
|
|
|
438
474
|
type EasingFunction,
|
|
439
475
|
type ScrollParams,
|
|
440
476
|
type ScrollStartParams,
|
|
441
|
-
type SliderDirection
|
|
477
|
+
type SliderDirection,
|
|
478
|
+
type MarqueeDirection
|
|
442
479
|
} from 'lazer-slider'
|
|
443
480
|
```
|
|
444
481
|
|
package/dist/index.cjs
CHANGED
|
@@ -268,10 +268,34 @@ var cleanupDrag = (state) => {
|
|
|
268
268
|
// src/core/marquee.ts
|
|
269
269
|
var createMarqueeState = () => ({
|
|
270
270
|
initialized: false,
|
|
271
|
-
clonedSlides: []
|
|
271
|
+
clonedSlides: [],
|
|
272
|
+
styleElement: null
|
|
272
273
|
});
|
|
274
|
+
var injectMarqueeKeyframes = () => {
|
|
275
|
+
const existingStyle = document.getElementById("lazer-marquee-keyframes");
|
|
276
|
+
if (existingStyle) {
|
|
277
|
+
return existingStyle;
|
|
278
|
+
}
|
|
279
|
+
const style = document.createElement("style");
|
|
280
|
+
style.id = "lazer-marquee-keyframes";
|
|
281
|
+
style.textContent = `
|
|
282
|
+
@keyframes lazer-marquee-scroll {
|
|
283
|
+
from {
|
|
284
|
+
transform: translateX(0);
|
|
285
|
+
}
|
|
286
|
+
to {
|
|
287
|
+
transform: translateX(calc(-1 * var(--lazer-marquee-distance) * 1px));
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
`;
|
|
291
|
+
document.head.appendChild(style);
|
|
292
|
+
return style;
|
|
293
|
+
};
|
|
273
294
|
var setupMarqueeClones = (settings, marqueeState) => {
|
|
274
295
|
if (marqueeState.initialized) return;
|
|
296
|
+
marqueeState.styleElement = injectMarqueeKeyframes();
|
|
297
|
+
settings.feed.style.display = "flex";
|
|
298
|
+
settings.feed.style.willChange = "transform";
|
|
275
299
|
settings.slides.forEach((slide) => {
|
|
276
300
|
const clone = slide.cloneNode(true);
|
|
277
301
|
clone.setAttribute("data-lazer-marquee-clone", "true");
|
|
@@ -289,54 +313,34 @@ var cleanupMarqueeClones = (marqueeState) => {
|
|
|
289
313
|
marqueeState.clonedSlides = [];
|
|
290
314
|
marqueeState.initialized = false;
|
|
291
315
|
};
|
|
292
|
-
var
|
|
316
|
+
var setupMarqueeCss = (settings, state) => {
|
|
317
|
+
const scrollWidth = settings.feed.scrollWidth;
|
|
318
|
+
const speed = settings.marqueeSpeed ?? 50;
|
|
293
319
|
const direction = settings.marqueeDirection ?? "left";
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
if (settings.feed.scrollLeft <= 0) {
|
|
301
|
-
settings.feed.scrollLeft = halfWidth;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
320
|
+
const distance = scrollWidth / 2;
|
|
321
|
+
settings.feed.style.setProperty("--lazer-marquee-distance", String(distance));
|
|
322
|
+
const duration = distance / speed;
|
|
323
|
+
const animationDirection = direction === "right" ? "reverse" : "normal";
|
|
324
|
+
settings.feed.style.animation = `lazer-marquee-scroll ${duration}s linear infinite ${animationDirection}`;
|
|
325
|
+
settings.feed.style.animationPlayState = state.marqueePaused ? "paused" : "running";
|
|
304
326
|
};
|
|
305
327
|
var startMarquee = (settings, state) => {
|
|
306
|
-
|
|
307
|
-
const speed = settings.marqueeSpeed ?? 50;
|
|
308
|
-
const direction = settings.marqueeDirection ?? "left";
|
|
309
|
-
const directionMultiplier = direction === "left" ? 1 : -1;
|
|
310
|
-
if (direction === "right") {
|
|
311
|
-
settings.feed.scrollLeft = settings.feed.scrollWidth / 2;
|
|
312
|
-
}
|
|
313
|
-
const animate = (timestamp) => {
|
|
314
|
-
if (state.marqueePaused) {
|
|
315
|
-
state.marqueeLastTimestamp = timestamp;
|
|
316
|
-
state.marqueeAnimationId = requestAnimationFrame(animate);
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
const deltaTime = state.marqueeLastTimestamp ? (timestamp - state.marqueeLastTimestamp) / 1e3 : 0;
|
|
320
|
-
state.marqueeLastTimestamp = timestamp;
|
|
321
|
-
const movement = speed * deltaTime * directionMultiplier;
|
|
322
|
-
settings.feed.scrollLeft += movement;
|
|
323
|
-
handleMarqueeLoop(settings);
|
|
324
|
-
state.marqueeAnimationId = requestAnimationFrame(animate);
|
|
325
|
-
};
|
|
326
|
-
state.marqueeAnimationId = requestAnimationFrame(animate);
|
|
328
|
+
setupMarqueeCss(settings, state);
|
|
327
329
|
};
|
|
328
|
-
var stopMarquee = (state) => {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
330
|
+
var stopMarquee = (state, settings) => {
|
|
331
|
+
settings.feed.style.animation = "";
|
|
332
|
+
settings.feed.style.animationPlayState = "";
|
|
333
|
+
settings.feed.style.transform = "";
|
|
334
|
+
settings.feed.style.willChange = "";
|
|
335
|
+
settings.feed.style.removeProperty("--lazer-marquee-distance");
|
|
334
336
|
};
|
|
335
|
-
var pauseMarquee = (state) => {
|
|
337
|
+
var pauseMarquee = (state, settings) => {
|
|
336
338
|
state.marqueePaused = true;
|
|
339
|
+
settings.feed.style.animationPlayState = "paused";
|
|
337
340
|
};
|
|
338
|
-
var resumeMarquee = (state) => {
|
|
341
|
+
var resumeMarquee = (state, settings) => {
|
|
339
342
|
state.marqueePaused = false;
|
|
343
|
+
settings.feed.style.animationPlayState = "running";
|
|
340
344
|
};
|
|
341
345
|
var setupMarquee = (settings, state, marqueeState) => {
|
|
342
346
|
if (!settings.marquee) return;
|
|
@@ -347,22 +351,22 @@ var attachMarqueeEventListeners = (settings, state, signal) => {
|
|
|
347
351
|
if (!settings.marquee || settings.pauseOnHover === false) return;
|
|
348
352
|
settings.feed.addEventListener(
|
|
349
353
|
"mouseenter",
|
|
350
|
-
() => pauseMarquee(state),
|
|
354
|
+
() => pauseMarquee(state, settings),
|
|
351
355
|
{ signal }
|
|
352
356
|
);
|
|
353
357
|
settings.feed.addEventListener(
|
|
354
358
|
"mouseleave",
|
|
355
|
-
() => resumeMarquee(state),
|
|
359
|
+
() => resumeMarquee(state, settings),
|
|
356
360
|
{ signal }
|
|
357
361
|
);
|
|
358
362
|
settings.feed.addEventListener(
|
|
359
363
|
"touchstart",
|
|
360
|
-
() => pauseMarquee(state),
|
|
364
|
+
() => pauseMarquee(state, settings),
|
|
361
365
|
{ passive: true, signal }
|
|
362
366
|
);
|
|
363
367
|
settings.feed.addEventListener(
|
|
364
368
|
"touchend",
|
|
365
|
-
() => resumeMarquee(state),
|
|
369
|
+
() => resumeMarquee(state, settings),
|
|
366
370
|
{ signal }
|
|
367
371
|
);
|
|
368
372
|
};
|
|
@@ -791,10 +795,13 @@ var createSlider = (settings) => {
|
|
|
791
795
|
applySlideWidths();
|
|
792
796
|
updateScrollbar();
|
|
793
797
|
updateControlsVisibility();
|
|
798
|
+
if (settings.marquee && !state.marqueePaused) {
|
|
799
|
+
startMarquee(settings, state);
|
|
800
|
+
}
|
|
794
801
|
};
|
|
795
802
|
const unload = () => {
|
|
796
803
|
stopAutoplay();
|
|
797
|
-
stopMarquee(state);
|
|
804
|
+
stopMarquee(state, settings);
|
|
798
805
|
state.abortController.abort();
|
|
799
806
|
window.removeEventListener("resize", handleWindowResize);
|
|
800
807
|
if (state.updateThumbTimeout) {
|
|
@@ -825,14 +832,18 @@ var createSlider = (settings) => {
|
|
|
825
832
|
updateScrollbar();
|
|
826
833
|
const play = () => {
|
|
827
834
|
if (settings.marquee) {
|
|
828
|
-
|
|
835
|
+
if (state.marqueePaused) {
|
|
836
|
+
resumeMarquee(state, settings);
|
|
837
|
+
} else {
|
|
838
|
+
startMarquee(settings, state);
|
|
839
|
+
}
|
|
829
840
|
} else if (settings.autoplay) {
|
|
830
841
|
startAutoplay();
|
|
831
842
|
}
|
|
832
843
|
};
|
|
833
844
|
const pause = () => {
|
|
834
845
|
if (settings.marquee) {
|
|
835
|
-
|
|
846
|
+
pauseMarquee(state, settings);
|
|
836
847
|
} else {
|
|
837
848
|
stopAutoplay();
|
|
838
849
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/core/easing.ts","../src/core/accessibility.ts","../src/core/drag.ts","../src/core/marquee.ts","../src/core/slider.ts"],"sourcesContent":["// Main slider factory\nexport { createSlider } from './core/slider.js'\n\n// Types\nexport type {\n SliderSettings,\n SliderState,\n SliderDirection,\n MarqueeDirection,\n ScrollStartParams,\n ScrollParams,\n EasingFunction,\n Slider,\n DragState\n} from './core/types.js'\n\n// Easing functions\nexport {\n easeOutExpo,\n easeOutCubic,\n easeInOutCubic,\n easeOutQuad,\n linear\n} from './core/easing.js'\n","import type { EasingFunction } from './types.js'\n\n/**\n * Exponential ease-out - starts fast, decelerates smoothly\n * Best for scroll animations as it feels natural and responsive\n */\nexport const easeOutExpo: EasingFunction = (t) =>\n t === 1 ? 1 : 1 - Math.pow(2, -10 * t)\n\n/**\n * Cubic ease-out - smoother deceleration than exponential\n */\nexport const easeOutCubic: EasingFunction = (t) => 1 - Math.pow(1 - t, 3)\n\n/**\n * Cubic ease-in-out - smooth acceleration and deceleration\n */\nexport const easeInOutCubic: EasingFunction = (t) =>\n t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2\n\n/**\n * Quadratic ease-out - gentle deceleration\n */\nexport const easeOutQuad: EasingFunction = (t) => 1 - (1 - t) * (1 - t)\n\n/**\n * Linear - no easing, constant speed\n */\nexport const linear: EasingFunction = (t) => t\n","import type { SliderSettings } from './types.js'\n\n/**\n * Generate a unique ID for the slider\n */\nexport const generateSliderId = (): string =>\n `slider-${Math.random().toString(36).substring(2, 9)}`\n\n/**\n * Initialize ARIA attributes for accessibility\n */\nexport const initAria = (settings: SliderSettings): void => {\n const { feed, prevSlideButton, nextSlideButton, thumbs, slides } = settings\n\n // Ensure feed has an ID for aria-controls references\n if (!feed.id) {\n feed.id = generateSliderId()\n }\n\n // Set up feed as a scrollable region\n feed.setAttribute('role', 'region')\n feed.setAttribute('aria-label', 'Carousel')\n feed.setAttribute('aria-roledescription', 'carousel')\n\n // Remove tabindex to allow natural tab order\n feed.removeAttribute('tabindex')\n\n // Set up slides with proper roles\n slides.forEach((slide, index) => {\n slide.setAttribute('role', 'group')\n slide.setAttribute('aria-roledescription', 'slide')\n slide.setAttribute('aria-label', `Slide ${index + 1} of ${slides.length}`)\n })\n\n // Set up previous button\n if (prevSlideButton) {\n prevSlideButton.setAttribute('aria-label', 'Previous slide')\n prevSlideButton.setAttribute('aria-controls', feed.id)\n prevSlideButton.setAttribute('tabindex', '0')\n if (prevSlideButton.tagName !== 'BUTTON') {\n prevSlideButton.setAttribute('role', 'button')\n }\n }\n\n // Set up next button\n if (nextSlideButton) {\n nextSlideButton.setAttribute('aria-label', 'Next slide')\n nextSlideButton.setAttribute('aria-controls', feed.id)\n nextSlideButton.setAttribute('tabindex', '0')\n if (nextSlideButton.tagName !== 'BUTTON') {\n nextSlideButton.setAttribute('role', 'button')\n }\n }\n\n // Set up thumbnail/dot navigation\n if (thumbs?.length) {\n thumbs.forEach((thumb, index) => {\n if (thumb.tagName !== 'BUTTON') {\n thumb.setAttribute('role', 'button')\n }\n thumb.setAttribute('aria-label', `Go to slide ${index + 1}`)\n thumb.setAttribute('tabindex', '0')\n thumb.setAttribute('aria-controls', feed.id)\n })\n }\n}\n\n/**\n * Toggle visibility of navigation controls with proper ARIA handling\n */\nexport const toggleControlVisibility = (\n button: HTMLElement | null | undefined,\n shouldShow: boolean,\n feedElement?: HTMLElement\n): void => {\n if (!button) return\n\n // If hiding the button while it has focus, move focus to feed\n if (!shouldShow && button === document.activeElement && feedElement) {\n feedElement.focus()\n }\n\n // Update visual state with transition\n const transition = 'opacity 0.3s ease'\n const opacity = shouldShow ? '1' : '0'\n\n Object.assign(button.style, {\n opacity,\n transition,\n pointerEvents: shouldShow ? 'auto' : 'none'\n })\n\n // Update ARIA state\n if (!shouldShow) {\n button.setAttribute('aria-hidden', 'true')\n button.setAttribute('tabindex', '-1')\n } else {\n button.removeAttribute('aria-hidden')\n button.setAttribute('tabindex', '0')\n }\n\n // Hide from layout after transition if not visible\n if (!shouldShow) {\n setTimeout(() => {\n if (button.style.opacity === '0') {\n button.style.visibility = 'hidden'\n }\n }, 300)\n } else {\n button.style.visibility = 'visible'\n }\n}\n\n/**\n * Update active state on thumbnail elements\n */\nexport const updateActiveThumb = (\n thumbs: HTMLElement[] | undefined,\n currentIndex: number,\n activeClass: string = 'active'\n): void => {\n if (!thumbs?.length) return\n\n thumbs.forEach((thumb, index) => {\n const isActive = index === currentIndex\n thumb.classList.toggle(activeClass, isActive)\n thumb.setAttribute('aria-selected', isActive.toString())\n })\n}\n\n/**\n * Set up keyboard navigation for the slider\n */\nexport const setupKeyboardNavigation = (\n feed: HTMLElement,\n onPrev: () => void,\n onNext: () => void,\n abortSignal: AbortSignal\n): void => {\n feed.addEventListener(\n 'keydown',\n (event: KeyboardEvent) => {\n switch (event.key) {\n case 'ArrowLeft':\n event.preventDefault()\n onPrev()\n break\n case 'ArrowRight':\n event.preventDefault()\n onNext()\n break\n }\n },\n { signal: abortSignal }\n )\n\n // Make feed focusable for keyboard navigation\n if (!feed.hasAttribute('tabindex')) {\n feed.setAttribute('tabindex', '0')\n }\n}\n","import type { DragState, EasingFunction } from './types.js'\nimport { easeOutCubic } from './easing.js'\n\n/**\n * Configuration for drag behavior\n */\ninterface DragConfig {\n /** Feed element to enable dragging on */\n feed: HTMLElement\n /** Slide elements for snap-to-slide calculation */\n slides: HTMLElement[]\n /** AbortSignal for cleanup */\n abortSignal: AbortSignal\n /** Callback to smooth scroll to a position */\n smoothScrollTo: (target: number, easing?: EasingFunction) => void\n /** Callback when drag ends (for updating state) */\n onDragEnd?: () => void\n}\n\n/**\n * Create initial drag state\n */\nexport const createDragState = (): DragState => ({\n isDragging: false,\n startX: 0,\n startScrollLeft: 0,\n velocity: 0,\n lastX: 0,\n lastTime: 0,\n momentumId: null\n})\n\n/**\n * Find the nearest slide to snap to after drag\n */\nconst findNearestSlide = (\n feed: HTMLElement,\n slides: HTMLElement[]\n): HTMLElement | null => {\n const feedRect = feed.getBoundingClientRect()\n const feedCenter = feedRect.left + feedRect.width / 2\n\n let nearestSlide: HTMLElement | null = null\n let minDistance = Infinity\n\n for (const slide of slides) {\n if (slide.offsetParent === null) continue // Skip hidden slides\n\n const slideRect = slide.getBoundingClientRect()\n const slideCenter = slideRect.left + slideRect.width / 2\n const distance = Math.abs(feedCenter - slideCenter)\n\n if (distance < minDistance) {\n minDistance = distance\n nearestSlide = slide\n }\n }\n\n return nearestSlide\n}\n\n/**\n * Apply momentum scrolling after drag release\n */\nconst applyMomentum = (\n state: DragState,\n feed: HTMLElement,\n slides: HTMLElement[],\n smoothScrollTo: (target: number, easing?: EasingFunction) => void,\n onDragEnd?: () => void\n): void => {\n const friction = 0.95\n const minVelocity = 0.5\n\n const animate = () => {\n if (Math.abs(state.velocity) < minVelocity) {\n state.momentumId = null\n\n // Snap to nearest slide\n const nearestSlide = findNearestSlide(feed, slides)\n if (nearestSlide) {\n smoothScrollTo(nearestSlide.offsetLeft, easeOutCubic)\n }\n\n onDragEnd?.()\n return\n }\n\n feed.scrollLeft += state.velocity\n state.velocity *= friction\n\n state.momentumId = requestAnimationFrame(animate)\n }\n\n state.momentumId = requestAnimationFrame(animate)\n}\n\n/**\n * Get X position from mouse or touch event\n */\nconst getEventX = (event: MouseEvent | TouchEvent): number => {\n if ('touches' in event) {\n return event.touches[0]?.clientX ?? 0\n }\n return event.clientX\n}\n\n/**\n * Set up drag-to-scroll functionality\n */\nexport const setupDragToScroll = (config: DragConfig): DragState => {\n const { feed, slides, abortSignal, smoothScrollTo, onDragEnd } = config\n const state = createDragState()\n\n const handleDragStart = (event: MouseEvent | TouchEvent) => {\n // Cancel any ongoing momentum animation\n if (state.momentumId !== null) {\n cancelAnimationFrame(state.momentumId)\n state.momentumId = null\n }\n\n state.isDragging = true\n state.startX = getEventX(event)\n state.startScrollLeft = feed.scrollLeft\n state.velocity = 0\n state.lastX = state.startX\n state.lastTime = performance.now()\n\n // Visual feedback\n feed.style.cursor = 'grabbing'\n feed.style.userSelect = 'none'\n\n // Prevent default for mouse to avoid text selection\n if (event.type === 'mousedown') {\n event.preventDefault()\n }\n }\n\n const handleDragMove = (event: MouseEvent | TouchEvent) => {\n if (!state.isDragging) return\n\n const currentX = getEventX(event)\n const currentTime = performance.now()\n const deltaX = state.startX - currentX\n const deltaTime = currentTime - state.lastTime\n\n // Update scroll position\n feed.scrollLeft = state.startScrollLeft + deltaX\n\n // Calculate velocity for momentum\n if (deltaTime > 0) {\n state.velocity = (state.lastX - currentX) / deltaTime * 16 // Normalize to ~60fps\n }\n\n state.lastX = currentX\n state.lastTime = currentTime\n\n // Prevent default to stop page scrolling on touch\n if (event.type === 'touchmove') {\n event.preventDefault()\n }\n }\n\n const handleDragEnd = () => {\n if (!state.isDragging) return\n\n state.isDragging = false\n\n // Reset visual feedback\n feed.style.cursor = 'grab'\n feed.style.userSelect = ''\n\n // Apply momentum if velocity is significant\n if (Math.abs(state.velocity) > 1) {\n applyMomentum(state, feed, slides, smoothScrollTo, onDragEnd)\n } else {\n // Snap to nearest slide immediately\n const nearestSlide = findNearestSlide(feed, slides)\n if (nearestSlide) {\n smoothScrollTo(nearestSlide.offsetLeft, easeOutCubic)\n }\n onDragEnd?.()\n }\n }\n\n // Set initial cursor\n feed.style.cursor = 'grab'\n\n // Mouse events\n feed.addEventListener('mousedown', handleDragStart, { signal: abortSignal })\n document.addEventListener('mousemove', handleDragMove, { signal: abortSignal })\n document.addEventListener('mouseup', handleDragEnd, { signal: abortSignal })\n\n // Touch events\n feed.addEventListener('touchstart', handleDragStart, {\n passive: true,\n signal: abortSignal\n })\n feed.addEventListener('touchmove', handleDragMove, {\n passive: false, // Need to prevent default\n signal: abortSignal\n })\n feed.addEventListener('touchend', handleDragEnd, { signal: abortSignal })\n\n // Handle mouse leaving the window\n document.addEventListener('mouseleave', handleDragEnd, { signal: abortSignal })\n\n return state\n}\n\n/**\n * Clean up drag state (cancel any pending animations)\n */\nexport const cleanupDrag = (state: DragState): void => {\n if (state.momentumId !== null) {\n cancelAnimationFrame(state.momentumId)\n state.momentumId = null\n }\n}\n","import type { SliderSettings, SliderState } from './types.js'\n\n/**\n * Marquee state - tracks cloned slides for marquee mode\n */\nexport interface MarqueeState {\n initialized: boolean\n clonedSlides: HTMLElement[]\n}\n\n/**\n * Create initial marquee state\n */\nexport const createMarqueeState = (): MarqueeState => ({\n initialized: false,\n clonedSlides: []\n})\n\n/**\n * Clone all slides and append for seamless marquee looping\n */\nexport const setupMarqueeClones = (\n settings: SliderSettings,\n marqueeState: MarqueeState\n): void => {\n if (marqueeState.initialized) return\n\n // Clone all original slides and append to create seamless loop\n settings.slides.forEach((slide) => {\n const clone = slide.cloneNode(true) as HTMLElement\n clone.setAttribute('data-lazer-marquee-clone', 'true')\n clone.setAttribute('aria-hidden', 'true')\n settings.feed.appendChild(clone)\n marqueeState.clonedSlides.push(clone)\n })\n\n marqueeState.initialized = true\n}\n\n/**\n * Remove marquee cloned slides from the DOM\n */\nexport const cleanupMarqueeClones = (marqueeState: MarqueeState): void => {\n if (!marqueeState.initialized) return\n\n marqueeState.clonedSlides.forEach((clone) => {\n clone.remove()\n })\n\n marqueeState.clonedSlides = []\n marqueeState.initialized = false\n}\n\n/**\n * Handle seamless repositioning when marquee reaches content boundaries\n */\nconst handleMarqueeLoop = (\n settings: SliderSettings\n): void => {\n const direction = settings.marqueeDirection ?? 'left'\n const halfWidth = settings.feed.scrollWidth / 2\n\n if (direction === 'left') {\n // When scrolled past halfway (cloned content), jump back to start\n if (settings.feed.scrollLeft >= halfWidth) {\n settings.feed.scrollLeft = settings.feed.scrollLeft - halfWidth\n }\n } else {\n // When scrolled before start, jump to halfway point\n if (settings.feed.scrollLeft <= 0) {\n settings.feed.scrollLeft = halfWidth\n }\n }\n}\n\n/**\n * Start marquee animation using requestAnimationFrame\n */\nexport const startMarquee = (\n settings: SliderSettings,\n state: SliderState\n): void => {\n if (state.marqueeAnimationId) return\n\n const speed = settings.marqueeSpeed ?? 50 // pixels per second\n const direction = settings.marqueeDirection ?? 'left'\n const directionMultiplier = direction === 'left' ? 1 : -1\n\n // Set initial scroll position for right-to-left marquee\n if (direction === 'right') {\n settings.feed.scrollLeft = settings.feed.scrollWidth / 2\n }\n\n const animate = (timestamp: number): void => {\n if (state.marqueePaused) {\n // When paused, just update timestamp to prevent jump on resume\n state.marqueeLastTimestamp = timestamp\n state.marqueeAnimationId = requestAnimationFrame(animate)\n return\n }\n\n // Calculate delta time for consistent speed regardless of frame rate\n const deltaTime = state.marqueeLastTimestamp\n ? (timestamp - state.marqueeLastTimestamp) / 1000\n : 0\n state.marqueeLastTimestamp = timestamp\n\n // Move scroll position based on speed and direction\n const movement = speed * deltaTime * directionMultiplier\n settings.feed.scrollLeft += movement\n\n // Handle seamless loop repositioning\n handleMarqueeLoop(settings)\n\n state.marqueeAnimationId = requestAnimationFrame(animate)\n }\n\n state.marqueeAnimationId = requestAnimationFrame(animate)\n}\n\n/**\n * Stop marquee animation\n */\nexport const stopMarquee = (state: SliderState): void => {\n if (state.marqueeAnimationId) {\n cancelAnimationFrame(state.marqueeAnimationId)\n state.marqueeAnimationId = null\n }\n state.marqueeLastTimestamp = 0\n}\n\n/**\n * Pause marquee animation (temporary, resumes on mouse leave)\n */\nexport const pauseMarquee = (state: SliderState): void => {\n state.marqueePaused = true\n}\n\n/**\n * Resume marquee animation after pause\n */\nexport const resumeMarquee = (state: SliderState): void => {\n state.marqueePaused = false\n}\n\n/**\n * Setup marquee mode (clones + animation)\n */\nexport const setupMarquee = (\n settings: SliderSettings,\n state: SliderState,\n marqueeState: MarqueeState\n): void => {\n if (!settings.marquee) return\n\n setupMarqueeClones(settings, marqueeState)\n startMarquee(settings, state)\n}\n\n/**\n * Attach marquee pause-on-hover event listeners\n */\nexport const attachMarqueeEventListeners = (\n settings: SliderSettings,\n state: SliderState,\n signal: AbortSignal\n): void => {\n if (!settings.marquee || settings.pauseOnHover === false) return\n\n settings.feed.addEventListener(\n 'mouseenter',\n () => pauseMarquee(state),\n { signal }\n )\n settings.feed.addEventListener(\n 'mouseleave',\n () => resumeMarquee(state),\n { signal }\n )\n settings.feed.addEventListener(\n 'touchstart',\n () => pauseMarquee(state),\n { passive: true, signal }\n )\n settings.feed.addEventListener(\n 'touchend',\n () => resumeMarquee(state),\n { signal }\n )\n}\n","import type {\n SliderSettings,\n SliderState,\n SliderDirection,\n EasingFunction,\n Slider,\n DragState\n} from './types.js'\nimport { easeOutExpo } from './easing.js'\nimport {\n initAria,\n toggleControlVisibility,\n updateActiveThumb,\n setupKeyboardNavigation\n} from './accessibility.js'\nimport { setupDragToScroll, cleanupDrag } from './drag.js'\nimport {\n createMarqueeState,\n setupMarquee,\n startMarquee,\n stopMarquee,\n cleanupMarqueeClones,\n attachMarqueeEventListeners\n} from './marquee.js'\n\n/**\n * Animation duration configuration\n */\nconst ANIMATION = {\n MIN_DURATION: 400,\n MAX_DURATION: 1000,\n SPEED_FACTOR: 1.5,\n SCROLL_END_DELAY: 50,\n THUMB_UPDATE_DELAY: 500\n} as const\n\n/**\n * Desktop breakpoint for responsive slidesPerScroll\n */\nconst DESKTOP_BREAKPOINT = '(min-width: 64rem)'\n\n/**\n * Create a slider instance\n */\nexport const createSlider = (settings: SliderSettings): Slider => {\n // Validate required settings\n if (!settings.feed) {\n throw new Error('lazer-slider: feed element is required')\n }\n\n if (!settings.slides?.length) {\n throw new Error('lazer-slider: slides array is required and must not be empty')\n }\n\n // Initialize state\n const state: SliderState = {\n currentSlideIndex: 0,\n isScrolling: false,\n ticking: false,\n cachedFeedRect: null,\n lastWidth: 0,\n updateThumbTimeout: null,\n scrollEndTimeout: null,\n abortController: new AbortController(),\n autoplayIntervalId: null,\n autoplayPaused: false,\n marqueeAnimationId: null,\n marqueePaused: false,\n marqueeLastTimestamp: 0\n }\n\n // Drag state (initialized if drag is enabled)\n let dragState: DragState | null = null\n\n // Loop state - tracks cloned slides for infinite loop\n const loopState = {\n initialized: false,\n clonedSlides: [] as HTMLElement[],\n realSlides: [...settings.slides] as HTMLElement[],\n clonesPerSide: 0\n }\n\n // Marquee state - tracks cloned slides for marquee mode\n const marqueeState = createMarqueeState()\n\n // Default easing function\n const easing = settings.easing ?? easeOutExpo\n\n /**\n * Get cached feed bounding rect (invalidated on resize)\n */\n const getFeedRect = (): DOMRect => {\n const currentWidth = settings.feed.clientWidth\n if (!state.cachedFeedRect || state.lastWidth !== currentWidth) {\n state.cachedFeedRect = settings.feed.getBoundingClientRect()\n state.lastWidth = currentWidth\n }\n return state.cachedFeedRect\n }\n\n /**\n * Get only visible slides (not hidden via CSS)\n */\n const getVisibleSlides = (): HTMLElement[] => {\n return settings.slides.filter((slide) => slide.offsetParent !== null)\n }\n\n /**\n * Check if we're on desktop based on breakpoint\n */\n const isDesktop = (): boolean => {\n return window.matchMedia(DESKTOP_BREAKPOINT).matches\n }\n\n /**\n * Get the number of slides to clone per side for infinite loop\n */\n const getLoopClonesCount = (): number => {\n const perView = isDesktop()\n ? settings.desktopSlidesPerView\n : settings.mobileSlidesPerView\n\n // Clone at least 1 slide, or the number of visible slides\n if (!perView || perView === 'auto') {\n return 1\n }\n return Math.ceil(perView)\n }\n\n /**\n * Setup cloned slides for infinite loop functionality\n * Clones first N slides to the end and last N slides to the beginning\n */\n const setupLoopClones = (): void => {\n if (!settings.loop || loopState.initialized) return\n\n const realSlides = loopState.realSlides\n const clonesCount = getLoopClonesCount()\n loopState.clonesPerSide = clonesCount\n\n // Clone last N slides and prepend to beginning\n for (let i = realSlides.length - clonesCount; i < realSlides.length; i++) {\n const slide = realSlides[i]\n if (!slide) continue\n\n const clone = slide.cloneNode(true) as HTMLElement\n clone.setAttribute('data-lazer-clone', 'prepend')\n clone.setAttribute('aria-hidden', 'true')\n settings.feed.insertBefore(clone, settings.feed.firstChild)\n loopState.clonedSlides.push(clone)\n }\n\n // Clone first N slides and append to end\n for (let i = 0; i < clonesCount; i++) {\n const slide = realSlides[i]\n if (!slide) continue\n\n const clone = slide.cloneNode(true) as HTMLElement\n clone.setAttribute('data-lazer-clone', 'append')\n clone.setAttribute('aria-hidden', 'true')\n settings.feed.appendChild(clone)\n loopState.clonedSlides.push(clone)\n }\n\n // Set initial scroll position to first real slide (after prepended clones)\n requestAnimationFrame(() => {\n const firstRealSlide = realSlides[0]\n if (firstRealSlide) {\n settings.feed.scrollLeft = firstRealSlide.offsetLeft\n }\n })\n\n loopState.initialized = true\n }\n\n /**\n * Handle repositioning when reaching clone boundaries\n * Silently jumps to the corresponding real slide without animation\n */\n const handleLoopReposition = (direction: SliderDirection): void => {\n if (!settings.loop || !loopState.initialized) return\n\n const realSlides = loopState.realSlides\n const totalRealSlides = realSlides.length\n\n if (direction === 'next') {\n const firstRealSlide = realSlides[0]\n if (firstRealSlide) {\n settings.feed.scrollLeft = firstRealSlide.offsetLeft\n }\n state.currentSlideIndex = 0\n } else {\n const lastRealSlide = realSlides[totalRealSlides - 1]\n if (lastRealSlide) {\n settings.feed.scrollLeft = lastRealSlide.offsetLeft\n }\n state.currentSlideIndex = totalRealSlides - 1\n }\n }\n\n /**\n * Remove cloned slides from the DOM\n */\n const cleanupLoopClones = (): void => {\n if (!loopState.initialized) return\n\n loopState.clonedSlides.forEach((clone) => {\n clone.remove()\n })\n\n loopState.clonedSlides = []\n loopState.initialized = false\n loopState.clonesPerSide = 0\n }\n\n /**\n * Apply slide widths based on slidesPerView settings\n */\n const applySlideWidths = (): void => {\n const perView = isDesktop()\n ? settings.desktopSlidesPerView\n : settings.mobileSlidesPerView\n\n const gap = settings.slideGap ?? 0\n\n // Set gap on the feed container\n if (gap > 0) {\n settings.feed.style.gap = `${gap}px`\n }\n\n // If 'auto' or not set, let CSS handle widths (natural sizing)\n if (!perView || perView === 'auto') {\n // Reset any previously set widths\n settings.slides.forEach((slide) => {\n slide.style.flex = ''\n slide.style.minWidth = ''\n })\n return\n }\n\n const totalGapWidth = gap * (perView - 1)\n const slideWidth = `calc((100% - ${totalGapWidth}px) / ${perView})`\n\n settings.slides.forEach((slide) => {\n slide.style.flex = `0 0 ${slideWidth}`\n slide.style.minWidth = slideWidth\n })\n }\n\n /**\n * Update scrollbar thumb width based on visible content\n */\n const updateScrollbar = (): void => {\n if (!settings.scrollbarThumb) return\n\n const feedRect = getFeedRect()\n const thumbWidth = (feedRect.width / settings.feed.scrollWidth) * 100\n settings.scrollbarThumb.style.width = `${thumbWidth}%`\n }\n\n /**\n * Update scrollbar thumb position based on scroll\n */\n const updateScrollbarPosition = (): void => {\n if (!settings.scrollbarThumb || !settings.scrollbarTrack) return\n\n const trackWidth = settings.scrollbarTrack.getBoundingClientRect().width\n const thumbWidth = settings.scrollbarThumb.getBoundingClientRect().width\n const totalTransform = trackWidth - thumbWidth\n\n const maxScroll = settings.feed.scrollWidth - settings.feed.clientWidth\n const scrollProgress = maxScroll > 0 ? settings.feed.scrollLeft / maxScroll : 0\n\n settings.scrollbarThumb.style.transform = `translateX(${totalTransform * scrollProgress}px)`\n }\n\n /**\n * Update visibility of navigation controls based on scroll position\n */\n const updateControlsVisibility = (): void => {\n const feedRect = getFeedRect()\n const isAtStart = settings.feed.scrollLeft <= 1 // Allow 1px tolerance\n const isAtEnd =\n settings.feed.scrollLeft + feedRect.width >= settings.feed.scrollWidth - 1\n const shouldHideScrollbar = settings.feed.scrollWidth <= feedRect.width\n\n // Hide/show scrollbar track\n if (settings.scrollbarTrack) {\n settings.scrollbarTrack.style.display = shouldHideScrollbar ? 'none' : 'block'\n }\n\n // When loop is enabled, always show both buttons (unless scrollbar should be hidden)\n if (settings.loop) {\n toggleControlVisibility(\n settings.prevSlideButton,\n !shouldHideScrollbar,\n settings.feed\n )\n toggleControlVisibility(\n settings.nextSlideButton,\n !shouldHideScrollbar,\n settings.feed\n )\n return\n }\n\n // Hide/show navigation buttons based on position\n toggleControlVisibility(\n settings.prevSlideButton,\n !isAtStart && !shouldHideScrollbar,\n settings.feed\n )\n toggleControlVisibility(\n settings.nextSlideButton,\n !isAtEnd && !shouldHideScrollbar,\n settings.feed\n )\n }\n\n /**\n * Calculate current slide index based on viewport position\n */\n const updateCurrentSlideIndex = (): void => {\n const feedRect = getFeedRect()\n\n // Use real slides for index calculation when loop is enabled\n const slidesToCheck = loopState.initialized\n ? loopState.realSlides\n : getVisibleSlides()\n\n // Find slides that are visible in the viewport\n const viewportVisibleSlides = slidesToCheck.filter((slide) => {\n const slideRect = slide.getBoundingClientRect()\n const tolerance = 20 // px tolerance for edge detection\n return (\n slideRect.right > feedRect.left + tolerance &&\n slideRect.left < feedRect.right - tolerance\n )\n })\n\n if (viewportVisibleSlides.length && viewportVisibleSlides[0]) {\n const newIndex = slidesToCheck.indexOf(viewportVisibleSlides[0])\n if (newIndex !== -1) {\n state.currentSlideIndex = newIndex\n settings.onScroll?.({\n currentScroll: settings.feed.scrollLeft,\n currentSlideIndex: state.currentSlideIndex\n })\n }\n }\n }\n\n /**\n * Smooth scroll to a target position using requestAnimationFrame\n */\n const smoothScrollTo = (\n target: number,\n customEasing: EasingFunction = easing,\n onComplete?: () => void\n ): void => {\n const start = settings.feed.scrollLeft\n const distance = Math.abs(target - start)\n\n // Dynamic duration based on distance\n const duration = Math.min(\n ANIMATION.MAX_DURATION,\n Math.max(ANIMATION.MIN_DURATION, distance / ANIMATION.SPEED_FACTOR)\n )\n\n const startTime = performance.now()\n\n const animateScroll = (currentTime: number): void => {\n const elapsed = (currentTime - startTime) / duration\n const progress = Math.min(elapsed, 1)\n const ease = customEasing(progress)\n\n settings.feed.scrollLeft = start + (target - start) * ease\n\n if (progress < 1) {\n requestAnimationFrame(animateScroll)\n } else {\n settings.feed.scrollLeft = target\n onComplete?.()\n }\n }\n\n requestAnimationFrame(animateScroll)\n }\n\n /**\n * Handle thumbnail click navigation\n */\n const handleThumbClick = (thumb: HTMLElement): void => {\n if (!settings.thumbs) return\n\n const index = settings.thumbs.indexOf(thumb)\n if (index === -1 || !settings.slides[index]) return\n\n state.currentSlideIndex = index\n updateActiveThumb(settings.thumbs, index)\n state.isScrolling = true\n\n // Clear existing timeout\n if (state.updateThumbTimeout) {\n clearTimeout(state.updateThumbTimeout)\n }\n\n // Reset scrolling flag after animation\n state.updateThumbTimeout = setTimeout(() => {\n state.isScrolling = false\n }, ANIMATION.THUMB_UPDATE_DELAY)\n\n smoothScrollTo(settings.slides[index].offsetLeft)\n }\n\n /**\n * Handle navigation button click (prev/next)\n */\n const handleNavButtonClick = (direction: SliderDirection): void => {\n const realSlides = loopState.initialized ? loopState.realSlides : getVisibleSlides()\n const slidesToScroll = isDesktop()\n ? settings.desktopSlidesPerScroll ?? 1\n : settings.mobileSlidesPerScroll ?? 1\n const totalRealSlides = realSlides.length\n\n // Update current index first\n updateCurrentSlideIndex()\n\n let targetSlide: HTMLElement | undefined\n let needsReposition = false\n\n if (direction === 'prev') {\n if (settings.loop && loopState.initialized && state.currentSlideIndex === 0) {\n\n const prependedClones = loopState.clonedSlides.filter(\n (clone) => clone.getAttribute('data-lazer-clone') === 'prepend'\n )\n targetSlide = prependedClones[prependedClones.length - 1]\n needsReposition = true\n } else {\n state.currentSlideIndex = Math.max(0, state.currentSlideIndex - slidesToScroll)\n targetSlide = realSlides[state.currentSlideIndex]\n }\n } else {\n if (settings.loop && loopState.initialized && state.currentSlideIndex >= totalRealSlides - 1) {\n const appendedClones = loopState.clonedSlides.filter(\n (clone) => clone.getAttribute('data-lazer-clone') === 'append'\n )\n targetSlide = appendedClones[0]\n needsReposition = true\n } else {\n state.currentSlideIndex = Math.min(\n totalRealSlides - 1,\n state.currentSlideIndex + slidesToScroll\n )\n targetSlide = realSlides[state.currentSlideIndex]\n }\n }\n\n if (!targetSlide) return\n\n // Fire scroll start callback\n settings.onScrollStart?.({\n currentScroll: settings.feed.scrollLeft,\n target: targetSlide,\n direction\n })\n\n if (needsReposition) {\n // Scroll to clone, then silently reposition to the real slide\n smoothScrollTo(targetSlide.offsetLeft, easing, () => {\n handleLoopReposition(direction)\n })\n } else {\n smoothScrollTo(targetSlide.offsetLeft)\n }\n }\n\n /**\n * Update all scroll-related state\n */\n const updateScrollPosition = (): void => {\n updateScrollbarPosition()\n updateControlsVisibility()\n updateCurrentSlideIndex()\n\n // Update thumbs only when not programmatically scrolling\n if (!state.isScrolling) {\n updateActiveThumb(settings.thumbs, state.currentSlideIndex)\n }\n\n // Clear existing timeout\n if (state.scrollEndTimeout) {\n clearTimeout(state.scrollEndTimeout)\n }\n\n // Fire scroll end callback after scroll settles\n state.scrollEndTimeout = setTimeout(() => {\n state.isScrolling = false\n settings.onScrollEnd?.({\n currentScroll: settings.feed.scrollLeft,\n currentSlideIndex: state.currentSlideIndex\n })\n }, ANIMATION.SCROLL_END_DELAY)\n }\n\n /**\n * Throttled scroll event handler\n */\n const handleFeedScroll = (): void => {\n if (!state.ticking) {\n requestAnimationFrame(() => {\n updateScrollPosition()\n state.ticking = false\n })\n state.ticking = true\n }\n }\n\n /**\n * Handle window resize\n */\n const handleWindowResize = (): void => {\n state.cachedFeedRect = null\n refresh()\n }\n\n /**\n * Attach all event listeners\n */\n const attachEventListeners = (): void => {\n const { signal } = state.abortController\n\n // Resize handler (not abortable, cleaned up manually)\n window.addEventListener('resize', handleWindowResize)\n\n // Scroll handler\n settings.feed.addEventListener('scroll', handleFeedScroll, {\n passive: true,\n signal\n })\n\n // Navigation button handlers\n if (settings.prevSlideButton) {\n settings.prevSlideButton.addEventListener(\n 'click',\n () => handleNavButtonClick('prev'),\n { signal }\n )\n }\n\n if (settings.nextSlideButton) {\n settings.nextSlideButton.addEventListener(\n 'click',\n () => handleNavButtonClick('next'),\n { signal }\n )\n }\n\n // Thumbnail handlers\n if (settings.thumbs?.length) {\n settings.thumbs[0]?.classList.add('active')\n settings.thumbs.forEach((thumb) => {\n thumb.addEventListener('click', () => handleThumbClick(thumb), { signal })\n })\n }\n\n // Keyboard navigation\n setupKeyboardNavigation(\n settings.feed,\n () => handleNavButtonClick('prev'),\n () => handleNavButtonClick('next'),\n signal\n )\n\n // Drag-to-scroll\n if (settings.enableDragToScroll) {\n dragState = setupDragToScroll({\n feed: settings.feed,\n slides: settings.slides,\n abortSignal: signal,\n smoothScrollTo,\n onDragEnd: () => {\n updateCurrentSlideIndex()\n updateActiveThumb(settings.thumbs, state.currentSlideIndex)\n }\n })\n }\n\n // Autoplay pause on hover/touch\n if (settings.autoplay && settings.pauseOnHover !== false) {\n settings.feed.addEventListener(\n 'mouseenter',\n pauseAutoplay,\n { signal }\n )\n settings.feed.addEventListener(\n 'mouseleave',\n resumeAutoplay,\n { signal }\n )\n settings.feed.addEventListener(\n 'touchstart',\n pauseAutoplay,\n { passive: true, signal }\n )\n settings.feed.addEventListener(\n 'touchend',\n resumeAutoplay,\n { signal }\n )\n }\n\n // Marquee pause on hover/touch\n attachMarqueeEventListeners(settings, state, signal)\n }\n\n /**\n * Start autoplay\n */\n const startAutoplay = (): void => {\n if (state.autoplayIntervalId) return;\n\n const interval = settings.autoplayInterval ?? 3000\n state.autoplayIntervalId = setInterval(() => {\n if (!state.autoplayPaused) {\n handleNavButtonClick('next')\n }\n }, interval)\n }\n\n /**\n * Stop autoplay\n */\n const stopAutoplay = (): void => {\n if (state.autoplayIntervalId) {\n clearInterval(state.autoplayIntervalId)\n state.autoplayIntervalId = null\n }\n }\n\n /**\n * Pause autoplay (temporary, resumes on mouse leave)\n */\n const pauseAutoplay = (): void => {\n state.autoplayPaused = true\n }\n\n /**\n * Resume autoplay after pause\n */\n const resumeAutoplay = (): void => {\n state.autoplayPaused = false\n }\n\n /**\n * Navigate to a specific slide by index\n */\n const goToIndex = (index: number): void => {\n // Use real slides when loop is enabled, otherwise use visible slides\n const slides = loopState.initialized ? loopState.realSlides : getVisibleSlides()\n const safeIndex = Math.max(0, Math.min(index, slides.length - 1))\n const targetSlide = slides[safeIndex]\n\n if (!targetSlide) return\n\n state.currentSlideIndex = safeIndex\n updateActiveThumb(settings.thumbs, safeIndex)\n smoothScrollTo(targetSlide.offsetLeft)\n }\n\n /**\n * Refresh slider state (recalculate dimensions, update controls)\n */\n const refresh = (): void => {\n state.cachedFeedRect = null\n applySlideWidths()\n updateScrollbar()\n updateControlsVisibility()\n }\n\n /**\n * Clean up all event listeners and timers\n */\n const unload = (): void => {\n // Stop autoplay and marquee\n stopAutoplay()\n stopMarquee(state)\n\n // Abort all event listeners\n state.abortController.abort()\n\n // Remove resize listener\n window.removeEventListener('resize', handleWindowResize)\n\n // Clear timeouts\n if (state.updateThumbTimeout) {\n clearTimeout(state.updateThumbTimeout)\n }\n if (state.scrollEndTimeout) {\n clearTimeout(state.scrollEndTimeout)\n }\n\n // Clean up drag state\n if (dragState) {\n cleanupDrag(dragState)\n }\n\n // Clean up loop clones and marquee clones\n cleanupLoopClones()\n cleanupMarqueeClones(marqueeState)\n\n // Reset cached rect\n state.cachedFeedRect = null\n }\n\n // Initialize slider\n initAria(settings)\n applySlideWidths()\n\n // Marquee mode takes precedence over loop/autoplay\n if (settings.marquee) {\n setupMarquee(settings, state, marqueeState)\n } else {\n setupLoopClones()\n // Start autoplay if enabled (only in non-marquee mode)\n if (settings.autoplay) {\n startAutoplay()\n }\n }\n\n updateControlsVisibility()\n attachEventListeners()\n updateScrollbar()\n\n // Unified play/pause that works for both autoplay and marquee\n const play = (): void => {\n if (settings.marquee) {\n startMarquee(settings, state)\n } else if (settings.autoplay) {\n startAutoplay()\n }\n }\n\n const pause = (): void => {\n if (settings.marquee) {\n stopMarquee(state)\n } else {\n stopAutoplay()\n }\n }\n\n return {\n goToIndex,\n refresh,\n unload,\n play,\n pause,\n next: () => handleNavButtonClick('next'),\n prev: () => handleNavButtonClick('prev')\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/core/easing.ts","../src/core/accessibility.ts","../src/core/drag.ts","../src/core/marquee.ts","../src/core/slider.ts"],"sourcesContent":["// Main slider factory\nexport { createSlider } from './core/slider.js'\n\n// Types\nexport type {\n SliderSettings,\n SliderState,\n SliderDirection,\n MarqueeDirection,\n ScrollStartParams,\n ScrollParams,\n EasingFunction,\n Slider,\n DragState\n} from './core/types.js'\n\n// Easing functions\nexport {\n easeOutExpo,\n easeOutCubic,\n easeInOutCubic,\n easeOutQuad,\n linear\n} from './core/easing.js'\n","import type { EasingFunction } from './types.js'\n\n/**\n * Exponential ease-out - starts fast, decelerates smoothly\n * Best for scroll animations as it feels natural and responsive\n */\nexport const easeOutExpo: EasingFunction = (t) =>\n t === 1 ? 1 : 1 - Math.pow(2, -10 * t)\n\n/**\n * Cubic ease-out - smoother deceleration than exponential\n */\nexport const easeOutCubic: EasingFunction = (t) => 1 - Math.pow(1 - t, 3)\n\n/**\n * Cubic ease-in-out - smooth acceleration and deceleration\n */\nexport const easeInOutCubic: EasingFunction = (t) =>\n t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2\n\n/**\n * Quadratic ease-out - gentle deceleration\n */\nexport const easeOutQuad: EasingFunction = (t) => 1 - (1 - t) * (1 - t)\n\n/**\n * Linear - no easing, constant speed\n */\nexport const linear: EasingFunction = (t) => t\n","import type { SliderSettings } from './types.js'\n\n/**\n * Generate a unique ID for the slider\n */\nexport const generateSliderId = (): string =>\n `slider-${Math.random().toString(36).substring(2, 9)}`\n\n/**\n * Initialize ARIA attributes for accessibility\n */\nexport const initAria = (settings: SliderSettings): void => {\n const { feed, prevSlideButton, nextSlideButton, thumbs, slides } = settings\n\n // Ensure feed has an ID for aria-controls references\n if (!feed.id) {\n feed.id = generateSliderId()\n }\n\n // Set up feed as a scrollable region\n feed.setAttribute('role', 'region')\n feed.setAttribute('aria-label', 'Carousel')\n feed.setAttribute('aria-roledescription', 'carousel')\n\n // Remove tabindex to allow natural tab order\n feed.removeAttribute('tabindex')\n\n // Set up slides with proper roles\n slides.forEach((slide, index) => {\n slide.setAttribute('role', 'group')\n slide.setAttribute('aria-roledescription', 'slide')\n slide.setAttribute('aria-label', `Slide ${index + 1} of ${slides.length}`)\n })\n\n // Set up previous button\n if (prevSlideButton) {\n prevSlideButton.setAttribute('aria-label', 'Previous slide')\n prevSlideButton.setAttribute('aria-controls', feed.id)\n prevSlideButton.setAttribute('tabindex', '0')\n if (prevSlideButton.tagName !== 'BUTTON') {\n prevSlideButton.setAttribute('role', 'button')\n }\n }\n\n // Set up next button\n if (nextSlideButton) {\n nextSlideButton.setAttribute('aria-label', 'Next slide')\n nextSlideButton.setAttribute('aria-controls', feed.id)\n nextSlideButton.setAttribute('tabindex', '0')\n if (nextSlideButton.tagName !== 'BUTTON') {\n nextSlideButton.setAttribute('role', 'button')\n }\n }\n\n // Set up thumbnail/dot navigation\n if (thumbs?.length) {\n thumbs.forEach((thumb, index) => {\n if (thumb.tagName !== 'BUTTON') {\n thumb.setAttribute('role', 'button')\n }\n thumb.setAttribute('aria-label', `Go to slide ${index + 1}`)\n thumb.setAttribute('tabindex', '0')\n thumb.setAttribute('aria-controls', feed.id)\n })\n }\n}\n\n/**\n * Toggle visibility of navigation controls with proper ARIA handling\n */\nexport const toggleControlVisibility = (\n button: HTMLElement | null | undefined,\n shouldShow: boolean,\n feedElement?: HTMLElement\n): void => {\n if (!button) return\n\n // If hiding the button while it has focus, move focus to feed\n if (!shouldShow && button === document.activeElement && feedElement) {\n feedElement.focus()\n }\n\n // Update visual state with transition\n const transition = 'opacity 0.3s ease'\n const opacity = shouldShow ? '1' : '0'\n\n Object.assign(button.style, {\n opacity,\n transition,\n pointerEvents: shouldShow ? 'auto' : 'none'\n })\n\n // Update ARIA state\n if (!shouldShow) {\n button.setAttribute('aria-hidden', 'true')\n button.setAttribute('tabindex', '-1')\n } else {\n button.removeAttribute('aria-hidden')\n button.setAttribute('tabindex', '0')\n }\n\n // Hide from layout after transition if not visible\n if (!shouldShow) {\n setTimeout(() => {\n if (button.style.opacity === '0') {\n button.style.visibility = 'hidden'\n }\n }, 300)\n } else {\n button.style.visibility = 'visible'\n }\n}\n\n/**\n * Update active state on thumbnail elements\n */\nexport const updateActiveThumb = (\n thumbs: HTMLElement[] | undefined,\n currentIndex: number,\n activeClass: string = 'active'\n): void => {\n if (!thumbs?.length) return\n\n thumbs.forEach((thumb, index) => {\n const isActive = index === currentIndex\n thumb.classList.toggle(activeClass, isActive)\n thumb.setAttribute('aria-selected', isActive.toString())\n })\n}\n\n/**\n * Set up keyboard navigation for the slider\n */\nexport const setupKeyboardNavigation = (\n feed: HTMLElement,\n onPrev: () => void,\n onNext: () => void,\n abortSignal: AbortSignal\n): void => {\n feed.addEventListener(\n 'keydown',\n (event: KeyboardEvent) => {\n switch (event.key) {\n case 'ArrowLeft':\n event.preventDefault()\n onPrev()\n break\n case 'ArrowRight':\n event.preventDefault()\n onNext()\n break\n }\n },\n { signal: abortSignal }\n )\n\n // Make feed focusable for keyboard navigation\n if (!feed.hasAttribute('tabindex')) {\n feed.setAttribute('tabindex', '0')\n }\n}\n","import type { DragState, EasingFunction } from './types.js'\nimport { easeOutCubic } from './easing.js'\n\n/**\n * Configuration for drag behavior\n */\ninterface DragConfig {\n /** Feed element to enable dragging on */\n feed: HTMLElement\n /** Slide elements for snap-to-slide calculation */\n slides: HTMLElement[]\n /** AbortSignal for cleanup */\n abortSignal: AbortSignal\n /** Callback to smooth scroll to a position */\n smoothScrollTo: (target: number, easing?: EasingFunction) => void\n /** Callback when drag ends (for updating state) */\n onDragEnd?: () => void\n}\n\n/**\n * Create initial drag state\n */\nexport const createDragState = (): DragState => ({\n isDragging: false,\n startX: 0,\n startScrollLeft: 0,\n velocity: 0,\n lastX: 0,\n lastTime: 0,\n momentumId: null\n})\n\n/**\n * Find the nearest slide to snap to after drag\n */\nconst findNearestSlide = (\n feed: HTMLElement,\n slides: HTMLElement[]\n): HTMLElement | null => {\n const feedRect = feed.getBoundingClientRect()\n const feedCenter = feedRect.left + feedRect.width / 2\n\n let nearestSlide: HTMLElement | null = null\n let minDistance = Infinity\n\n for (const slide of slides) {\n if (slide.offsetParent === null) continue // Skip hidden slides\n\n const slideRect = slide.getBoundingClientRect()\n const slideCenter = slideRect.left + slideRect.width / 2\n const distance = Math.abs(feedCenter - slideCenter)\n\n if (distance < minDistance) {\n minDistance = distance\n nearestSlide = slide\n }\n }\n\n return nearestSlide\n}\n\n/**\n * Apply momentum scrolling after drag release\n */\nconst applyMomentum = (\n state: DragState,\n feed: HTMLElement,\n slides: HTMLElement[],\n smoothScrollTo: (target: number, easing?: EasingFunction) => void,\n onDragEnd?: () => void\n): void => {\n const friction = 0.95\n const minVelocity = 0.5\n\n const animate = () => {\n if (Math.abs(state.velocity) < minVelocity) {\n state.momentumId = null\n\n // Snap to nearest slide\n const nearestSlide = findNearestSlide(feed, slides)\n if (nearestSlide) {\n smoothScrollTo(nearestSlide.offsetLeft, easeOutCubic)\n }\n\n onDragEnd?.()\n return\n }\n\n feed.scrollLeft += state.velocity\n state.velocity *= friction\n\n state.momentumId = requestAnimationFrame(animate)\n }\n\n state.momentumId = requestAnimationFrame(animate)\n}\n\n/**\n * Get X position from mouse or touch event\n */\nconst getEventX = (event: MouseEvent | TouchEvent): number => {\n if ('touches' in event) {\n return event.touches[0]?.clientX ?? 0\n }\n return event.clientX\n}\n\n/**\n * Set up drag-to-scroll functionality\n */\nexport const setupDragToScroll = (config: DragConfig): DragState => {\n const { feed, slides, abortSignal, smoothScrollTo, onDragEnd } = config\n const state = createDragState()\n\n const handleDragStart = (event: MouseEvent | TouchEvent) => {\n // Cancel any ongoing momentum animation\n if (state.momentumId !== null) {\n cancelAnimationFrame(state.momentumId)\n state.momentumId = null\n }\n\n state.isDragging = true\n state.startX = getEventX(event)\n state.startScrollLeft = feed.scrollLeft\n state.velocity = 0\n state.lastX = state.startX\n state.lastTime = performance.now()\n\n // Visual feedback\n feed.style.cursor = 'grabbing'\n feed.style.userSelect = 'none'\n\n // Prevent default for mouse to avoid text selection\n if (event.type === 'mousedown') {\n event.preventDefault()\n }\n }\n\n const handleDragMove = (event: MouseEvent | TouchEvent) => {\n if (!state.isDragging) return\n\n const currentX = getEventX(event)\n const currentTime = performance.now()\n const deltaX = state.startX - currentX\n const deltaTime = currentTime - state.lastTime\n\n // Update scroll position\n feed.scrollLeft = state.startScrollLeft + deltaX\n\n // Calculate velocity for momentum\n if (deltaTime > 0) {\n state.velocity = (state.lastX - currentX) / deltaTime * 16 // Normalize to ~60fps\n }\n\n state.lastX = currentX\n state.lastTime = currentTime\n\n // Prevent default to stop page scrolling on touch\n if (event.type === 'touchmove') {\n event.preventDefault()\n }\n }\n\n const handleDragEnd = () => {\n if (!state.isDragging) return\n\n state.isDragging = false\n\n // Reset visual feedback\n feed.style.cursor = 'grab'\n feed.style.userSelect = ''\n\n // Apply momentum if velocity is significant\n if (Math.abs(state.velocity) > 1) {\n applyMomentum(state, feed, slides, smoothScrollTo, onDragEnd)\n } else {\n // Snap to nearest slide immediately\n const nearestSlide = findNearestSlide(feed, slides)\n if (nearestSlide) {\n smoothScrollTo(nearestSlide.offsetLeft, easeOutCubic)\n }\n onDragEnd?.()\n }\n }\n\n // Set initial cursor\n feed.style.cursor = 'grab'\n\n // Mouse events\n feed.addEventListener('mousedown', handleDragStart, { signal: abortSignal })\n document.addEventListener('mousemove', handleDragMove, { signal: abortSignal })\n document.addEventListener('mouseup', handleDragEnd, { signal: abortSignal })\n\n // Touch events\n feed.addEventListener('touchstart', handleDragStart, {\n passive: true,\n signal: abortSignal\n })\n feed.addEventListener('touchmove', handleDragMove, {\n passive: false, // Need to prevent default\n signal: abortSignal\n })\n feed.addEventListener('touchend', handleDragEnd, { signal: abortSignal })\n\n // Handle mouse leaving the window\n document.addEventListener('mouseleave', handleDragEnd, { signal: abortSignal })\n\n return state\n}\n\n/**\n * Clean up drag state (cancel any pending animations)\n */\nexport const cleanupDrag = (state: DragState): void => {\n if (state.momentumId !== null) {\n cancelAnimationFrame(state.momentumId)\n state.momentumId = null\n }\n}\n","import type { SliderSettings, SliderState } from './types.js'\n\n/**\n * Marquee state - tracks cloned slides for marquee mode\n */\nexport interface MarqueeState {\n initialized: boolean\n clonedSlides: HTMLElement[]\n styleElement: HTMLStyleElement | null\n}\n\n/**\n * Create initial marquee state\n */\nexport const createMarqueeState = (): MarqueeState => ({\n initialized: false,\n clonedSlides: [],\n styleElement: null\n})\n\n/**\n * Inject CSS keyframes for smooth marquee animation\n */\nconst injectMarqueeKeyframes = (): HTMLStyleElement => {\n // Check if keyframes already exist\n const existingStyle = document.getElementById('lazer-marquee-keyframes')\n if (existingStyle) {\n return existingStyle as HTMLStyleElement\n }\n\n const style = document.createElement('style')\n style.id = 'lazer-marquee-keyframes'\n style.textContent = `\n @keyframes lazer-marquee-scroll {\n from {\n transform: translateX(0);\n }\n to {\n transform: translateX(calc(-1 * var(--lazer-marquee-distance) * 1px));\n }\n }\n `\n document.head.appendChild(style)\n return style\n}\n\n/**\n * Clone all slides and append for seamless marquee looping\n */\nexport const setupMarqueeClones = (\n settings: SliderSettings,\n marqueeState: MarqueeState\n): void => {\n if (marqueeState.initialized) return\n\n // Inject CSS keyframes\n marqueeState.styleElement = injectMarqueeKeyframes()\n\n // Ensure feed has proper CSS for transform animation\n // Note: Parent container should have overflow: hidden for marquee effect\n settings.feed.style.display = 'flex'\n settings.feed.style.willChange = 'transform'\n\n // Clone all original slides and append to create seamless loop\n settings.slides.forEach((slide) => {\n const clone = slide.cloneNode(true) as HTMLElement\n clone.setAttribute('data-lazer-marquee-clone', 'true')\n clone.setAttribute('aria-hidden', 'true')\n settings.feed.appendChild(clone)\n marqueeState.clonedSlides.push(clone)\n })\n\n marqueeState.initialized = true\n}\n\n/**\n * Remove marquee cloned slides from the DOM\n */\nexport const cleanupMarqueeClones = (marqueeState: MarqueeState): void => {\n if (!marqueeState.initialized) return\n\n marqueeState.clonedSlides.forEach((clone) => {\n clone.remove()\n })\n\n marqueeState.clonedSlides = []\n marqueeState.initialized = false\n}\n\n/**\n * Setup marquee CSS animation for smooth hardware-accelerated scrolling\n */\nconst setupMarqueeCss = (\n settings: SliderSettings,\n state: SliderState\n): void => {\n const scrollWidth = settings.feed.scrollWidth\n const speed = settings.marqueeSpeed ?? 50 // pixels per second\n const direction = settings.marqueeDirection ?? 'left'\n const distance = scrollWidth / 2\n\n // Set CSS custom property for the exact pixel distance\n settings.feed.style.setProperty('--lazer-marquee-distance', String(distance))\n\n // Calculate duration: distance / speed\n const duration = distance / speed\n const animationDirection = direction === 'right' ? 'reverse' : 'normal'\n\n // Apply smooth CSS animation with hardware acceleration\n settings.feed.style.animation = `lazer-marquee-scroll ${duration}s linear infinite ${animationDirection}`\n settings.feed.style.animationPlayState = state.marqueePaused ? 'paused' : 'running'\n}\n\n/**\n * Start marquee animation using smooth CSS animations\n */\nexport const startMarquee = (\n settings: SliderSettings,\n state: SliderState\n): void => {\n // Setup CSS animation for smooth hardware-accelerated scrolling\n setupMarqueeCss(settings, state)\n}\n\n/**\n * Stop marquee animation\n */\nexport const stopMarquee = (state: SliderState, settings: SliderSettings): void => {\n // Remove animation\n settings.feed.style.animation = ''\n settings.feed.style.animationPlayState = ''\n\n // Reset transform and CSS custom property\n settings.feed.style.transform = ''\n settings.feed.style.willChange = ''\n settings.feed.style.removeProperty('--lazer-marquee-distance')\n}\n\n/**\n * Pause marquee animation (temporary, resumes on mouse leave)\n */\nexport const pauseMarquee = (state: SliderState, settings: SliderSettings): void => {\n state.marqueePaused = true\n settings.feed.style.animationPlayState = 'paused'\n}\n\n/**\n * Resume marquee animation after pause\n */\nexport const resumeMarquee = (state: SliderState, settings: SliderSettings): void => {\n state.marqueePaused = false\n settings.feed.style.animationPlayState = 'running'\n}\n\n/**\n * Setup marquee mode (clones + animation)\n */\nexport const setupMarquee = (\n settings: SliderSettings,\n state: SliderState,\n marqueeState: MarqueeState\n): void => {\n if (!settings.marquee) return\n\n setupMarqueeClones(settings, marqueeState)\n startMarquee(settings, state)\n}\n\n/**\n * Attach marquee pause-on-hover event listeners\n */\nexport const attachMarqueeEventListeners = (\n settings: SliderSettings,\n state: SliderState,\n signal: AbortSignal\n): void => {\n if (!settings.marquee || settings.pauseOnHover === false) return\n\n settings.feed.addEventListener(\n 'mouseenter',\n () => pauseMarquee(state, settings),\n { signal }\n )\n settings.feed.addEventListener(\n 'mouseleave',\n () => resumeMarquee(state, settings),\n { signal }\n )\n settings.feed.addEventListener(\n 'touchstart',\n () => pauseMarquee(state, settings),\n { passive: true, signal }\n )\n settings.feed.addEventListener(\n 'touchend',\n () => resumeMarquee(state, settings),\n { signal }\n )\n}\n","import type {\n SliderSettings,\n SliderState,\n SliderDirection,\n EasingFunction,\n Slider,\n DragState\n} from './types.js'\nimport { easeOutExpo } from './easing.js'\nimport {\n initAria,\n toggleControlVisibility,\n updateActiveThumb,\n setupKeyboardNavigation\n} from './accessibility.js'\nimport { setupDragToScroll, cleanupDrag } from './drag.js'\nimport {\n createMarqueeState,\n setupMarquee,\n startMarquee,\n stopMarquee,\n pauseMarquee,\n resumeMarquee,\n cleanupMarqueeClones,\n attachMarqueeEventListeners\n} from './marquee.js'\n\n/**\n * Animation duration configuration\n */\nconst ANIMATION = {\n MIN_DURATION: 400,\n MAX_DURATION: 1000,\n SPEED_FACTOR: 1.5,\n SCROLL_END_DELAY: 50,\n THUMB_UPDATE_DELAY: 500\n} as const\n\n/**\n * Desktop breakpoint for responsive slidesPerScroll\n */\nconst DESKTOP_BREAKPOINT = '(min-width: 64rem)'\n\n/**\n * Create a slider instance\n */\nexport const createSlider = (settings: SliderSettings): Slider => {\n // Validate required settings\n if (!settings.feed) {\n throw new Error('lazer-slider: feed element is required')\n }\n\n if (!settings.slides?.length) {\n throw new Error('lazer-slider: slides array is required and must not be empty')\n }\n\n // Initialize state\n const state: SliderState = {\n currentSlideIndex: 0,\n isScrolling: false,\n ticking: false,\n cachedFeedRect: null,\n lastWidth: 0,\n updateThumbTimeout: null,\n scrollEndTimeout: null,\n abortController: new AbortController(),\n autoplayIntervalId: null,\n autoplayPaused: false,\n marqueeAnimationId: null,\n marqueePaused: false,\n marqueeLastTimestamp: 0\n }\n\n // Drag state (initialized if drag is enabled)\n let dragState: DragState | null = null\n\n // Loop state - tracks cloned slides for infinite loop\n const loopState = {\n initialized: false,\n clonedSlides: [] as HTMLElement[],\n realSlides: [...settings.slides] as HTMLElement[],\n clonesPerSide: 0\n }\n\n // Marquee state - tracks cloned slides for marquee mode\n const marqueeState = createMarqueeState()\n\n // Default easing function\n const easing = settings.easing ?? easeOutExpo\n\n /**\n * Get cached feed bounding rect (invalidated on resize)\n */\n const getFeedRect = (): DOMRect => {\n const currentWidth = settings.feed.clientWidth\n if (!state.cachedFeedRect || state.lastWidth !== currentWidth) {\n state.cachedFeedRect = settings.feed.getBoundingClientRect()\n state.lastWidth = currentWidth\n }\n return state.cachedFeedRect\n }\n\n /**\n * Get only visible slides (not hidden via CSS)\n */\n const getVisibleSlides = (): HTMLElement[] => {\n return settings.slides.filter((slide) => slide.offsetParent !== null)\n }\n\n /**\n * Check if we're on desktop based on breakpoint\n */\n const isDesktop = (): boolean => {\n return window.matchMedia(DESKTOP_BREAKPOINT).matches\n }\n\n /**\n * Get the number of slides to clone per side for infinite loop\n */\n const getLoopClonesCount = (): number => {\n const perView = isDesktop()\n ? settings.desktopSlidesPerView\n : settings.mobileSlidesPerView\n\n // Clone at least 1 slide, or the number of visible slides\n if (!perView || perView === 'auto') {\n return 1\n }\n return Math.ceil(perView)\n }\n\n /**\n * Setup cloned slides for infinite loop functionality\n * Clones first N slides to the end and last N slides to the beginning\n */\n const setupLoopClones = (): void => {\n if (!settings.loop || loopState.initialized) return\n\n const realSlides = loopState.realSlides\n const clonesCount = getLoopClonesCount()\n loopState.clonesPerSide = clonesCount\n\n // Clone last N slides and prepend to beginning\n for (let i = realSlides.length - clonesCount; i < realSlides.length; i++) {\n const slide = realSlides[i]\n if (!slide) continue\n\n const clone = slide.cloneNode(true) as HTMLElement\n clone.setAttribute('data-lazer-clone', 'prepend')\n clone.setAttribute('aria-hidden', 'true')\n settings.feed.insertBefore(clone, settings.feed.firstChild)\n loopState.clonedSlides.push(clone)\n }\n\n // Clone first N slides and append to end\n for (let i = 0; i < clonesCount; i++) {\n const slide = realSlides[i]\n if (!slide) continue\n\n const clone = slide.cloneNode(true) as HTMLElement\n clone.setAttribute('data-lazer-clone', 'append')\n clone.setAttribute('aria-hidden', 'true')\n settings.feed.appendChild(clone)\n loopState.clonedSlides.push(clone)\n }\n\n // Set initial scroll position to first real slide (after prepended clones)\n requestAnimationFrame(() => {\n const firstRealSlide = realSlides[0]\n if (firstRealSlide) {\n settings.feed.scrollLeft = firstRealSlide.offsetLeft\n }\n })\n\n loopState.initialized = true\n }\n\n /**\n * Handle repositioning when reaching clone boundaries\n * Silently jumps to the corresponding real slide without animation\n */\n const handleLoopReposition = (direction: SliderDirection): void => {\n if (!settings.loop || !loopState.initialized) return\n\n const realSlides = loopState.realSlides\n const totalRealSlides = realSlides.length\n\n if (direction === 'next') {\n const firstRealSlide = realSlides[0]\n if (firstRealSlide) {\n settings.feed.scrollLeft = firstRealSlide.offsetLeft\n }\n state.currentSlideIndex = 0\n } else {\n const lastRealSlide = realSlides[totalRealSlides - 1]\n if (lastRealSlide) {\n settings.feed.scrollLeft = lastRealSlide.offsetLeft\n }\n state.currentSlideIndex = totalRealSlides - 1\n }\n }\n\n /**\n * Remove cloned slides from the DOM\n */\n const cleanupLoopClones = (): void => {\n if (!loopState.initialized) return\n\n loopState.clonedSlides.forEach((clone) => {\n clone.remove()\n })\n\n loopState.clonedSlides = []\n loopState.initialized = false\n loopState.clonesPerSide = 0\n }\n\n /**\n * Apply slide widths based on slidesPerView settings\n */\n const applySlideWidths = (): void => {\n const perView = isDesktop()\n ? settings.desktopSlidesPerView\n : settings.mobileSlidesPerView\n\n const gap = settings.slideGap ?? 0\n\n // Set gap on the feed container\n if (gap > 0) {\n settings.feed.style.gap = `${gap}px`\n }\n\n // If 'auto' or not set, let CSS handle widths (natural sizing)\n if (!perView || perView === 'auto') {\n // Reset any previously set widths\n settings.slides.forEach((slide) => {\n slide.style.flex = ''\n slide.style.minWidth = ''\n })\n return\n }\n\n const totalGapWidth = gap * (perView - 1)\n const slideWidth = `calc((100% - ${totalGapWidth}px) / ${perView})`\n\n settings.slides.forEach((slide) => {\n slide.style.flex = `0 0 ${slideWidth}`\n slide.style.minWidth = slideWidth\n })\n }\n\n /**\n * Update scrollbar thumb width based on visible content\n */\n const updateScrollbar = (): void => {\n if (!settings.scrollbarThumb) return\n\n const feedRect = getFeedRect()\n const thumbWidth = (feedRect.width / settings.feed.scrollWidth) * 100\n settings.scrollbarThumb.style.width = `${thumbWidth}%`\n }\n\n /**\n * Update scrollbar thumb position based on scroll\n */\n const updateScrollbarPosition = (): void => {\n if (!settings.scrollbarThumb || !settings.scrollbarTrack) return\n\n const trackWidth = settings.scrollbarTrack.getBoundingClientRect().width\n const thumbWidth = settings.scrollbarThumb.getBoundingClientRect().width\n const totalTransform = trackWidth - thumbWidth\n\n const maxScroll = settings.feed.scrollWidth - settings.feed.clientWidth\n const scrollProgress = maxScroll > 0 ? settings.feed.scrollLeft / maxScroll : 0\n\n settings.scrollbarThumb.style.transform = `translateX(${totalTransform * scrollProgress}px)`\n }\n\n /**\n * Update visibility of navigation controls based on scroll position\n */\n const updateControlsVisibility = (): void => {\n const feedRect = getFeedRect()\n const isAtStart = settings.feed.scrollLeft <= 1 // Allow 1px tolerance\n const isAtEnd =\n settings.feed.scrollLeft + feedRect.width >= settings.feed.scrollWidth - 1\n const shouldHideScrollbar = settings.feed.scrollWidth <= feedRect.width\n\n // Hide/show scrollbar track\n if (settings.scrollbarTrack) {\n settings.scrollbarTrack.style.display = shouldHideScrollbar ? 'none' : 'block'\n }\n\n // When loop is enabled, always show both buttons (unless scrollbar should be hidden)\n if (settings.loop) {\n toggleControlVisibility(\n settings.prevSlideButton,\n !shouldHideScrollbar,\n settings.feed\n )\n toggleControlVisibility(\n settings.nextSlideButton,\n !shouldHideScrollbar,\n settings.feed\n )\n return\n }\n\n // Hide/show navigation buttons based on position\n toggleControlVisibility(\n settings.prevSlideButton,\n !isAtStart && !shouldHideScrollbar,\n settings.feed\n )\n toggleControlVisibility(\n settings.nextSlideButton,\n !isAtEnd && !shouldHideScrollbar,\n settings.feed\n )\n }\n\n /**\n * Calculate current slide index based on viewport position\n */\n const updateCurrentSlideIndex = (): void => {\n const feedRect = getFeedRect()\n\n // Use real slides for index calculation when loop is enabled\n const slidesToCheck = loopState.initialized\n ? loopState.realSlides\n : getVisibleSlides()\n\n // Find slides that are visible in the viewport\n const viewportVisibleSlides = slidesToCheck.filter((slide) => {\n const slideRect = slide.getBoundingClientRect()\n const tolerance = 20 // px tolerance for edge detection\n return (\n slideRect.right > feedRect.left + tolerance &&\n slideRect.left < feedRect.right - tolerance\n )\n })\n\n if (viewportVisibleSlides.length && viewportVisibleSlides[0]) {\n const newIndex = slidesToCheck.indexOf(viewportVisibleSlides[0])\n if (newIndex !== -1) {\n state.currentSlideIndex = newIndex\n settings.onScroll?.({\n currentScroll: settings.feed.scrollLeft,\n currentSlideIndex: state.currentSlideIndex\n })\n }\n }\n }\n\n /**\n * Smooth scroll to a target position using requestAnimationFrame\n */\n const smoothScrollTo = (\n target: number,\n customEasing: EasingFunction = easing,\n onComplete?: () => void\n ): void => {\n const start = settings.feed.scrollLeft\n const distance = Math.abs(target - start)\n\n // Dynamic duration based on distance\n const duration = Math.min(\n ANIMATION.MAX_DURATION,\n Math.max(ANIMATION.MIN_DURATION, distance / ANIMATION.SPEED_FACTOR)\n )\n\n const startTime = performance.now()\n\n const animateScroll = (currentTime: number): void => {\n const elapsed = (currentTime - startTime) / duration\n const progress = Math.min(elapsed, 1)\n const ease = customEasing(progress)\n\n settings.feed.scrollLeft = start + (target - start) * ease\n\n if (progress < 1) {\n requestAnimationFrame(animateScroll)\n } else {\n settings.feed.scrollLeft = target\n onComplete?.()\n }\n }\n\n requestAnimationFrame(animateScroll)\n }\n\n /**\n * Handle thumbnail click navigation\n */\n const handleThumbClick = (thumb: HTMLElement): void => {\n if (!settings.thumbs) return\n\n const index = settings.thumbs.indexOf(thumb)\n if (index === -1 || !settings.slides[index]) return\n\n state.currentSlideIndex = index\n updateActiveThumb(settings.thumbs, index)\n state.isScrolling = true\n\n // Clear existing timeout\n if (state.updateThumbTimeout) {\n clearTimeout(state.updateThumbTimeout)\n }\n\n // Reset scrolling flag after animation\n state.updateThumbTimeout = setTimeout(() => {\n state.isScrolling = false\n }, ANIMATION.THUMB_UPDATE_DELAY)\n\n smoothScrollTo(settings.slides[index].offsetLeft)\n }\n\n /**\n * Handle navigation button click (prev/next)\n */\n const handleNavButtonClick = (direction: SliderDirection): void => {\n const realSlides = loopState.initialized ? loopState.realSlides : getVisibleSlides()\n const slidesToScroll = isDesktop()\n ? settings.desktopSlidesPerScroll ?? 1\n : settings.mobileSlidesPerScroll ?? 1\n const totalRealSlides = realSlides.length\n\n // Update current index first\n updateCurrentSlideIndex()\n\n let targetSlide: HTMLElement | undefined\n let needsReposition = false\n\n if (direction === 'prev') {\n if (settings.loop && loopState.initialized && state.currentSlideIndex === 0) {\n\n const prependedClones = loopState.clonedSlides.filter(\n (clone) => clone.getAttribute('data-lazer-clone') === 'prepend'\n )\n targetSlide = prependedClones[prependedClones.length - 1]\n needsReposition = true\n } else {\n state.currentSlideIndex = Math.max(0, state.currentSlideIndex - slidesToScroll)\n targetSlide = realSlides[state.currentSlideIndex]\n }\n } else {\n if (settings.loop && loopState.initialized && state.currentSlideIndex >= totalRealSlides - 1) {\n const appendedClones = loopState.clonedSlides.filter(\n (clone) => clone.getAttribute('data-lazer-clone') === 'append'\n )\n targetSlide = appendedClones[0]\n needsReposition = true\n } else {\n state.currentSlideIndex = Math.min(\n totalRealSlides - 1,\n state.currentSlideIndex + slidesToScroll\n )\n targetSlide = realSlides[state.currentSlideIndex]\n }\n }\n\n if (!targetSlide) return\n\n // Fire scroll start callback\n settings.onScrollStart?.({\n currentScroll: settings.feed.scrollLeft,\n target: targetSlide,\n direction\n })\n\n if (needsReposition) {\n // Scroll to clone, then silently reposition to the real slide\n smoothScrollTo(targetSlide.offsetLeft, easing, () => {\n handleLoopReposition(direction)\n })\n } else {\n smoothScrollTo(targetSlide.offsetLeft)\n }\n }\n\n /**\n * Update all scroll-related state\n */\n const updateScrollPosition = (): void => {\n updateScrollbarPosition()\n updateControlsVisibility()\n updateCurrentSlideIndex()\n\n // Update thumbs only when not programmatically scrolling\n if (!state.isScrolling) {\n updateActiveThumb(settings.thumbs, state.currentSlideIndex)\n }\n\n // Clear existing timeout\n if (state.scrollEndTimeout) {\n clearTimeout(state.scrollEndTimeout)\n }\n\n // Fire scroll end callback after scroll settles\n state.scrollEndTimeout = setTimeout(() => {\n state.isScrolling = false\n settings.onScrollEnd?.({\n currentScroll: settings.feed.scrollLeft,\n currentSlideIndex: state.currentSlideIndex\n })\n }, ANIMATION.SCROLL_END_DELAY)\n }\n\n /**\n * Throttled scroll event handler\n */\n const handleFeedScroll = (): void => {\n if (!state.ticking) {\n requestAnimationFrame(() => {\n updateScrollPosition()\n state.ticking = false\n })\n state.ticking = true\n }\n }\n\n /**\n * Handle window resize\n */\n const handleWindowResize = (): void => {\n state.cachedFeedRect = null\n refresh()\n }\n\n /**\n * Attach all event listeners\n */\n const attachEventListeners = (): void => {\n const { signal } = state.abortController\n\n // Resize handler (not abortable, cleaned up manually)\n window.addEventListener('resize', handleWindowResize)\n\n // Scroll handler\n settings.feed.addEventListener('scroll', handleFeedScroll, {\n passive: true,\n signal\n })\n\n // Navigation button handlers\n if (settings.prevSlideButton) {\n settings.prevSlideButton.addEventListener(\n 'click',\n () => handleNavButtonClick('prev'),\n { signal }\n )\n }\n\n if (settings.nextSlideButton) {\n settings.nextSlideButton.addEventListener(\n 'click',\n () => handleNavButtonClick('next'),\n { signal }\n )\n }\n\n // Thumbnail handlers\n if (settings.thumbs?.length) {\n settings.thumbs[0]?.classList.add('active')\n settings.thumbs.forEach((thumb) => {\n thumb.addEventListener('click', () => handleThumbClick(thumb), { signal })\n })\n }\n\n // Keyboard navigation\n setupKeyboardNavigation(\n settings.feed,\n () => handleNavButtonClick('prev'),\n () => handleNavButtonClick('next'),\n signal\n )\n\n // Drag-to-scroll\n if (settings.enableDragToScroll) {\n dragState = setupDragToScroll({\n feed: settings.feed,\n slides: settings.slides,\n abortSignal: signal,\n smoothScrollTo,\n onDragEnd: () => {\n updateCurrentSlideIndex()\n updateActiveThumb(settings.thumbs, state.currentSlideIndex)\n }\n })\n }\n\n // Autoplay pause on hover/touch\n if (settings.autoplay && settings.pauseOnHover !== false) {\n settings.feed.addEventListener(\n 'mouseenter',\n pauseAutoplay,\n { signal }\n )\n settings.feed.addEventListener(\n 'mouseleave',\n resumeAutoplay,\n { signal }\n )\n settings.feed.addEventListener(\n 'touchstart',\n pauseAutoplay,\n { passive: true, signal }\n )\n settings.feed.addEventListener(\n 'touchend',\n resumeAutoplay,\n { signal }\n )\n }\n\n // Marquee pause on hover/touch\n attachMarqueeEventListeners(settings, state, signal)\n }\n\n /**\n * Start autoplay\n */\n const startAutoplay = (): void => {\n if (state.autoplayIntervalId) return;\n\n const interval = settings.autoplayInterval ?? 3000\n state.autoplayIntervalId = setInterval(() => {\n if (!state.autoplayPaused) {\n handleNavButtonClick('next')\n }\n }, interval)\n }\n\n /**\n * Stop autoplay\n */\n const stopAutoplay = (): void => {\n if (state.autoplayIntervalId) {\n clearInterval(state.autoplayIntervalId)\n state.autoplayIntervalId = null\n }\n }\n\n /**\n * Pause autoplay (temporary, resumes on mouse leave)\n */\n const pauseAutoplay = (): void => {\n state.autoplayPaused = true\n }\n\n /**\n * Resume autoplay after pause\n */\n const resumeAutoplay = (): void => {\n state.autoplayPaused = false\n }\n\n /**\n * Navigate to a specific slide by index\n */\n const goToIndex = (index: number): void => {\n // Use real slides when loop is enabled, otherwise use visible slides\n const slides = loopState.initialized ? loopState.realSlides : getVisibleSlides()\n const safeIndex = Math.max(0, Math.min(index, slides.length - 1))\n const targetSlide = slides[safeIndex]\n\n if (!targetSlide) return\n\n state.currentSlideIndex = safeIndex\n updateActiveThumb(settings.thumbs, safeIndex)\n smoothScrollTo(targetSlide.offsetLeft)\n }\n\n /**\n * Refresh slider state (recalculate dimensions, update controls)\n */\n const refresh = (): void => {\n state.cachedFeedRect = null\n applySlideWidths()\n updateScrollbar()\n updateControlsVisibility()\n\n // Recalculate marquee animation if in marquee mode\n if (settings.marquee && !state.marqueePaused) {\n startMarquee(settings, state)\n }\n }\n\n /**\n * Clean up all event listeners and timers\n */\n const unload = (): void => {\n // Stop autoplay and marquee\n stopAutoplay()\n stopMarquee(state, settings)\n\n // Abort all event listeners\n state.abortController.abort()\n\n // Remove resize listener\n window.removeEventListener('resize', handleWindowResize)\n\n // Clear timeouts\n if (state.updateThumbTimeout) {\n clearTimeout(state.updateThumbTimeout)\n }\n if (state.scrollEndTimeout) {\n clearTimeout(state.scrollEndTimeout)\n }\n\n // Clean up drag state\n if (dragState) {\n cleanupDrag(dragState)\n }\n\n // Clean up loop clones and marquee clones\n cleanupLoopClones()\n cleanupMarqueeClones(marqueeState)\n\n // Reset cached rect\n state.cachedFeedRect = null\n }\n\n // Initialize slider\n initAria(settings)\n applySlideWidths()\n\n // Marquee mode takes precedence over loop/autoplay\n if (settings.marquee) {\n setupMarquee(settings, state, marqueeState)\n } else {\n setupLoopClones()\n // Start autoplay if enabled (only in non-marquee mode)\n if (settings.autoplay) {\n startAutoplay()\n }\n }\n\n updateControlsVisibility()\n attachEventListeners()\n updateScrollbar()\n\n // Unified play/pause that works for both autoplay and marquee\n const play = (): void => {\n if (settings.marquee) {\n if (state.marqueePaused) {\n resumeMarquee(state, settings)\n } else {\n startMarquee(settings, state)\n }\n } else if (settings.autoplay) {\n startAutoplay()\n }\n }\n\n const pause = (): void => {\n if (settings.marquee) {\n pauseMarquee(state, settings)\n } else {\n stopAutoplay()\n }\n }\n\n return {\n goToIndex,\n refresh,\n unload,\n play,\n pause,\n next: () => handleNavButtonClick('next'),\n prev: () => handleNavButtonClick('prev')\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACMO,IAAM,cAA8B,CAAC,MAC1C,MAAM,IAAI,IAAI,IAAI,KAAK,IAAI,GAAG,MAAM,CAAC;AAKhC,IAAM,eAA+B,CAAC,MAAM,IAAI,KAAK,IAAI,IAAI,GAAG,CAAC;AAKjE,IAAM,iBAAiC,CAAC,MAC7C,IAAI,MAAM,IAAI,IAAI,IAAI,IAAI,IAAI,KAAK,IAAI,KAAK,IAAI,GAAG,CAAC,IAAI;AAKnD,IAAM,cAA8B,CAAC,MAAM,KAAK,IAAI,MAAM,IAAI;AAK9D,IAAM,SAAyB,CAAC,MAAM;;;ACvBtC,IAAM,mBAAmB,MAC9B,UAAU,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,GAAG,CAAC,CAAC;AAK/C,IAAM,WAAW,CAAC,aAAmC;AAC1D,QAAM,EAAE,MAAM,iBAAiB,iBAAiB,QAAQ,OAAO,IAAI;AAGnE,MAAI,CAAC,KAAK,IAAI;AACZ,SAAK,KAAK,iBAAiB;AAAA,EAC7B;AAGA,OAAK,aAAa,QAAQ,QAAQ;AAClC,OAAK,aAAa,cAAc,UAAU;AAC1C,OAAK,aAAa,wBAAwB,UAAU;AAGpD,OAAK,gBAAgB,UAAU;AAG/B,SAAO,QAAQ,CAAC,OAAO,UAAU;AAC/B,UAAM,aAAa,QAAQ,OAAO;AAClC,UAAM,aAAa,wBAAwB,OAAO;AAClD,UAAM,aAAa,cAAc,SAAS,QAAQ,CAAC,OAAO,OAAO,MAAM,EAAE;AAAA,EAC3E,CAAC;AAGD,MAAI,iBAAiB;AACnB,oBAAgB,aAAa,cAAc,gBAAgB;AAC3D,oBAAgB,aAAa,iBAAiB,KAAK,EAAE;AACrD,oBAAgB,aAAa,YAAY,GAAG;AAC5C,QAAI,gBAAgB,YAAY,UAAU;AACxC,sBAAgB,aAAa,QAAQ,QAAQ;AAAA,IAC/C;AAAA,EACF;AAGA,MAAI,iBAAiB;AACnB,oBAAgB,aAAa,cAAc,YAAY;AACvD,oBAAgB,aAAa,iBAAiB,KAAK,EAAE;AACrD,oBAAgB,aAAa,YAAY,GAAG;AAC5C,QAAI,gBAAgB,YAAY,UAAU;AACxC,sBAAgB,aAAa,QAAQ,QAAQ;AAAA,IAC/C;AAAA,EACF;AAGA,MAAI,QAAQ,QAAQ;AAClB,WAAO,QAAQ,CAAC,OAAO,UAAU;AAC/B,UAAI,MAAM,YAAY,UAAU;AAC9B,cAAM,aAAa,QAAQ,QAAQ;AAAA,MACrC;AACA,YAAM,aAAa,cAAc,eAAe,QAAQ,CAAC,EAAE;AAC3D,YAAM,aAAa,YAAY,GAAG;AAClC,YAAM,aAAa,iBAAiB,KAAK,EAAE;AAAA,IAC7C,CAAC;AAAA,EACH;AACF;AAKO,IAAM,0BAA0B,CACrC,QACA,YACA,gBACS;AACT,MAAI,CAAC,OAAQ;AAGb,MAAI,CAAC,cAAc,WAAW,SAAS,iBAAiB,aAAa;AACnE,gBAAY,MAAM;AAAA,EACpB;AAGA,QAAM,aAAa;AACnB,QAAM,UAAU,aAAa,MAAM;AAEnC,SAAO,OAAO,OAAO,OAAO;AAAA,IAC1B;AAAA,IACA;AAAA,IACA,eAAe,aAAa,SAAS;AAAA,EACvC,CAAC;AAGD,MAAI,CAAC,YAAY;AACf,WAAO,aAAa,eAAe,MAAM;AACzC,WAAO,aAAa,YAAY,IAAI;AAAA,EACtC,OAAO;AACL,WAAO,gBAAgB,aAAa;AACpC,WAAO,aAAa,YAAY,GAAG;AAAA,EACrC;AAGA,MAAI,CAAC,YAAY;AACf,eAAW,MAAM;AACf,UAAI,OAAO,MAAM,YAAY,KAAK;AAChC,eAAO,MAAM,aAAa;AAAA,MAC5B;AAAA,IACF,GAAG,GAAG;AAAA,EACR,OAAO;AACL,WAAO,MAAM,aAAa;AAAA,EAC5B;AACF;AAKO,IAAM,oBAAoB,CAC/B,QACA,cACA,cAAsB,aACb;AACT,MAAI,CAAC,QAAQ,OAAQ;AAErB,SAAO,QAAQ,CAAC,OAAO,UAAU;AAC/B,UAAM,WAAW,UAAU;AAC3B,UAAM,UAAU,OAAO,aAAa,QAAQ;AAC5C,UAAM,aAAa,iBAAiB,SAAS,SAAS,CAAC;AAAA,EACzD,CAAC;AACH;AAKO,IAAM,0BAA0B,CACrC,MACA,QACA,QACA,gBACS;AACT,OAAK;AAAA,IACH;AAAA,IACA,CAAC,UAAyB;AACxB,cAAQ,MAAM,KAAK;AAAA,QACjB,KAAK;AACH,gBAAM,eAAe;AACrB,iBAAO;AACP;AAAA,QACF,KAAK;AACH,gBAAM,eAAe;AACrB,iBAAO;AACP;AAAA,MACJ;AAAA,IACF;AAAA,IACA,EAAE,QAAQ,YAAY;AAAA,EACxB;AAGA,MAAI,CAAC,KAAK,aAAa,UAAU,GAAG;AAClC,SAAK,aAAa,YAAY,GAAG;AAAA,EACnC;AACF;;;AC1IO,IAAM,kBAAkB,OAAkB;AAAA,EAC/C,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,iBAAiB;AAAA,EACjB,UAAU;AAAA,EACV,OAAO;AAAA,EACP,UAAU;AAAA,EACV,YAAY;AACd;AAKA,IAAM,mBAAmB,CACvB,MACA,WACuB;AACvB,QAAM,WAAW,KAAK,sBAAsB;AAC5C,QAAM,aAAa,SAAS,OAAO,SAAS,QAAQ;AAEpD,MAAI,eAAmC;AACvC,MAAI,cAAc;AAElB,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,iBAAiB,KAAM;AAEjC,UAAM,YAAY,MAAM,sBAAsB;AAC9C,UAAM,cAAc,UAAU,OAAO,UAAU,QAAQ;AACvD,UAAM,WAAW,KAAK,IAAI,aAAa,WAAW;AAElD,QAAI,WAAW,aAAa;AAC1B,oBAAc;AACd,qBAAe;AAAA,IACjB;AAAA,EACF;AAEA,SAAO;AACT;AAKA,IAAM,gBAAgB,CACpB,OACA,MACA,QACA,gBACA,cACS;AACT,QAAM,WAAW;AACjB,QAAM,cAAc;AAEpB,QAAM,UAAU,MAAM;AACpB,QAAI,KAAK,IAAI,MAAM,QAAQ,IAAI,aAAa;AAC1C,YAAM,aAAa;AAGnB,YAAM,eAAe,iBAAiB,MAAM,MAAM;AAClD,UAAI,cAAc;AAChB,uBAAe,aAAa,YAAY,YAAY;AAAA,MACtD;AAEA,kBAAY;AACZ;AAAA,IACF;AAEA,SAAK,cAAc,MAAM;AACzB,UAAM,YAAY;AAElB,UAAM,aAAa,sBAAsB,OAAO;AAAA,EAClD;AAEA,QAAM,aAAa,sBAAsB,OAAO;AAClD;AAKA,IAAM,YAAY,CAAC,UAA2C;AAC5D,MAAI,aAAa,OAAO;AACtB,WAAO,MAAM,QAAQ,CAAC,GAAG,WAAW;AAAA,EACtC;AACA,SAAO,MAAM;AACf;AAKO,IAAM,oBAAoB,CAAC,WAAkC;AAClE,QAAM,EAAE,MAAM,QAAQ,aAAa,gBAAgB,UAAU,IAAI;AACjE,QAAM,QAAQ,gBAAgB;AAE9B,QAAM,kBAAkB,CAAC,UAAmC;AAE1D,QAAI,MAAM,eAAe,MAAM;AAC7B,2BAAqB,MAAM,UAAU;AACrC,YAAM,aAAa;AAAA,IACrB;AAEA,UAAM,aAAa;AACnB,UAAM,SAAS,UAAU,KAAK;AAC9B,UAAM,kBAAkB,KAAK;AAC7B,UAAM,WAAW;AACjB,UAAM,QAAQ,MAAM;AACpB,UAAM,WAAW,YAAY,IAAI;AAGjC,SAAK,MAAM,SAAS;AACpB,SAAK,MAAM,aAAa;AAGxB,QAAI,MAAM,SAAS,aAAa;AAC9B,YAAM,eAAe;AAAA,IACvB;AAAA,EACF;AAEA,QAAM,iBAAiB,CAAC,UAAmC;AACzD,QAAI,CAAC,MAAM,WAAY;AAEvB,UAAM,WAAW,UAAU,KAAK;AAChC,UAAM,cAAc,YAAY,IAAI;AACpC,UAAM,SAAS,MAAM,SAAS;AAC9B,UAAM,YAAY,cAAc,MAAM;AAGtC,SAAK,aAAa,MAAM,kBAAkB;AAG1C,QAAI,YAAY,GAAG;AACjB,YAAM,YAAY,MAAM,QAAQ,YAAY,YAAY;AAAA,IAC1D;AAEA,UAAM,QAAQ;AACd,UAAM,WAAW;AAGjB,QAAI,MAAM,SAAS,aAAa;AAC9B,YAAM,eAAe;AAAA,IACvB;AAAA,EACF;AAEA,QAAM,gBAAgB,MAAM;AAC1B,QAAI,CAAC,MAAM,WAAY;AAEvB,UAAM,aAAa;AAGnB,SAAK,MAAM,SAAS;AACpB,SAAK,MAAM,aAAa;AAGxB,QAAI,KAAK,IAAI,MAAM,QAAQ,IAAI,GAAG;AAChC,oBAAc,OAAO,MAAM,QAAQ,gBAAgB,SAAS;AAAA,IAC9D,OAAO;AAEL,YAAM,eAAe,iBAAiB,MAAM,MAAM;AAClD,UAAI,cAAc;AAChB,uBAAe,aAAa,YAAY,YAAY;AAAA,MACtD;AACA,kBAAY;AAAA,IACd;AAAA,EACF;AAGA,OAAK,MAAM,SAAS;AAGpB,OAAK,iBAAiB,aAAa,iBAAiB,EAAE,QAAQ,YAAY,CAAC;AAC3E,WAAS,iBAAiB,aAAa,gBAAgB,EAAE,QAAQ,YAAY,CAAC;AAC9E,WAAS,iBAAiB,WAAW,eAAe,EAAE,QAAQ,YAAY,CAAC;AAG3E,OAAK,iBAAiB,cAAc,iBAAiB;AAAA,IACnD,SAAS;AAAA,IACT,QAAQ;AAAA,EACV,CAAC;AACD,OAAK,iBAAiB,aAAa,gBAAgB;AAAA,IACjD,SAAS;AAAA;AAAA,IACT,QAAQ;AAAA,EACV,CAAC;AACD,OAAK,iBAAiB,YAAY,eAAe,EAAE,QAAQ,YAAY,CAAC;AAGxE,WAAS,iBAAiB,cAAc,eAAe,EAAE,QAAQ,YAAY,CAAC;AAE9E,SAAO;AACT;AAKO,IAAM,cAAc,CAAC,UAA2B;AACrD,MAAI,MAAM,eAAe,MAAM;AAC7B,yBAAqB,MAAM,UAAU;AACrC,UAAM,aAAa;AAAA,EACrB;AACF;;;AC5MO,IAAM,qBAAqB,OAAqB;AAAA,EACrD,aAAa;AAAA,EACb,cAAc,CAAC;AAAA,EACf,cAAc;AAChB;AAKA,IAAM,yBAAyB,MAAwB;AAErD,QAAM,gBAAgB,SAAS,eAAe,yBAAyB;AACvE,MAAI,eAAe;AACjB,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,SAAS,cAAc,OAAO;AAC5C,QAAM,KAAK;AACX,QAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUpB,WAAS,KAAK,YAAY,KAAK;AAC/B,SAAO;AACT;AAKO,IAAM,qBAAqB,CAChC,UACA,iBACS;AACT,MAAI,aAAa,YAAa;AAG9B,eAAa,eAAe,uBAAuB;AAInD,WAAS,KAAK,MAAM,UAAU;AAC9B,WAAS,KAAK,MAAM,aAAa;AAGjC,WAAS,OAAO,QAAQ,CAAC,UAAU;AACjC,UAAM,QAAQ,MAAM,UAAU,IAAI;AAClC,UAAM,aAAa,4BAA4B,MAAM;AACrD,UAAM,aAAa,eAAe,MAAM;AACxC,aAAS,KAAK,YAAY,KAAK;AAC/B,iBAAa,aAAa,KAAK,KAAK;AAAA,EACtC,CAAC;AAED,eAAa,cAAc;AAC7B;AAKO,IAAM,uBAAuB,CAAC,iBAAqC;AACxE,MAAI,CAAC,aAAa,YAAa;AAE/B,eAAa,aAAa,QAAQ,CAAC,UAAU;AAC3C,UAAM,OAAO;AAAA,EACf,CAAC;AAED,eAAa,eAAe,CAAC;AAC7B,eAAa,cAAc;AAC7B;AAKA,IAAM,kBAAkB,CACtB,UACA,UACS;AACT,QAAM,cAAc,SAAS,KAAK;AAClC,QAAM,QAAQ,SAAS,gBAAgB;AACvC,QAAM,YAAY,SAAS,oBAAoB;AAC/C,QAAM,WAAW,cAAc;AAG/B,WAAS,KAAK,MAAM,YAAY,4BAA4B,OAAO,QAAQ,CAAC;AAG5E,QAAM,WAAW,WAAW;AAC5B,QAAM,qBAAqB,cAAc,UAAU,YAAY;AAG/D,WAAS,KAAK,MAAM,YAAY,wBAAwB,QAAQ,qBAAqB,kBAAkB;AACvG,WAAS,KAAK,MAAM,qBAAqB,MAAM,gBAAgB,WAAW;AAC5E;AAKO,IAAM,eAAe,CAC1B,UACA,UACS;AAET,kBAAgB,UAAU,KAAK;AACjC;AAKO,IAAM,cAAc,CAAC,OAAoB,aAAmC;AAEjF,WAAS,KAAK,MAAM,YAAY;AAChC,WAAS,KAAK,MAAM,qBAAqB;AAGzC,WAAS,KAAK,MAAM,YAAY;AAChC,WAAS,KAAK,MAAM,aAAa;AACjC,WAAS,KAAK,MAAM,eAAe,0BAA0B;AAC/D;AAKO,IAAM,eAAe,CAAC,OAAoB,aAAmC;AAClF,QAAM,gBAAgB;AACtB,WAAS,KAAK,MAAM,qBAAqB;AAC3C;AAKO,IAAM,gBAAgB,CAAC,OAAoB,aAAmC;AACnF,QAAM,gBAAgB;AACtB,WAAS,KAAK,MAAM,qBAAqB;AAC3C;AAKO,IAAM,eAAe,CAC1B,UACA,OACA,iBACS;AACT,MAAI,CAAC,SAAS,QAAS;AAEvB,qBAAmB,UAAU,YAAY;AACzC,eAAa,UAAU,KAAK;AAC9B;AAKO,IAAM,8BAA8B,CACzC,UACA,OACA,WACS;AACT,MAAI,CAAC,SAAS,WAAW,SAAS,iBAAiB,MAAO;AAE1D,WAAS,KAAK;AAAA,IACZ;AAAA,IACA,MAAM,aAAa,OAAO,QAAQ;AAAA,IAClC,EAAE,OAAO;AAAA,EACX;AACA,WAAS,KAAK;AAAA,IACZ;AAAA,IACA,MAAM,cAAc,OAAO,QAAQ;AAAA,IACnC,EAAE,OAAO;AAAA,EACX;AACA,WAAS,KAAK;AAAA,IACZ;AAAA,IACA,MAAM,aAAa,OAAO,QAAQ;AAAA,IAClC,EAAE,SAAS,MAAM,OAAO;AAAA,EAC1B;AACA,WAAS,KAAK;AAAA,IACZ;AAAA,IACA,MAAM,cAAc,OAAO,QAAQ;AAAA,IACnC,EAAE,OAAO;AAAA,EACX;AACF;;;ACxKA,IAAM,YAAY;AAAA,EAChB,cAAc;AAAA,EACd,cAAc;AAAA,EACd,cAAc;AAAA,EACd,kBAAkB;AAAA,EAClB,oBAAoB;AACtB;AAKA,IAAM,qBAAqB;AAKpB,IAAM,eAAe,CAAC,aAAqC;AAEhE,MAAI,CAAC,SAAS,MAAM;AAClB,UAAM,IAAI,MAAM,wCAAwC;AAAA,EAC1D;AAEA,MAAI,CAAC,SAAS,QAAQ,QAAQ;AAC5B,UAAM,IAAI,MAAM,8DAA8D;AAAA,EAChF;AAGA,QAAM,QAAqB;AAAA,IACzB,mBAAmB;AAAA,IACnB,aAAa;AAAA,IACb,SAAS;AAAA,IACT,gBAAgB;AAAA,IAChB,WAAW;AAAA,IACX,oBAAoB;AAAA,IACpB,kBAAkB;AAAA,IAClB,iBAAiB,IAAI,gBAAgB;AAAA,IACrC,oBAAoB;AAAA,IACpB,gBAAgB;AAAA,IAChB,oBAAoB;AAAA,IACpB,eAAe;AAAA,IACf,sBAAsB;AAAA,EACxB;AAGA,MAAI,YAA8B;AAGlC,QAAM,YAAY;AAAA,IAChB,aAAa;AAAA,IACb,cAAc,CAAC;AAAA,IACf,YAAY,CAAC,GAAG,SAAS,MAAM;AAAA,IAC/B,eAAe;AAAA,EACjB;AAGA,QAAM,eAAe,mBAAmB;AAGxC,QAAM,SAAS,SAAS,UAAU;AAKlC,QAAM,cAAc,MAAe;AACjC,UAAM,eAAe,SAAS,KAAK;AACnC,QAAI,CAAC,MAAM,kBAAkB,MAAM,cAAc,cAAc;AAC7D,YAAM,iBAAiB,SAAS,KAAK,sBAAsB;AAC3D,YAAM,YAAY;AAAA,IACpB;AACA,WAAO,MAAM;AAAA,EACf;AAKA,QAAM,mBAAmB,MAAqB;AAC5C,WAAO,SAAS,OAAO,OAAO,CAAC,UAAU,MAAM,iBAAiB,IAAI;AAAA,EACtE;AAKA,QAAM,YAAY,MAAe;AAC/B,WAAO,OAAO,WAAW,kBAAkB,EAAE;AAAA,EAC/C;AAKA,QAAM,qBAAqB,MAAc;AACvC,UAAM,UAAU,UAAU,IACtB,SAAS,uBACT,SAAS;AAGb,QAAI,CAAC,WAAW,YAAY,QAAQ;AAClC,aAAO;AAAA,IACT;AACA,WAAO,KAAK,KAAK,OAAO;AAAA,EAC1B;AAMA,QAAM,kBAAkB,MAAY;AAClC,QAAI,CAAC,SAAS,QAAQ,UAAU,YAAa;AAE7C,UAAM,aAAa,UAAU;AAC7B,UAAM,cAAc,mBAAmB;AACvC,cAAU,gBAAgB;AAG1B,aAAS,IAAI,WAAW,SAAS,aAAa,IAAI,WAAW,QAAQ,KAAK;AACxE,YAAM,QAAQ,WAAW,CAAC;AAC1B,UAAI,CAAC,MAAO;AAEZ,YAAM,QAAQ,MAAM,UAAU,IAAI;AAClC,YAAM,aAAa,oBAAoB,SAAS;AAChD,YAAM,aAAa,eAAe,MAAM;AACxC,eAAS,KAAK,aAAa,OAAO,SAAS,KAAK,UAAU;AAC1D,gBAAU,aAAa,KAAK,KAAK;AAAA,IACnC;AAGA,aAAS,IAAI,GAAG,IAAI,aAAa,KAAK;AACpC,YAAM,QAAQ,WAAW,CAAC;AAC1B,UAAI,CAAC,MAAO;AAEZ,YAAM,QAAQ,MAAM,UAAU,IAAI;AAClC,YAAM,aAAa,oBAAoB,QAAQ;AAC/C,YAAM,aAAa,eAAe,MAAM;AACxC,eAAS,KAAK,YAAY,KAAK;AAC/B,gBAAU,aAAa,KAAK,KAAK;AAAA,IACnC;AAGA,0BAAsB,MAAM;AAC1B,YAAM,iBAAiB,WAAW,CAAC;AACnC,UAAI,gBAAgB;AAClB,iBAAS,KAAK,aAAa,eAAe;AAAA,MAC5C;AAAA,IACF,CAAC;AAED,cAAU,cAAc;AAAA,EAC1B;AAMA,QAAM,uBAAuB,CAAC,cAAqC;AACjE,QAAI,CAAC,SAAS,QAAQ,CAAC,UAAU,YAAa;AAE9C,UAAM,aAAa,UAAU;AAC7B,UAAM,kBAAkB,WAAW;AAEnC,QAAI,cAAc,QAAQ;AACxB,YAAM,iBAAiB,WAAW,CAAC;AACnC,UAAI,gBAAgB;AAClB,iBAAS,KAAK,aAAa,eAAe;AAAA,MAC5C;AACA,YAAM,oBAAoB;AAAA,IAC5B,OAAO;AACL,YAAM,gBAAgB,WAAW,kBAAkB,CAAC;AACpD,UAAI,eAAe;AACjB,iBAAS,KAAK,aAAa,cAAc;AAAA,MAC3C;AACA,YAAM,oBAAoB,kBAAkB;AAAA,IAC9C;AAAA,EACF;AAKA,QAAM,oBAAoB,MAAY;AACpC,QAAI,CAAC,UAAU,YAAa;AAE5B,cAAU,aAAa,QAAQ,CAAC,UAAU;AACxC,YAAM,OAAO;AAAA,IACf,CAAC;AAED,cAAU,eAAe,CAAC;AAC1B,cAAU,cAAc;AACxB,cAAU,gBAAgB;AAAA,EAC5B;AAKA,QAAM,mBAAmB,MAAY;AACnC,UAAM,UAAU,UAAU,IACtB,SAAS,uBACT,SAAS;AAEb,UAAM,MAAM,SAAS,YAAY;AAGjC,QAAI,MAAM,GAAG;AACX,eAAS,KAAK,MAAM,MAAM,GAAG,GAAG;AAAA,IAClC;AAGA,QAAI,CAAC,WAAW,YAAY,QAAQ;AAElC,eAAS,OAAO,QAAQ,CAAC,UAAU;AACjC,cAAM,MAAM,OAAO;AACnB,cAAM,MAAM,WAAW;AAAA,MACzB,CAAC;AACD;AAAA,IACF;AAEA,UAAM,gBAAgB,OAAO,UAAU;AACvC,UAAM,aAAa,gBAAgB,aAAa,SAAS,OAAO;AAEhE,aAAS,OAAO,QAAQ,CAAC,UAAU;AACjC,YAAM,MAAM,OAAO,OAAO,UAAU;AACpC,YAAM,MAAM,WAAW;AAAA,IACzB,CAAC;AAAA,EACH;AAKA,QAAM,kBAAkB,MAAY;AAClC,QAAI,CAAC,SAAS,eAAgB;AAE9B,UAAM,WAAW,YAAY;AAC7B,UAAM,aAAc,SAAS,QAAQ,SAAS,KAAK,cAAe;AAClE,aAAS,eAAe,MAAM,QAAQ,GAAG,UAAU;AAAA,EACrD;AAKA,QAAM,0BAA0B,MAAY;AAC1C,QAAI,CAAC,SAAS,kBAAkB,CAAC,SAAS,eAAgB;AAE1D,UAAM,aAAa,SAAS,eAAe,sBAAsB,EAAE;AACnE,UAAM,aAAa,SAAS,eAAe,sBAAsB,EAAE;AACnE,UAAM,iBAAiB,aAAa;AAEpC,UAAM,YAAY,SAAS,KAAK,cAAc,SAAS,KAAK;AAC5D,UAAM,iBAAiB,YAAY,IAAI,SAAS,KAAK,aAAa,YAAY;AAE9E,aAAS,eAAe,MAAM,YAAY,cAAc,iBAAiB,cAAc;AAAA,EACzF;AAKA,QAAM,2BAA2B,MAAY;AAC3C,UAAM,WAAW,YAAY;AAC7B,UAAM,YAAY,SAAS,KAAK,cAAc;AAC9C,UAAM,UACJ,SAAS,KAAK,aAAa,SAAS,SAAS,SAAS,KAAK,cAAc;AAC3E,UAAM,sBAAsB,SAAS,KAAK,eAAe,SAAS;AAGlE,QAAI,SAAS,gBAAgB;AAC3B,eAAS,eAAe,MAAM,UAAU,sBAAsB,SAAS;AAAA,IACzE;AAGA,QAAI,SAAS,MAAM;AACjB;AAAA,QACE,SAAS;AAAA,QACT,CAAC;AAAA,QACD,SAAS;AAAA,MACX;AACA;AAAA,QACE,SAAS;AAAA,QACT,CAAC;AAAA,QACD,SAAS;AAAA,MACX;AACA;AAAA,IACF;AAGA;AAAA,MACE,SAAS;AAAA,MACT,CAAC,aAAa,CAAC;AAAA,MACf,SAAS;AAAA,IACX;AACA;AAAA,MACE,SAAS;AAAA,MACT,CAAC,WAAW,CAAC;AAAA,MACb,SAAS;AAAA,IACX;AAAA,EACF;AAKA,QAAM,0BAA0B,MAAY;AAC1C,UAAM,WAAW,YAAY;AAG7B,UAAM,gBAAgB,UAAU,cAC5B,UAAU,aACV,iBAAiB;AAGrB,UAAM,wBAAwB,cAAc,OAAO,CAAC,UAAU;AAC5D,YAAM,YAAY,MAAM,sBAAsB;AAC9C,YAAM,YAAY;AAClB,aACE,UAAU,QAAQ,SAAS,OAAO,aAClC,UAAU,OAAO,SAAS,QAAQ;AAAA,IAEtC,CAAC;AAED,QAAI,sBAAsB,UAAU,sBAAsB,CAAC,GAAG;AAC5D,YAAM,WAAW,cAAc,QAAQ,sBAAsB,CAAC,CAAC;AAC/D,UAAI,aAAa,IAAI;AACnB,cAAM,oBAAoB;AAC1B,iBAAS,WAAW;AAAA,UAClB,eAAe,SAAS,KAAK;AAAA,UAC7B,mBAAmB,MAAM;AAAA,QAC3B,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAKA,QAAM,iBAAiB,CACrB,QACA,eAA+B,QAC/B,eACS;AACT,UAAM,QAAQ,SAAS,KAAK;AAC5B,UAAM,WAAW,KAAK,IAAI,SAAS,KAAK;AAGxC,UAAM,WAAW,KAAK;AAAA,MACpB,UAAU;AAAA,MACV,KAAK,IAAI,UAAU,cAAc,WAAW,UAAU,YAAY;AAAA,IACpE;AAEA,UAAM,YAAY,YAAY,IAAI;AAElC,UAAM,gBAAgB,CAAC,gBAA8B;AACnD,YAAM,WAAW,cAAc,aAAa;AAC5C,YAAM,WAAW,KAAK,IAAI,SAAS,CAAC;AACpC,YAAM,OAAO,aAAa,QAAQ;AAElC,eAAS,KAAK,aAAa,SAAS,SAAS,SAAS;AAEtD,UAAI,WAAW,GAAG;AAChB,8BAAsB,aAAa;AAAA,MACrC,OAAO;AACL,iBAAS,KAAK,aAAa;AAC3B,qBAAa;AAAA,MACf;AAAA,IACF;AAEA,0BAAsB,aAAa;AAAA,EACrC;AAKA,QAAM,mBAAmB,CAAC,UAA6B;AACrD,QAAI,CAAC,SAAS,OAAQ;AAEtB,UAAM,QAAQ,SAAS,OAAO,QAAQ,KAAK;AAC3C,QAAI,UAAU,MAAM,CAAC,SAAS,OAAO,KAAK,EAAG;AAE7C,UAAM,oBAAoB;AAC1B,sBAAkB,SAAS,QAAQ,KAAK;AACxC,UAAM,cAAc;AAGpB,QAAI,MAAM,oBAAoB;AAC5B,mBAAa,MAAM,kBAAkB;AAAA,IACvC;AAGA,UAAM,qBAAqB,WAAW,MAAM;AAC1C,YAAM,cAAc;AAAA,IACtB,GAAG,UAAU,kBAAkB;AAE/B,mBAAe,SAAS,OAAO,KAAK,EAAE,UAAU;AAAA,EAClD;AAKA,QAAM,uBAAuB,CAAC,cAAqC;AACjE,UAAM,aAAa,UAAU,cAAc,UAAU,aAAa,iBAAiB;AACnF,UAAM,iBAAiB,UAAU,IAC7B,SAAS,0BAA0B,IACnC,SAAS,yBAAyB;AACtC,UAAM,kBAAkB,WAAW;AAGnC,4BAAwB;AAExB,QAAI;AACJ,QAAI,kBAAkB;AAEtB,QAAI,cAAc,QAAQ;AACxB,UAAI,SAAS,QAAQ,UAAU,eAAe,MAAM,sBAAsB,GAAG;AAE3E,cAAM,kBAAkB,UAAU,aAAa;AAAA,UAC7C,CAAC,UAAU,MAAM,aAAa,kBAAkB,MAAM;AAAA,QACxD;AACA,sBAAc,gBAAgB,gBAAgB,SAAS,CAAC;AACxD,0BAAkB;AAAA,MACpB,OAAO;AACL,cAAM,oBAAoB,KAAK,IAAI,GAAG,MAAM,oBAAoB,cAAc;AAC9E,sBAAc,WAAW,MAAM,iBAAiB;AAAA,MAClD;AAAA,IACF,OAAO;AACL,UAAI,SAAS,QAAQ,UAAU,eAAe,MAAM,qBAAqB,kBAAkB,GAAG;AAC5F,cAAM,iBAAiB,UAAU,aAAa;AAAA,UAC5C,CAAC,UAAU,MAAM,aAAa,kBAAkB,MAAM;AAAA,QACxD;AACA,sBAAc,eAAe,CAAC;AAC9B,0BAAkB;AAAA,MACpB,OAAO;AACL,cAAM,oBAAoB,KAAK;AAAA,UAC7B,kBAAkB;AAAA,UAClB,MAAM,oBAAoB;AAAA,QAC5B;AACA,sBAAc,WAAW,MAAM,iBAAiB;AAAA,MAClD;AAAA,IACF;AAEA,QAAI,CAAC,YAAa;AAGlB,aAAS,gBAAgB;AAAA,MACvB,eAAe,SAAS,KAAK;AAAA,MAC7B,QAAQ;AAAA,MACR;AAAA,IACF,CAAC;AAED,QAAI,iBAAiB;AAEnB,qBAAe,YAAY,YAAY,QAAQ,MAAM;AACnD,6BAAqB,SAAS;AAAA,MAChC,CAAC;AAAA,IACH,OAAO;AACL,qBAAe,YAAY,UAAU;AAAA,IACvC;AAAA,EACF;AAKA,QAAM,uBAAuB,MAAY;AACvC,4BAAwB;AACxB,6BAAyB;AACzB,4BAAwB;AAGxB,QAAI,CAAC,MAAM,aAAa;AACtB,wBAAkB,SAAS,QAAQ,MAAM,iBAAiB;AAAA,IAC5D;AAGA,QAAI,MAAM,kBAAkB;AAC1B,mBAAa,MAAM,gBAAgB;AAAA,IACrC;AAGA,UAAM,mBAAmB,WAAW,MAAM;AACxC,YAAM,cAAc;AACpB,eAAS,cAAc;AAAA,QACrB,eAAe,SAAS,KAAK;AAAA,QAC7B,mBAAmB,MAAM;AAAA,MAC3B,CAAC;AAAA,IACH,GAAG,UAAU,gBAAgB;AAAA,EAC/B;AAKA,QAAM,mBAAmB,MAAY;AACnC,QAAI,CAAC,MAAM,SAAS;AAClB,4BAAsB,MAAM;AAC1B,6BAAqB;AACrB,cAAM,UAAU;AAAA,MAClB,CAAC;AACD,YAAM,UAAU;AAAA,IAClB;AAAA,EACF;AAKA,QAAM,qBAAqB,MAAY;AACrC,UAAM,iBAAiB;AACvB,YAAQ;AAAA,EACV;AAKA,QAAM,uBAAuB,MAAY;AACvC,UAAM,EAAE,OAAO,IAAI,MAAM;AAGzB,WAAO,iBAAiB,UAAU,kBAAkB;AAGpD,aAAS,KAAK,iBAAiB,UAAU,kBAAkB;AAAA,MACzD,SAAS;AAAA,MACT;AAAA,IACF,CAAC;AAGD,QAAI,SAAS,iBAAiB;AAC5B,eAAS,gBAAgB;AAAA,QACvB;AAAA,QACA,MAAM,qBAAqB,MAAM;AAAA,QACjC,EAAE,OAAO;AAAA,MACX;AAAA,IACF;AAEA,QAAI,SAAS,iBAAiB;AAC5B,eAAS,gBAAgB;AAAA,QACvB;AAAA,QACA,MAAM,qBAAqB,MAAM;AAAA,QACjC,EAAE,OAAO;AAAA,MACX;AAAA,IACF;AAGA,QAAI,SAAS,QAAQ,QAAQ;AAC3B,eAAS,OAAO,CAAC,GAAG,UAAU,IAAI,QAAQ;AAC1C,eAAS,OAAO,QAAQ,CAAC,UAAU;AACjC,cAAM,iBAAiB,SAAS,MAAM,iBAAiB,KAAK,GAAG,EAAE,OAAO,CAAC;AAAA,MAC3E,CAAC;AAAA,IACH;AAGA;AAAA,MACE,SAAS;AAAA,MACT,MAAM,qBAAqB,MAAM;AAAA,MACjC,MAAM,qBAAqB,MAAM;AAAA,MACjC;AAAA,IACF;AAGA,QAAI,SAAS,oBAAoB;AAC/B,kBAAY,kBAAkB;AAAA,QAC5B,MAAM,SAAS;AAAA,QACf,QAAQ,SAAS;AAAA,QACjB,aAAa;AAAA,QACb;AAAA,QACA,WAAW,MAAM;AACf,kCAAwB;AACxB,4BAAkB,SAAS,QAAQ,MAAM,iBAAiB;AAAA,QAC5D;AAAA,MACF,CAAC;AAAA,IACH;AAGA,QAAI,SAAS,YAAY,SAAS,iBAAiB,OAAO;AACxD,eAAS,KAAK;AAAA,QACZ;AAAA,QACA;AAAA,QACA,EAAE,OAAO;AAAA,MACX;AACA,eAAS,KAAK;AAAA,QACZ;AAAA,QACA;AAAA,QACA,EAAE,OAAO;AAAA,MACX;AACA,eAAS,KAAK;AAAA,QACZ;AAAA,QACA;AAAA,QACA,EAAE,SAAS,MAAM,OAAO;AAAA,MAC1B;AACA,eAAS,KAAK;AAAA,QACZ;AAAA,QACA;AAAA,QACA,EAAE,OAAO;AAAA,MACX;AAAA,IACF;AAGA,gCAA4B,UAAU,OAAO,MAAM;AAAA,EACrD;AAKA,QAAM,gBAAgB,MAAY;AAChC,QAAI,MAAM,mBAAoB;AAE9B,UAAM,WAAW,SAAS,oBAAoB;AAC9C,UAAM,qBAAqB,YAAY,MAAM;AAC3C,UAAI,CAAC,MAAM,gBAAgB;AACzB,6BAAqB,MAAM;AAAA,MAC7B;AAAA,IACF,GAAG,QAAQ;AAAA,EACb;AAKA,QAAM,eAAe,MAAY;AAC/B,QAAI,MAAM,oBAAoB;AAC5B,oBAAc,MAAM,kBAAkB;AACtC,YAAM,qBAAqB;AAAA,IAC7B;AAAA,EACF;AAKA,QAAM,gBAAgB,MAAY;AAChC,UAAM,iBAAiB;AAAA,EACzB;AAKA,QAAM,iBAAiB,MAAY;AACjC,UAAM,iBAAiB;AAAA,EACzB;AAKA,QAAM,YAAY,CAAC,UAAwB;AAEzC,UAAM,SAAS,UAAU,cAAc,UAAU,aAAa,iBAAiB;AAC/E,UAAM,YAAY,KAAK,IAAI,GAAG,KAAK,IAAI,OAAO,OAAO,SAAS,CAAC,CAAC;AAChE,UAAM,cAAc,OAAO,SAAS;AAEpC,QAAI,CAAC,YAAa;AAElB,UAAM,oBAAoB;AAC1B,sBAAkB,SAAS,QAAQ,SAAS;AAC5C,mBAAe,YAAY,UAAU;AAAA,EACvC;AAKA,QAAM,UAAU,MAAY;AAC1B,UAAM,iBAAiB;AACvB,qBAAiB;AACjB,oBAAgB;AAChB,6BAAyB;AAGzB,QAAI,SAAS,WAAW,CAAC,MAAM,eAAe;AAC5C,mBAAa,UAAU,KAAK;AAAA,IAC9B;AAAA,EACF;AAKA,QAAM,SAAS,MAAY;AAEzB,iBAAa;AACb,gBAAY,OAAO,QAAQ;AAG3B,UAAM,gBAAgB,MAAM;AAG5B,WAAO,oBAAoB,UAAU,kBAAkB;AAGvD,QAAI,MAAM,oBAAoB;AAC5B,mBAAa,MAAM,kBAAkB;AAAA,IACvC;AACA,QAAI,MAAM,kBAAkB;AAC1B,mBAAa,MAAM,gBAAgB;AAAA,IACrC;AAGA,QAAI,WAAW;AACb,kBAAY,SAAS;AAAA,IACvB;AAGA,sBAAkB;AAClB,yBAAqB,YAAY;AAGjC,UAAM,iBAAiB;AAAA,EACzB;AAGA,WAAS,QAAQ;AACjB,mBAAiB;AAGjB,MAAI,SAAS,SAAS;AACpB,iBAAa,UAAU,OAAO,YAAY;AAAA,EAC5C,OAAO;AACL,oBAAgB;AAEhB,QAAI,SAAS,UAAU;AACrB,oBAAc;AAAA,IAChB;AAAA,EACF;AAEA,2BAAyB;AACzB,uBAAqB;AACrB,kBAAgB;AAGhB,QAAM,OAAO,MAAY;AACvB,QAAI,SAAS,SAAS;AACpB,UAAI,MAAM,eAAe;AACvB,sBAAc,OAAO,QAAQ;AAAA,MAC/B,OAAO;AACL,qBAAa,UAAU,KAAK;AAAA,MAC9B;AAAA,IACF,WAAW,SAAS,UAAU;AAC5B,oBAAc;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,QAAQ,MAAY;AACxB,QAAI,SAAS,SAAS;AACpB,mBAAa,OAAO,QAAQ;AAAA,IAC9B,OAAO;AACL,mBAAa;AAAA,IACf;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,MAAM,MAAM,qBAAqB,MAAM;AAAA,IACvC,MAAM,MAAM,qBAAqB,MAAM;AAAA,EACzC;AACF;","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -237,10 +237,34 @@ var cleanupDrag = (state) => {
|
|
|
237
237
|
// src/core/marquee.ts
|
|
238
238
|
var createMarqueeState = () => ({
|
|
239
239
|
initialized: false,
|
|
240
|
-
clonedSlides: []
|
|
240
|
+
clonedSlides: [],
|
|
241
|
+
styleElement: null
|
|
241
242
|
});
|
|
243
|
+
var injectMarqueeKeyframes = () => {
|
|
244
|
+
const existingStyle = document.getElementById("lazer-marquee-keyframes");
|
|
245
|
+
if (existingStyle) {
|
|
246
|
+
return existingStyle;
|
|
247
|
+
}
|
|
248
|
+
const style = document.createElement("style");
|
|
249
|
+
style.id = "lazer-marquee-keyframes";
|
|
250
|
+
style.textContent = `
|
|
251
|
+
@keyframes lazer-marquee-scroll {
|
|
252
|
+
from {
|
|
253
|
+
transform: translateX(0);
|
|
254
|
+
}
|
|
255
|
+
to {
|
|
256
|
+
transform: translateX(calc(-1 * var(--lazer-marquee-distance) * 1px));
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
`;
|
|
260
|
+
document.head.appendChild(style);
|
|
261
|
+
return style;
|
|
262
|
+
};
|
|
242
263
|
var setupMarqueeClones = (settings, marqueeState) => {
|
|
243
264
|
if (marqueeState.initialized) return;
|
|
265
|
+
marqueeState.styleElement = injectMarqueeKeyframes();
|
|
266
|
+
settings.feed.style.display = "flex";
|
|
267
|
+
settings.feed.style.willChange = "transform";
|
|
244
268
|
settings.slides.forEach((slide) => {
|
|
245
269
|
const clone = slide.cloneNode(true);
|
|
246
270
|
clone.setAttribute("data-lazer-marquee-clone", "true");
|
|
@@ -258,54 +282,34 @@ var cleanupMarqueeClones = (marqueeState) => {
|
|
|
258
282
|
marqueeState.clonedSlides = [];
|
|
259
283
|
marqueeState.initialized = false;
|
|
260
284
|
};
|
|
261
|
-
var
|
|
285
|
+
var setupMarqueeCss = (settings, state) => {
|
|
286
|
+
const scrollWidth = settings.feed.scrollWidth;
|
|
287
|
+
const speed = settings.marqueeSpeed ?? 50;
|
|
262
288
|
const direction = settings.marqueeDirection ?? "left";
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
if (settings.feed.scrollLeft <= 0) {
|
|
270
|
-
settings.feed.scrollLeft = halfWidth;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
289
|
+
const distance = scrollWidth / 2;
|
|
290
|
+
settings.feed.style.setProperty("--lazer-marquee-distance", String(distance));
|
|
291
|
+
const duration = distance / speed;
|
|
292
|
+
const animationDirection = direction === "right" ? "reverse" : "normal";
|
|
293
|
+
settings.feed.style.animation = `lazer-marquee-scroll ${duration}s linear infinite ${animationDirection}`;
|
|
294
|
+
settings.feed.style.animationPlayState = state.marqueePaused ? "paused" : "running";
|
|
273
295
|
};
|
|
274
296
|
var startMarquee = (settings, state) => {
|
|
275
|
-
|
|
276
|
-
const speed = settings.marqueeSpeed ?? 50;
|
|
277
|
-
const direction = settings.marqueeDirection ?? "left";
|
|
278
|
-
const directionMultiplier = direction === "left" ? 1 : -1;
|
|
279
|
-
if (direction === "right") {
|
|
280
|
-
settings.feed.scrollLeft = settings.feed.scrollWidth / 2;
|
|
281
|
-
}
|
|
282
|
-
const animate = (timestamp) => {
|
|
283
|
-
if (state.marqueePaused) {
|
|
284
|
-
state.marqueeLastTimestamp = timestamp;
|
|
285
|
-
state.marqueeAnimationId = requestAnimationFrame(animate);
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
const deltaTime = state.marqueeLastTimestamp ? (timestamp - state.marqueeLastTimestamp) / 1e3 : 0;
|
|
289
|
-
state.marqueeLastTimestamp = timestamp;
|
|
290
|
-
const movement = speed * deltaTime * directionMultiplier;
|
|
291
|
-
settings.feed.scrollLeft += movement;
|
|
292
|
-
handleMarqueeLoop(settings);
|
|
293
|
-
state.marqueeAnimationId = requestAnimationFrame(animate);
|
|
294
|
-
};
|
|
295
|
-
state.marqueeAnimationId = requestAnimationFrame(animate);
|
|
297
|
+
setupMarqueeCss(settings, state);
|
|
296
298
|
};
|
|
297
|
-
var stopMarquee = (state) => {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
299
|
+
var stopMarquee = (state, settings) => {
|
|
300
|
+
settings.feed.style.animation = "";
|
|
301
|
+
settings.feed.style.animationPlayState = "";
|
|
302
|
+
settings.feed.style.transform = "";
|
|
303
|
+
settings.feed.style.willChange = "";
|
|
304
|
+
settings.feed.style.removeProperty("--lazer-marquee-distance");
|
|
303
305
|
};
|
|
304
|
-
var pauseMarquee = (state) => {
|
|
306
|
+
var pauseMarquee = (state, settings) => {
|
|
305
307
|
state.marqueePaused = true;
|
|
308
|
+
settings.feed.style.animationPlayState = "paused";
|
|
306
309
|
};
|
|
307
|
-
var resumeMarquee = (state) => {
|
|
310
|
+
var resumeMarquee = (state, settings) => {
|
|
308
311
|
state.marqueePaused = false;
|
|
312
|
+
settings.feed.style.animationPlayState = "running";
|
|
309
313
|
};
|
|
310
314
|
var setupMarquee = (settings, state, marqueeState) => {
|
|
311
315
|
if (!settings.marquee) return;
|
|
@@ -316,22 +320,22 @@ var attachMarqueeEventListeners = (settings, state, signal) => {
|
|
|
316
320
|
if (!settings.marquee || settings.pauseOnHover === false) return;
|
|
317
321
|
settings.feed.addEventListener(
|
|
318
322
|
"mouseenter",
|
|
319
|
-
() => pauseMarquee(state),
|
|
323
|
+
() => pauseMarquee(state, settings),
|
|
320
324
|
{ signal }
|
|
321
325
|
);
|
|
322
326
|
settings.feed.addEventListener(
|
|
323
327
|
"mouseleave",
|
|
324
|
-
() => resumeMarquee(state),
|
|
328
|
+
() => resumeMarquee(state, settings),
|
|
325
329
|
{ signal }
|
|
326
330
|
);
|
|
327
331
|
settings.feed.addEventListener(
|
|
328
332
|
"touchstart",
|
|
329
|
-
() => pauseMarquee(state),
|
|
333
|
+
() => pauseMarquee(state, settings),
|
|
330
334
|
{ passive: true, signal }
|
|
331
335
|
);
|
|
332
336
|
settings.feed.addEventListener(
|
|
333
337
|
"touchend",
|
|
334
|
-
() => resumeMarquee(state),
|
|
338
|
+
() => resumeMarquee(state, settings),
|
|
335
339
|
{ signal }
|
|
336
340
|
);
|
|
337
341
|
};
|
|
@@ -760,10 +764,13 @@ var createSlider = (settings) => {
|
|
|
760
764
|
applySlideWidths();
|
|
761
765
|
updateScrollbar();
|
|
762
766
|
updateControlsVisibility();
|
|
767
|
+
if (settings.marquee && !state.marqueePaused) {
|
|
768
|
+
startMarquee(settings, state);
|
|
769
|
+
}
|
|
763
770
|
};
|
|
764
771
|
const unload = () => {
|
|
765
772
|
stopAutoplay();
|
|
766
|
-
stopMarquee(state);
|
|
773
|
+
stopMarquee(state, settings);
|
|
767
774
|
state.abortController.abort();
|
|
768
775
|
window.removeEventListener("resize", handleWindowResize);
|
|
769
776
|
if (state.updateThumbTimeout) {
|
|
@@ -794,14 +801,18 @@ var createSlider = (settings) => {
|
|
|
794
801
|
updateScrollbar();
|
|
795
802
|
const play = () => {
|
|
796
803
|
if (settings.marquee) {
|
|
797
|
-
|
|
804
|
+
if (state.marqueePaused) {
|
|
805
|
+
resumeMarquee(state, settings);
|
|
806
|
+
} else {
|
|
807
|
+
startMarquee(settings, state);
|
|
808
|
+
}
|
|
798
809
|
} else if (settings.autoplay) {
|
|
799
810
|
startAutoplay();
|
|
800
811
|
}
|
|
801
812
|
};
|
|
802
813
|
const pause = () => {
|
|
803
814
|
if (settings.marquee) {
|
|
804
|
-
|
|
815
|
+
pauseMarquee(state, settings);
|
|
805
816
|
} else {
|
|
806
817
|
stopAutoplay();
|
|
807
818
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/easing.ts","../src/core/accessibility.ts","../src/core/drag.ts","../src/core/marquee.ts","../src/core/slider.ts"],"sourcesContent":["import type { EasingFunction } from './types.js'\n\n/**\n * Exponential ease-out - starts fast, decelerates smoothly\n * Best for scroll animations as it feels natural and responsive\n */\nexport const easeOutExpo: EasingFunction = (t) =>\n t === 1 ? 1 : 1 - Math.pow(2, -10 * t)\n\n/**\n * Cubic ease-out - smoother deceleration than exponential\n */\nexport const easeOutCubic: EasingFunction = (t) => 1 - Math.pow(1 - t, 3)\n\n/**\n * Cubic ease-in-out - smooth acceleration and deceleration\n */\nexport const easeInOutCubic: EasingFunction = (t) =>\n t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2\n\n/**\n * Quadratic ease-out - gentle deceleration\n */\nexport const easeOutQuad: EasingFunction = (t) => 1 - (1 - t) * (1 - t)\n\n/**\n * Linear - no easing, constant speed\n */\nexport const linear: EasingFunction = (t) => t\n","import type { SliderSettings } from './types.js'\n\n/**\n * Generate a unique ID for the slider\n */\nexport const generateSliderId = (): string =>\n `slider-${Math.random().toString(36).substring(2, 9)}`\n\n/**\n * Initialize ARIA attributes for accessibility\n */\nexport const initAria = (settings: SliderSettings): void => {\n const { feed, prevSlideButton, nextSlideButton, thumbs, slides } = settings\n\n // Ensure feed has an ID for aria-controls references\n if (!feed.id) {\n feed.id = generateSliderId()\n }\n\n // Set up feed as a scrollable region\n feed.setAttribute('role', 'region')\n feed.setAttribute('aria-label', 'Carousel')\n feed.setAttribute('aria-roledescription', 'carousel')\n\n // Remove tabindex to allow natural tab order\n feed.removeAttribute('tabindex')\n\n // Set up slides with proper roles\n slides.forEach((slide, index) => {\n slide.setAttribute('role', 'group')\n slide.setAttribute('aria-roledescription', 'slide')\n slide.setAttribute('aria-label', `Slide ${index + 1} of ${slides.length}`)\n })\n\n // Set up previous button\n if (prevSlideButton) {\n prevSlideButton.setAttribute('aria-label', 'Previous slide')\n prevSlideButton.setAttribute('aria-controls', feed.id)\n prevSlideButton.setAttribute('tabindex', '0')\n if (prevSlideButton.tagName !== 'BUTTON') {\n prevSlideButton.setAttribute('role', 'button')\n }\n }\n\n // Set up next button\n if (nextSlideButton) {\n nextSlideButton.setAttribute('aria-label', 'Next slide')\n nextSlideButton.setAttribute('aria-controls', feed.id)\n nextSlideButton.setAttribute('tabindex', '0')\n if (nextSlideButton.tagName !== 'BUTTON') {\n nextSlideButton.setAttribute('role', 'button')\n }\n }\n\n // Set up thumbnail/dot navigation\n if (thumbs?.length) {\n thumbs.forEach((thumb, index) => {\n if (thumb.tagName !== 'BUTTON') {\n thumb.setAttribute('role', 'button')\n }\n thumb.setAttribute('aria-label', `Go to slide ${index + 1}`)\n thumb.setAttribute('tabindex', '0')\n thumb.setAttribute('aria-controls', feed.id)\n })\n }\n}\n\n/**\n * Toggle visibility of navigation controls with proper ARIA handling\n */\nexport const toggleControlVisibility = (\n button: HTMLElement | null | undefined,\n shouldShow: boolean,\n feedElement?: HTMLElement\n): void => {\n if (!button) return\n\n // If hiding the button while it has focus, move focus to feed\n if (!shouldShow && button === document.activeElement && feedElement) {\n feedElement.focus()\n }\n\n // Update visual state with transition\n const transition = 'opacity 0.3s ease'\n const opacity = shouldShow ? '1' : '0'\n\n Object.assign(button.style, {\n opacity,\n transition,\n pointerEvents: shouldShow ? 'auto' : 'none'\n })\n\n // Update ARIA state\n if (!shouldShow) {\n button.setAttribute('aria-hidden', 'true')\n button.setAttribute('tabindex', '-1')\n } else {\n button.removeAttribute('aria-hidden')\n button.setAttribute('tabindex', '0')\n }\n\n // Hide from layout after transition if not visible\n if (!shouldShow) {\n setTimeout(() => {\n if (button.style.opacity === '0') {\n button.style.visibility = 'hidden'\n }\n }, 300)\n } else {\n button.style.visibility = 'visible'\n }\n}\n\n/**\n * Update active state on thumbnail elements\n */\nexport const updateActiveThumb = (\n thumbs: HTMLElement[] | undefined,\n currentIndex: number,\n activeClass: string = 'active'\n): void => {\n if (!thumbs?.length) return\n\n thumbs.forEach((thumb, index) => {\n const isActive = index === currentIndex\n thumb.classList.toggle(activeClass, isActive)\n thumb.setAttribute('aria-selected', isActive.toString())\n })\n}\n\n/**\n * Set up keyboard navigation for the slider\n */\nexport const setupKeyboardNavigation = (\n feed: HTMLElement,\n onPrev: () => void,\n onNext: () => void,\n abortSignal: AbortSignal\n): void => {\n feed.addEventListener(\n 'keydown',\n (event: KeyboardEvent) => {\n switch (event.key) {\n case 'ArrowLeft':\n event.preventDefault()\n onPrev()\n break\n case 'ArrowRight':\n event.preventDefault()\n onNext()\n break\n }\n },\n { signal: abortSignal }\n )\n\n // Make feed focusable for keyboard navigation\n if (!feed.hasAttribute('tabindex')) {\n feed.setAttribute('tabindex', '0')\n }\n}\n","import type { DragState, EasingFunction } from './types.js'\nimport { easeOutCubic } from './easing.js'\n\n/**\n * Configuration for drag behavior\n */\ninterface DragConfig {\n /** Feed element to enable dragging on */\n feed: HTMLElement\n /** Slide elements for snap-to-slide calculation */\n slides: HTMLElement[]\n /** AbortSignal for cleanup */\n abortSignal: AbortSignal\n /** Callback to smooth scroll to a position */\n smoothScrollTo: (target: number, easing?: EasingFunction) => void\n /** Callback when drag ends (for updating state) */\n onDragEnd?: () => void\n}\n\n/**\n * Create initial drag state\n */\nexport const createDragState = (): DragState => ({\n isDragging: false,\n startX: 0,\n startScrollLeft: 0,\n velocity: 0,\n lastX: 0,\n lastTime: 0,\n momentumId: null\n})\n\n/**\n * Find the nearest slide to snap to after drag\n */\nconst findNearestSlide = (\n feed: HTMLElement,\n slides: HTMLElement[]\n): HTMLElement | null => {\n const feedRect = feed.getBoundingClientRect()\n const feedCenter = feedRect.left + feedRect.width / 2\n\n let nearestSlide: HTMLElement | null = null\n let minDistance = Infinity\n\n for (const slide of slides) {\n if (slide.offsetParent === null) continue // Skip hidden slides\n\n const slideRect = slide.getBoundingClientRect()\n const slideCenter = slideRect.left + slideRect.width / 2\n const distance = Math.abs(feedCenter - slideCenter)\n\n if (distance < minDistance) {\n minDistance = distance\n nearestSlide = slide\n }\n }\n\n return nearestSlide\n}\n\n/**\n * Apply momentum scrolling after drag release\n */\nconst applyMomentum = (\n state: DragState,\n feed: HTMLElement,\n slides: HTMLElement[],\n smoothScrollTo: (target: number, easing?: EasingFunction) => void,\n onDragEnd?: () => void\n): void => {\n const friction = 0.95\n const minVelocity = 0.5\n\n const animate = () => {\n if (Math.abs(state.velocity) < minVelocity) {\n state.momentumId = null\n\n // Snap to nearest slide\n const nearestSlide = findNearestSlide(feed, slides)\n if (nearestSlide) {\n smoothScrollTo(nearestSlide.offsetLeft, easeOutCubic)\n }\n\n onDragEnd?.()\n return\n }\n\n feed.scrollLeft += state.velocity\n state.velocity *= friction\n\n state.momentumId = requestAnimationFrame(animate)\n }\n\n state.momentumId = requestAnimationFrame(animate)\n}\n\n/**\n * Get X position from mouse or touch event\n */\nconst getEventX = (event: MouseEvent | TouchEvent): number => {\n if ('touches' in event) {\n return event.touches[0]?.clientX ?? 0\n }\n return event.clientX\n}\n\n/**\n * Set up drag-to-scroll functionality\n */\nexport const setupDragToScroll = (config: DragConfig): DragState => {\n const { feed, slides, abortSignal, smoothScrollTo, onDragEnd } = config\n const state = createDragState()\n\n const handleDragStart = (event: MouseEvent | TouchEvent) => {\n // Cancel any ongoing momentum animation\n if (state.momentumId !== null) {\n cancelAnimationFrame(state.momentumId)\n state.momentumId = null\n }\n\n state.isDragging = true\n state.startX = getEventX(event)\n state.startScrollLeft = feed.scrollLeft\n state.velocity = 0\n state.lastX = state.startX\n state.lastTime = performance.now()\n\n // Visual feedback\n feed.style.cursor = 'grabbing'\n feed.style.userSelect = 'none'\n\n // Prevent default for mouse to avoid text selection\n if (event.type === 'mousedown') {\n event.preventDefault()\n }\n }\n\n const handleDragMove = (event: MouseEvent | TouchEvent) => {\n if (!state.isDragging) return\n\n const currentX = getEventX(event)\n const currentTime = performance.now()\n const deltaX = state.startX - currentX\n const deltaTime = currentTime - state.lastTime\n\n // Update scroll position\n feed.scrollLeft = state.startScrollLeft + deltaX\n\n // Calculate velocity for momentum\n if (deltaTime > 0) {\n state.velocity = (state.lastX - currentX) / deltaTime * 16 // Normalize to ~60fps\n }\n\n state.lastX = currentX\n state.lastTime = currentTime\n\n // Prevent default to stop page scrolling on touch\n if (event.type === 'touchmove') {\n event.preventDefault()\n }\n }\n\n const handleDragEnd = () => {\n if (!state.isDragging) return\n\n state.isDragging = false\n\n // Reset visual feedback\n feed.style.cursor = 'grab'\n feed.style.userSelect = ''\n\n // Apply momentum if velocity is significant\n if (Math.abs(state.velocity) > 1) {\n applyMomentum(state, feed, slides, smoothScrollTo, onDragEnd)\n } else {\n // Snap to nearest slide immediately\n const nearestSlide = findNearestSlide(feed, slides)\n if (nearestSlide) {\n smoothScrollTo(nearestSlide.offsetLeft, easeOutCubic)\n }\n onDragEnd?.()\n }\n }\n\n // Set initial cursor\n feed.style.cursor = 'grab'\n\n // Mouse events\n feed.addEventListener('mousedown', handleDragStart, { signal: abortSignal })\n document.addEventListener('mousemove', handleDragMove, { signal: abortSignal })\n document.addEventListener('mouseup', handleDragEnd, { signal: abortSignal })\n\n // Touch events\n feed.addEventListener('touchstart', handleDragStart, {\n passive: true,\n signal: abortSignal\n })\n feed.addEventListener('touchmove', handleDragMove, {\n passive: false, // Need to prevent default\n signal: abortSignal\n })\n feed.addEventListener('touchend', handleDragEnd, { signal: abortSignal })\n\n // Handle mouse leaving the window\n document.addEventListener('mouseleave', handleDragEnd, { signal: abortSignal })\n\n return state\n}\n\n/**\n * Clean up drag state (cancel any pending animations)\n */\nexport const cleanupDrag = (state: DragState): void => {\n if (state.momentumId !== null) {\n cancelAnimationFrame(state.momentumId)\n state.momentumId = null\n }\n}\n","import type { SliderSettings, SliderState } from './types.js'\n\n/**\n * Marquee state - tracks cloned slides for marquee mode\n */\nexport interface MarqueeState {\n initialized: boolean\n clonedSlides: HTMLElement[]\n}\n\n/**\n * Create initial marquee state\n */\nexport const createMarqueeState = (): MarqueeState => ({\n initialized: false,\n clonedSlides: []\n})\n\n/**\n * Clone all slides and append for seamless marquee looping\n */\nexport const setupMarqueeClones = (\n settings: SliderSettings,\n marqueeState: MarqueeState\n): void => {\n if (marqueeState.initialized) return\n\n // Clone all original slides and append to create seamless loop\n settings.slides.forEach((slide) => {\n const clone = slide.cloneNode(true) as HTMLElement\n clone.setAttribute('data-lazer-marquee-clone', 'true')\n clone.setAttribute('aria-hidden', 'true')\n settings.feed.appendChild(clone)\n marqueeState.clonedSlides.push(clone)\n })\n\n marqueeState.initialized = true\n}\n\n/**\n * Remove marquee cloned slides from the DOM\n */\nexport const cleanupMarqueeClones = (marqueeState: MarqueeState): void => {\n if (!marqueeState.initialized) return\n\n marqueeState.clonedSlides.forEach((clone) => {\n clone.remove()\n })\n\n marqueeState.clonedSlides = []\n marqueeState.initialized = false\n}\n\n/**\n * Handle seamless repositioning when marquee reaches content boundaries\n */\nconst handleMarqueeLoop = (\n settings: SliderSettings\n): void => {\n const direction = settings.marqueeDirection ?? 'left'\n const halfWidth = settings.feed.scrollWidth / 2\n\n if (direction === 'left') {\n // When scrolled past halfway (cloned content), jump back to start\n if (settings.feed.scrollLeft >= halfWidth) {\n settings.feed.scrollLeft = settings.feed.scrollLeft - halfWidth\n }\n } else {\n // When scrolled before start, jump to halfway point\n if (settings.feed.scrollLeft <= 0) {\n settings.feed.scrollLeft = halfWidth\n }\n }\n}\n\n/**\n * Start marquee animation using requestAnimationFrame\n */\nexport const startMarquee = (\n settings: SliderSettings,\n state: SliderState\n): void => {\n if (state.marqueeAnimationId) return\n\n const speed = settings.marqueeSpeed ?? 50 // pixels per second\n const direction = settings.marqueeDirection ?? 'left'\n const directionMultiplier = direction === 'left' ? 1 : -1\n\n // Set initial scroll position for right-to-left marquee\n if (direction === 'right') {\n settings.feed.scrollLeft = settings.feed.scrollWidth / 2\n }\n\n const animate = (timestamp: number): void => {\n if (state.marqueePaused) {\n // When paused, just update timestamp to prevent jump on resume\n state.marqueeLastTimestamp = timestamp\n state.marqueeAnimationId = requestAnimationFrame(animate)\n return\n }\n\n // Calculate delta time for consistent speed regardless of frame rate\n const deltaTime = state.marqueeLastTimestamp\n ? (timestamp - state.marqueeLastTimestamp) / 1000\n : 0\n state.marqueeLastTimestamp = timestamp\n\n // Move scroll position based on speed and direction\n const movement = speed * deltaTime * directionMultiplier\n settings.feed.scrollLeft += movement\n\n // Handle seamless loop repositioning\n handleMarqueeLoop(settings)\n\n state.marqueeAnimationId = requestAnimationFrame(animate)\n }\n\n state.marqueeAnimationId = requestAnimationFrame(animate)\n}\n\n/**\n * Stop marquee animation\n */\nexport const stopMarquee = (state: SliderState): void => {\n if (state.marqueeAnimationId) {\n cancelAnimationFrame(state.marqueeAnimationId)\n state.marqueeAnimationId = null\n }\n state.marqueeLastTimestamp = 0\n}\n\n/**\n * Pause marquee animation (temporary, resumes on mouse leave)\n */\nexport const pauseMarquee = (state: SliderState): void => {\n state.marqueePaused = true\n}\n\n/**\n * Resume marquee animation after pause\n */\nexport const resumeMarquee = (state: SliderState): void => {\n state.marqueePaused = false\n}\n\n/**\n * Setup marquee mode (clones + animation)\n */\nexport const setupMarquee = (\n settings: SliderSettings,\n state: SliderState,\n marqueeState: MarqueeState\n): void => {\n if (!settings.marquee) return\n\n setupMarqueeClones(settings, marqueeState)\n startMarquee(settings, state)\n}\n\n/**\n * Attach marquee pause-on-hover event listeners\n */\nexport const attachMarqueeEventListeners = (\n settings: SliderSettings,\n state: SliderState,\n signal: AbortSignal\n): void => {\n if (!settings.marquee || settings.pauseOnHover === false) return\n\n settings.feed.addEventListener(\n 'mouseenter',\n () => pauseMarquee(state),\n { signal }\n )\n settings.feed.addEventListener(\n 'mouseleave',\n () => resumeMarquee(state),\n { signal }\n )\n settings.feed.addEventListener(\n 'touchstart',\n () => pauseMarquee(state),\n { passive: true, signal }\n )\n settings.feed.addEventListener(\n 'touchend',\n () => resumeMarquee(state),\n { signal }\n )\n}\n","import type {\n SliderSettings,\n SliderState,\n SliderDirection,\n EasingFunction,\n Slider,\n DragState\n} from './types.js'\nimport { easeOutExpo } from './easing.js'\nimport {\n initAria,\n toggleControlVisibility,\n updateActiveThumb,\n setupKeyboardNavigation\n} from './accessibility.js'\nimport { setupDragToScroll, cleanupDrag } from './drag.js'\nimport {\n createMarqueeState,\n setupMarquee,\n startMarquee,\n stopMarquee,\n cleanupMarqueeClones,\n attachMarqueeEventListeners\n} from './marquee.js'\n\n/**\n * Animation duration configuration\n */\nconst ANIMATION = {\n MIN_DURATION: 400,\n MAX_DURATION: 1000,\n SPEED_FACTOR: 1.5,\n SCROLL_END_DELAY: 50,\n THUMB_UPDATE_DELAY: 500\n} as const\n\n/**\n * Desktop breakpoint for responsive slidesPerScroll\n */\nconst DESKTOP_BREAKPOINT = '(min-width: 64rem)'\n\n/**\n * Create a slider instance\n */\nexport const createSlider = (settings: SliderSettings): Slider => {\n // Validate required settings\n if (!settings.feed) {\n throw new Error('lazer-slider: feed element is required')\n }\n\n if (!settings.slides?.length) {\n throw new Error('lazer-slider: slides array is required and must not be empty')\n }\n\n // Initialize state\n const state: SliderState = {\n currentSlideIndex: 0,\n isScrolling: false,\n ticking: false,\n cachedFeedRect: null,\n lastWidth: 0,\n updateThumbTimeout: null,\n scrollEndTimeout: null,\n abortController: new AbortController(),\n autoplayIntervalId: null,\n autoplayPaused: false,\n marqueeAnimationId: null,\n marqueePaused: false,\n marqueeLastTimestamp: 0\n }\n\n // Drag state (initialized if drag is enabled)\n let dragState: DragState | null = null\n\n // Loop state - tracks cloned slides for infinite loop\n const loopState = {\n initialized: false,\n clonedSlides: [] as HTMLElement[],\n realSlides: [...settings.slides] as HTMLElement[],\n clonesPerSide: 0\n }\n\n // Marquee state - tracks cloned slides for marquee mode\n const marqueeState = createMarqueeState()\n\n // Default easing function\n const easing = settings.easing ?? easeOutExpo\n\n /**\n * Get cached feed bounding rect (invalidated on resize)\n */\n const getFeedRect = (): DOMRect => {\n const currentWidth = settings.feed.clientWidth\n if (!state.cachedFeedRect || state.lastWidth !== currentWidth) {\n state.cachedFeedRect = settings.feed.getBoundingClientRect()\n state.lastWidth = currentWidth\n }\n return state.cachedFeedRect\n }\n\n /**\n * Get only visible slides (not hidden via CSS)\n */\n const getVisibleSlides = (): HTMLElement[] => {\n return settings.slides.filter((slide) => slide.offsetParent !== null)\n }\n\n /**\n * Check if we're on desktop based on breakpoint\n */\n const isDesktop = (): boolean => {\n return window.matchMedia(DESKTOP_BREAKPOINT).matches\n }\n\n /**\n * Get the number of slides to clone per side for infinite loop\n */\n const getLoopClonesCount = (): number => {\n const perView = isDesktop()\n ? settings.desktopSlidesPerView\n : settings.mobileSlidesPerView\n\n // Clone at least 1 slide, or the number of visible slides\n if (!perView || perView === 'auto') {\n return 1\n }\n return Math.ceil(perView)\n }\n\n /**\n * Setup cloned slides for infinite loop functionality\n * Clones first N slides to the end and last N slides to the beginning\n */\n const setupLoopClones = (): void => {\n if (!settings.loop || loopState.initialized) return\n\n const realSlides = loopState.realSlides\n const clonesCount = getLoopClonesCount()\n loopState.clonesPerSide = clonesCount\n\n // Clone last N slides and prepend to beginning\n for (let i = realSlides.length - clonesCount; i < realSlides.length; i++) {\n const slide = realSlides[i]\n if (!slide) continue\n\n const clone = slide.cloneNode(true) as HTMLElement\n clone.setAttribute('data-lazer-clone', 'prepend')\n clone.setAttribute('aria-hidden', 'true')\n settings.feed.insertBefore(clone, settings.feed.firstChild)\n loopState.clonedSlides.push(clone)\n }\n\n // Clone first N slides and append to end\n for (let i = 0; i < clonesCount; i++) {\n const slide = realSlides[i]\n if (!slide) continue\n\n const clone = slide.cloneNode(true) as HTMLElement\n clone.setAttribute('data-lazer-clone', 'append')\n clone.setAttribute('aria-hidden', 'true')\n settings.feed.appendChild(clone)\n loopState.clonedSlides.push(clone)\n }\n\n // Set initial scroll position to first real slide (after prepended clones)\n requestAnimationFrame(() => {\n const firstRealSlide = realSlides[0]\n if (firstRealSlide) {\n settings.feed.scrollLeft = firstRealSlide.offsetLeft\n }\n })\n\n loopState.initialized = true\n }\n\n /**\n * Handle repositioning when reaching clone boundaries\n * Silently jumps to the corresponding real slide without animation\n */\n const handleLoopReposition = (direction: SliderDirection): void => {\n if (!settings.loop || !loopState.initialized) return\n\n const realSlides = loopState.realSlides\n const totalRealSlides = realSlides.length\n\n if (direction === 'next') {\n const firstRealSlide = realSlides[0]\n if (firstRealSlide) {\n settings.feed.scrollLeft = firstRealSlide.offsetLeft\n }\n state.currentSlideIndex = 0\n } else {\n const lastRealSlide = realSlides[totalRealSlides - 1]\n if (lastRealSlide) {\n settings.feed.scrollLeft = lastRealSlide.offsetLeft\n }\n state.currentSlideIndex = totalRealSlides - 1\n }\n }\n\n /**\n * Remove cloned slides from the DOM\n */\n const cleanupLoopClones = (): void => {\n if (!loopState.initialized) return\n\n loopState.clonedSlides.forEach((clone) => {\n clone.remove()\n })\n\n loopState.clonedSlides = []\n loopState.initialized = false\n loopState.clonesPerSide = 0\n }\n\n /**\n * Apply slide widths based on slidesPerView settings\n */\n const applySlideWidths = (): void => {\n const perView = isDesktop()\n ? settings.desktopSlidesPerView\n : settings.mobileSlidesPerView\n\n const gap = settings.slideGap ?? 0\n\n // Set gap on the feed container\n if (gap > 0) {\n settings.feed.style.gap = `${gap}px`\n }\n\n // If 'auto' or not set, let CSS handle widths (natural sizing)\n if (!perView || perView === 'auto') {\n // Reset any previously set widths\n settings.slides.forEach((slide) => {\n slide.style.flex = ''\n slide.style.minWidth = ''\n })\n return\n }\n\n const totalGapWidth = gap * (perView - 1)\n const slideWidth = `calc((100% - ${totalGapWidth}px) / ${perView})`\n\n settings.slides.forEach((slide) => {\n slide.style.flex = `0 0 ${slideWidth}`\n slide.style.minWidth = slideWidth\n })\n }\n\n /**\n * Update scrollbar thumb width based on visible content\n */\n const updateScrollbar = (): void => {\n if (!settings.scrollbarThumb) return\n\n const feedRect = getFeedRect()\n const thumbWidth = (feedRect.width / settings.feed.scrollWidth) * 100\n settings.scrollbarThumb.style.width = `${thumbWidth}%`\n }\n\n /**\n * Update scrollbar thumb position based on scroll\n */\n const updateScrollbarPosition = (): void => {\n if (!settings.scrollbarThumb || !settings.scrollbarTrack) return\n\n const trackWidth = settings.scrollbarTrack.getBoundingClientRect().width\n const thumbWidth = settings.scrollbarThumb.getBoundingClientRect().width\n const totalTransform = trackWidth - thumbWidth\n\n const maxScroll = settings.feed.scrollWidth - settings.feed.clientWidth\n const scrollProgress = maxScroll > 0 ? settings.feed.scrollLeft / maxScroll : 0\n\n settings.scrollbarThumb.style.transform = `translateX(${totalTransform * scrollProgress}px)`\n }\n\n /**\n * Update visibility of navigation controls based on scroll position\n */\n const updateControlsVisibility = (): void => {\n const feedRect = getFeedRect()\n const isAtStart = settings.feed.scrollLeft <= 1 // Allow 1px tolerance\n const isAtEnd =\n settings.feed.scrollLeft + feedRect.width >= settings.feed.scrollWidth - 1\n const shouldHideScrollbar = settings.feed.scrollWidth <= feedRect.width\n\n // Hide/show scrollbar track\n if (settings.scrollbarTrack) {\n settings.scrollbarTrack.style.display = shouldHideScrollbar ? 'none' : 'block'\n }\n\n // When loop is enabled, always show both buttons (unless scrollbar should be hidden)\n if (settings.loop) {\n toggleControlVisibility(\n settings.prevSlideButton,\n !shouldHideScrollbar,\n settings.feed\n )\n toggleControlVisibility(\n settings.nextSlideButton,\n !shouldHideScrollbar,\n settings.feed\n )\n return\n }\n\n // Hide/show navigation buttons based on position\n toggleControlVisibility(\n settings.prevSlideButton,\n !isAtStart && !shouldHideScrollbar,\n settings.feed\n )\n toggleControlVisibility(\n settings.nextSlideButton,\n !isAtEnd && !shouldHideScrollbar,\n settings.feed\n )\n }\n\n /**\n * Calculate current slide index based on viewport position\n */\n const updateCurrentSlideIndex = (): void => {\n const feedRect = getFeedRect()\n\n // Use real slides for index calculation when loop is enabled\n const slidesToCheck = loopState.initialized\n ? loopState.realSlides\n : getVisibleSlides()\n\n // Find slides that are visible in the viewport\n const viewportVisibleSlides = slidesToCheck.filter((slide) => {\n const slideRect = slide.getBoundingClientRect()\n const tolerance = 20 // px tolerance for edge detection\n return (\n slideRect.right > feedRect.left + tolerance &&\n slideRect.left < feedRect.right - tolerance\n )\n })\n\n if (viewportVisibleSlides.length && viewportVisibleSlides[0]) {\n const newIndex = slidesToCheck.indexOf(viewportVisibleSlides[0])\n if (newIndex !== -1) {\n state.currentSlideIndex = newIndex\n settings.onScroll?.({\n currentScroll: settings.feed.scrollLeft,\n currentSlideIndex: state.currentSlideIndex\n })\n }\n }\n }\n\n /**\n * Smooth scroll to a target position using requestAnimationFrame\n */\n const smoothScrollTo = (\n target: number,\n customEasing: EasingFunction = easing,\n onComplete?: () => void\n ): void => {\n const start = settings.feed.scrollLeft\n const distance = Math.abs(target - start)\n\n // Dynamic duration based on distance\n const duration = Math.min(\n ANIMATION.MAX_DURATION,\n Math.max(ANIMATION.MIN_DURATION, distance / ANIMATION.SPEED_FACTOR)\n )\n\n const startTime = performance.now()\n\n const animateScroll = (currentTime: number): void => {\n const elapsed = (currentTime - startTime) / duration\n const progress = Math.min(elapsed, 1)\n const ease = customEasing(progress)\n\n settings.feed.scrollLeft = start + (target - start) * ease\n\n if (progress < 1) {\n requestAnimationFrame(animateScroll)\n } else {\n settings.feed.scrollLeft = target\n onComplete?.()\n }\n }\n\n requestAnimationFrame(animateScroll)\n }\n\n /**\n * Handle thumbnail click navigation\n */\n const handleThumbClick = (thumb: HTMLElement): void => {\n if (!settings.thumbs) return\n\n const index = settings.thumbs.indexOf(thumb)\n if (index === -1 || !settings.slides[index]) return\n\n state.currentSlideIndex = index\n updateActiveThumb(settings.thumbs, index)\n state.isScrolling = true\n\n // Clear existing timeout\n if (state.updateThumbTimeout) {\n clearTimeout(state.updateThumbTimeout)\n }\n\n // Reset scrolling flag after animation\n state.updateThumbTimeout = setTimeout(() => {\n state.isScrolling = false\n }, ANIMATION.THUMB_UPDATE_DELAY)\n\n smoothScrollTo(settings.slides[index].offsetLeft)\n }\n\n /**\n * Handle navigation button click (prev/next)\n */\n const handleNavButtonClick = (direction: SliderDirection): void => {\n const realSlides = loopState.initialized ? loopState.realSlides : getVisibleSlides()\n const slidesToScroll = isDesktop()\n ? settings.desktopSlidesPerScroll ?? 1\n : settings.mobileSlidesPerScroll ?? 1\n const totalRealSlides = realSlides.length\n\n // Update current index first\n updateCurrentSlideIndex()\n\n let targetSlide: HTMLElement | undefined\n let needsReposition = false\n\n if (direction === 'prev') {\n if (settings.loop && loopState.initialized && state.currentSlideIndex === 0) {\n\n const prependedClones = loopState.clonedSlides.filter(\n (clone) => clone.getAttribute('data-lazer-clone') === 'prepend'\n )\n targetSlide = prependedClones[prependedClones.length - 1]\n needsReposition = true\n } else {\n state.currentSlideIndex = Math.max(0, state.currentSlideIndex - slidesToScroll)\n targetSlide = realSlides[state.currentSlideIndex]\n }\n } else {\n if (settings.loop && loopState.initialized && state.currentSlideIndex >= totalRealSlides - 1) {\n const appendedClones = loopState.clonedSlides.filter(\n (clone) => clone.getAttribute('data-lazer-clone') === 'append'\n )\n targetSlide = appendedClones[0]\n needsReposition = true\n } else {\n state.currentSlideIndex = Math.min(\n totalRealSlides - 1,\n state.currentSlideIndex + slidesToScroll\n )\n targetSlide = realSlides[state.currentSlideIndex]\n }\n }\n\n if (!targetSlide) return\n\n // Fire scroll start callback\n settings.onScrollStart?.({\n currentScroll: settings.feed.scrollLeft,\n target: targetSlide,\n direction\n })\n\n if (needsReposition) {\n // Scroll to clone, then silently reposition to the real slide\n smoothScrollTo(targetSlide.offsetLeft, easing, () => {\n handleLoopReposition(direction)\n })\n } else {\n smoothScrollTo(targetSlide.offsetLeft)\n }\n }\n\n /**\n * Update all scroll-related state\n */\n const updateScrollPosition = (): void => {\n updateScrollbarPosition()\n updateControlsVisibility()\n updateCurrentSlideIndex()\n\n // Update thumbs only when not programmatically scrolling\n if (!state.isScrolling) {\n updateActiveThumb(settings.thumbs, state.currentSlideIndex)\n }\n\n // Clear existing timeout\n if (state.scrollEndTimeout) {\n clearTimeout(state.scrollEndTimeout)\n }\n\n // Fire scroll end callback after scroll settles\n state.scrollEndTimeout = setTimeout(() => {\n state.isScrolling = false\n settings.onScrollEnd?.({\n currentScroll: settings.feed.scrollLeft,\n currentSlideIndex: state.currentSlideIndex\n })\n }, ANIMATION.SCROLL_END_DELAY)\n }\n\n /**\n * Throttled scroll event handler\n */\n const handleFeedScroll = (): void => {\n if (!state.ticking) {\n requestAnimationFrame(() => {\n updateScrollPosition()\n state.ticking = false\n })\n state.ticking = true\n }\n }\n\n /**\n * Handle window resize\n */\n const handleWindowResize = (): void => {\n state.cachedFeedRect = null\n refresh()\n }\n\n /**\n * Attach all event listeners\n */\n const attachEventListeners = (): void => {\n const { signal } = state.abortController\n\n // Resize handler (not abortable, cleaned up manually)\n window.addEventListener('resize', handleWindowResize)\n\n // Scroll handler\n settings.feed.addEventListener('scroll', handleFeedScroll, {\n passive: true,\n signal\n })\n\n // Navigation button handlers\n if (settings.prevSlideButton) {\n settings.prevSlideButton.addEventListener(\n 'click',\n () => handleNavButtonClick('prev'),\n { signal }\n )\n }\n\n if (settings.nextSlideButton) {\n settings.nextSlideButton.addEventListener(\n 'click',\n () => handleNavButtonClick('next'),\n { signal }\n )\n }\n\n // Thumbnail handlers\n if (settings.thumbs?.length) {\n settings.thumbs[0]?.classList.add('active')\n settings.thumbs.forEach((thumb) => {\n thumb.addEventListener('click', () => handleThumbClick(thumb), { signal })\n })\n }\n\n // Keyboard navigation\n setupKeyboardNavigation(\n settings.feed,\n () => handleNavButtonClick('prev'),\n () => handleNavButtonClick('next'),\n signal\n )\n\n // Drag-to-scroll\n if (settings.enableDragToScroll) {\n dragState = setupDragToScroll({\n feed: settings.feed,\n slides: settings.slides,\n abortSignal: signal,\n smoothScrollTo,\n onDragEnd: () => {\n updateCurrentSlideIndex()\n updateActiveThumb(settings.thumbs, state.currentSlideIndex)\n }\n })\n }\n\n // Autoplay pause on hover/touch\n if (settings.autoplay && settings.pauseOnHover !== false) {\n settings.feed.addEventListener(\n 'mouseenter',\n pauseAutoplay,\n { signal }\n )\n settings.feed.addEventListener(\n 'mouseleave',\n resumeAutoplay,\n { signal }\n )\n settings.feed.addEventListener(\n 'touchstart',\n pauseAutoplay,\n { passive: true, signal }\n )\n settings.feed.addEventListener(\n 'touchend',\n resumeAutoplay,\n { signal }\n )\n }\n\n // Marquee pause on hover/touch\n attachMarqueeEventListeners(settings, state, signal)\n }\n\n /**\n * Start autoplay\n */\n const startAutoplay = (): void => {\n if (state.autoplayIntervalId) return;\n\n const interval = settings.autoplayInterval ?? 3000\n state.autoplayIntervalId = setInterval(() => {\n if (!state.autoplayPaused) {\n handleNavButtonClick('next')\n }\n }, interval)\n }\n\n /**\n * Stop autoplay\n */\n const stopAutoplay = (): void => {\n if (state.autoplayIntervalId) {\n clearInterval(state.autoplayIntervalId)\n state.autoplayIntervalId = null\n }\n }\n\n /**\n * Pause autoplay (temporary, resumes on mouse leave)\n */\n const pauseAutoplay = (): void => {\n state.autoplayPaused = true\n }\n\n /**\n * Resume autoplay after pause\n */\n const resumeAutoplay = (): void => {\n state.autoplayPaused = false\n }\n\n /**\n * Navigate to a specific slide by index\n */\n const goToIndex = (index: number): void => {\n // Use real slides when loop is enabled, otherwise use visible slides\n const slides = loopState.initialized ? loopState.realSlides : getVisibleSlides()\n const safeIndex = Math.max(0, Math.min(index, slides.length - 1))\n const targetSlide = slides[safeIndex]\n\n if (!targetSlide) return\n\n state.currentSlideIndex = safeIndex\n updateActiveThumb(settings.thumbs, safeIndex)\n smoothScrollTo(targetSlide.offsetLeft)\n }\n\n /**\n * Refresh slider state (recalculate dimensions, update controls)\n */\n const refresh = (): void => {\n state.cachedFeedRect = null\n applySlideWidths()\n updateScrollbar()\n updateControlsVisibility()\n }\n\n /**\n * Clean up all event listeners and timers\n */\n const unload = (): void => {\n // Stop autoplay and marquee\n stopAutoplay()\n stopMarquee(state)\n\n // Abort all event listeners\n state.abortController.abort()\n\n // Remove resize listener\n window.removeEventListener('resize', handleWindowResize)\n\n // Clear timeouts\n if (state.updateThumbTimeout) {\n clearTimeout(state.updateThumbTimeout)\n }\n if (state.scrollEndTimeout) {\n clearTimeout(state.scrollEndTimeout)\n }\n\n // Clean up drag state\n if (dragState) {\n cleanupDrag(dragState)\n }\n\n // Clean up loop clones and marquee clones\n cleanupLoopClones()\n cleanupMarqueeClones(marqueeState)\n\n // Reset cached rect\n state.cachedFeedRect = null\n }\n\n // Initialize slider\n initAria(settings)\n applySlideWidths()\n\n // Marquee mode takes precedence over loop/autoplay\n if (settings.marquee) {\n setupMarquee(settings, state, marqueeState)\n } else {\n setupLoopClones()\n // Start autoplay if enabled (only in non-marquee mode)\n if (settings.autoplay) {\n startAutoplay()\n }\n }\n\n updateControlsVisibility()\n attachEventListeners()\n updateScrollbar()\n\n // Unified play/pause that works for both autoplay and marquee\n const play = (): void => {\n if (settings.marquee) {\n startMarquee(settings, state)\n } else if (settings.autoplay) {\n startAutoplay()\n }\n }\n\n const pause = (): void => {\n if (settings.marquee) {\n stopMarquee(state)\n } else {\n stopAutoplay()\n }\n }\n\n return {\n goToIndex,\n refresh,\n unload,\n play,\n pause,\n next: () => handleNavButtonClick('next'),\n prev: () => handleNavButtonClick('prev')\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/core/easing.ts","../src/core/accessibility.ts","../src/core/drag.ts","../src/core/marquee.ts","../src/core/slider.ts"],"sourcesContent":["import type { EasingFunction } from './types.js'\n\n/**\n * Exponential ease-out - starts fast, decelerates smoothly\n * Best for scroll animations as it feels natural and responsive\n */\nexport const easeOutExpo: EasingFunction = (t) =>\n t === 1 ? 1 : 1 - Math.pow(2, -10 * t)\n\n/**\n * Cubic ease-out - smoother deceleration than exponential\n */\nexport const easeOutCubic: EasingFunction = (t) => 1 - Math.pow(1 - t, 3)\n\n/**\n * Cubic ease-in-out - smooth acceleration and deceleration\n */\nexport const easeInOutCubic: EasingFunction = (t) =>\n t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2\n\n/**\n * Quadratic ease-out - gentle deceleration\n */\nexport const easeOutQuad: EasingFunction = (t) => 1 - (1 - t) * (1 - t)\n\n/**\n * Linear - no easing, constant speed\n */\nexport const linear: EasingFunction = (t) => t\n","import type { SliderSettings } from './types.js'\n\n/**\n * Generate a unique ID for the slider\n */\nexport const generateSliderId = (): string =>\n `slider-${Math.random().toString(36).substring(2, 9)}`\n\n/**\n * Initialize ARIA attributes for accessibility\n */\nexport const initAria = (settings: SliderSettings): void => {\n const { feed, prevSlideButton, nextSlideButton, thumbs, slides } = settings\n\n // Ensure feed has an ID for aria-controls references\n if (!feed.id) {\n feed.id = generateSliderId()\n }\n\n // Set up feed as a scrollable region\n feed.setAttribute('role', 'region')\n feed.setAttribute('aria-label', 'Carousel')\n feed.setAttribute('aria-roledescription', 'carousel')\n\n // Remove tabindex to allow natural tab order\n feed.removeAttribute('tabindex')\n\n // Set up slides with proper roles\n slides.forEach((slide, index) => {\n slide.setAttribute('role', 'group')\n slide.setAttribute('aria-roledescription', 'slide')\n slide.setAttribute('aria-label', `Slide ${index + 1} of ${slides.length}`)\n })\n\n // Set up previous button\n if (prevSlideButton) {\n prevSlideButton.setAttribute('aria-label', 'Previous slide')\n prevSlideButton.setAttribute('aria-controls', feed.id)\n prevSlideButton.setAttribute('tabindex', '0')\n if (prevSlideButton.tagName !== 'BUTTON') {\n prevSlideButton.setAttribute('role', 'button')\n }\n }\n\n // Set up next button\n if (nextSlideButton) {\n nextSlideButton.setAttribute('aria-label', 'Next slide')\n nextSlideButton.setAttribute('aria-controls', feed.id)\n nextSlideButton.setAttribute('tabindex', '0')\n if (nextSlideButton.tagName !== 'BUTTON') {\n nextSlideButton.setAttribute('role', 'button')\n }\n }\n\n // Set up thumbnail/dot navigation\n if (thumbs?.length) {\n thumbs.forEach((thumb, index) => {\n if (thumb.tagName !== 'BUTTON') {\n thumb.setAttribute('role', 'button')\n }\n thumb.setAttribute('aria-label', `Go to slide ${index + 1}`)\n thumb.setAttribute('tabindex', '0')\n thumb.setAttribute('aria-controls', feed.id)\n })\n }\n}\n\n/**\n * Toggle visibility of navigation controls with proper ARIA handling\n */\nexport const toggleControlVisibility = (\n button: HTMLElement | null | undefined,\n shouldShow: boolean,\n feedElement?: HTMLElement\n): void => {\n if (!button) return\n\n // If hiding the button while it has focus, move focus to feed\n if (!shouldShow && button === document.activeElement && feedElement) {\n feedElement.focus()\n }\n\n // Update visual state with transition\n const transition = 'opacity 0.3s ease'\n const opacity = shouldShow ? '1' : '0'\n\n Object.assign(button.style, {\n opacity,\n transition,\n pointerEvents: shouldShow ? 'auto' : 'none'\n })\n\n // Update ARIA state\n if (!shouldShow) {\n button.setAttribute('aria-hidden', 'true')\n button.setAttribute('tabindex', '-1')\n } else {\n button.removeAttribute('aria-hidden')\n button.setAttribute('tabindex', '0')\n }\n\n // Hide from layout after transition if not visible\n if (!shouldShow) {\n setTimeout(() => {\n if (button.style.opacity === '0') {\n button.style.visibility = 'hidden'\n }\n }, 300)\n } else {\n button.style.visibility = 'visible'\n }\n}\n\n/**\n * Update active state on thumbnail elements\n */\nexport const updateActiveThumb = (\n thumbs: HTMLElement[] | undefined,\n currentIndex: number,\n activeClass: string = 'active'\n): void => {\n if (!thumbs?.length) return\n\n thumbs.forEach((thumb, index) => {\n const isActive = index === currentIndex\n thumb.classList.toggle(activeClass, isActive)\n thumb.setAttribute('aria-selected', isActive.toString())\n })\n}\n\n/**\n * Set up keyboard navigation for the slider\n */\nexport const setupKeyboardNavigation = (\n feed: HTMLElement,\n onPrev: () => void,\n onNext: () => void,\n abortSignal: AbortSignal\n): void => {\n feed.addEventListener(\n 'keydown',\n (event: KeyboardEvent) => {\n switch (event.key) {\n case 'ArrowLeft':\n event.preventDefault()\n onPrev()\n break\n case 'ArrowRight':\n event.preventDefault()\n onNext()\n break\n }\n },\n { signal: abortSignal }\n )\n\n // Make feed focusable for keyboard navigation\n if (!feed.hasAttribute('tabindex')) {\n feed.setAttribute('tabindex', '0')\n }\n}\n","import type { DragState, EasingFunction } from './types.js'\nimport { easeOutCubic } from './easing.js'\n\n/**\n * Configuration for drag behavior\n */\ninterface DragConfig {\n /** Feed element to enable dragging on */\n feed: HTMLElement\n /** Slide elements for snap-to-slide calculation */\n slides: HTMLElement[]\n /** AbortSignal for cleanup */\n abortSignal: AbortSignal\n /** Callback to smooth scroll to a position */\n smoothScrollTo: (target: number, easing?: EasingFunction) => void\n /** Callback when drag ends (for updating state) */\n onDragEnd?: () => void\n}\n\n/**\n * Create initial drag state\n */\nexport const createDragState = (): DragState => ({\n isDragging: false,\n startX: 0,\n startScrollLeft: 0,\n velocity: 0,\n lastX: 0,\n lastTime: 0,\n momentumId: null\n})\n\n/**\n * Find the nearest slide to snap to after drag\n */\nconst findNearestSlide = (\n feed: HTMLElement,\n slides: HTMLElement[]\n): HTMLElement | null => {\n const feedRect = feed.getBoundingClientRect()\n const feedCenter = feedRect.left + feedRect.width / 2\n\n let nearestSlide: HTMLElement | null = null\n let minDistance = Infinity\n\n for (const slide of slides) {\n if (slide.offsetParent === null) continue // Skip hidden slides\n\n const slideRect = slide.getBoundingClientRect()\n const slideCenter = slideRect.left + slideRect.width / 2\n const distance = Math.abs(feedCenter - slideCenter)\n\n if (distance < minDistance) {\n minDistance = distance\n nearestSlide = slide\n }\n }\n\n return nearestSlide\n}\n\n/**\n * Apply momentum scrolling after drag release\n */\nconst applyMomentum = (\n state: DragState,\n feed: HTMLElement,\n slides: HTMLElement[],\n smoothScrollTo: (target: number, easing?: EasingFunction) => void,\n onDragEnd?: () => void\n): void => {\n const friction = 0.95\n const minVelocity = 0.5\n\n const animate = () => {\n if (Math.abs(state.velocity) < minVelocity) {\n state.momentumId = null\n\n // Snap to nearest slide\n const nearestSlide = findNearestSlide(feed, slides)\n if (nearestSlide) {\n smoothScrollTo(nearestSlide.offsetLeft, easeOutCubic)\n }\n\n onDragEnd?.()\n return\n }\n\n feed.scrollLeft += state.velocity\n state.velocity *= friction\n\n state.momentumId = requestAnimationFrame(animate)\n }\n\n state.momentumId = requestAnimationFrame(animate)\n}\n\n/**\n * Get X position from mouse or touch event\n */\nconst getEventX = (event: MouseEvent | TouchEvent): number => {\n if ('touches' in event) {\n return event.touches[0]?.clientX ?? 0\n }\n return event.clientX\n}\n\n/**\n * Set up drag-to-scroll functionality\n */\nexport const setupDragToScroll = (config: DragConfig): DragState => {\n const { feed, slides, abortSignal, smoothScrollTo, onDragEnd } = config\n const state = createDragState()\n\n const handleDragStart = (event: MouseEvent | TouchEvent) => {\n // Cancel any ongoing momentum animation\n if (state.momentumId !== null) {\n cancelAnimationFrame(state.momentumId)\n state.momentumId = null\n }\n\n state.isDragging = true\n state.startX = getEventX(event)\n state.startScrollLeft = feed.scrollLeft\n state.velocity = 0\n state.lastX = state.startX\n state.lastTime = performance.now()\n\n // Visual feedback\n feed.style.cursor = 'grabbing'\n feed.style.userSelect = 'none'\n\n // Prevent default for mouse to avoid text selection\n if (event.type === 'mousedown') {\n event.preventDefault()\n }\n }\n\n const handleDragMove = (event: MouseEvent | TouchEvent) => {\n if (!state.isDragging) return\n\n const currentX = getEventX(event)\n const currentTime = performance.now()\n const deltaX = state.startX - currentX\n const deltaTime = currentTime - state.lastTime\n\n // Update scroll position\n feed.scrollLeft = state.startScrollLeft + deltaX\n\n // Calculate velocity for momentum\n if (deltaTime > 0) {\n state.velocity = (state.lastX - currentX) / deltaTime * 16 // Normalize to ~60fps\n }\n\n state.lastX = currentX\n state.lastTime = currentTime\n\n // Prevent default to stop page scrolling on touch\n if (event.type === 'touchmove') {\n event.preventDefault()\n }\n }\n\n const handleDragEnd = () => {\n if (!state.isDragging) return\n\n state.isDragging = false\n\n // Reset visual feedback\n feed.style.cursor = 'grab'\n feed.style.userSelect = ''\n\n // Apply momentum if velocity is significant\n if (Math.abs(state.velocity) > 1) {\n applyMomentum(state, feed, slides, smoothScrollTo, onDragEnd)\n } else {\n // Snap to nearest slide immediately\n const nearestSlide = findNearestSlide(feed, slides)\n if (nearestSlide) {\n smoothScrollTo(nearestSlide.offsetLeft, easeOutCubic)\n }\n onDragEnd?.()\n }\n }\n\n // Set initial cursor\n feed.style.cursor = 'grab'\n\n // Mouse events\n feed.addEventListener('mousedown', handleDragStart, { signal: abortSignal })\n document.addEventListener('mousemove', handleDragMove, { signal: abortSignal })\n document.addEventListener('mouseup', handleDragEnd, { signal: abortSignal })\n\n // Touch events\n feed.addEventListener('touchstart', handleDragStart, {\n passive: true,\n signal: abortSignal\n })\n feed.addEventListener('touchmove', handleDragMove, {\n passive: false, // Need to prevent default\n signal: abortSignal\n })\n feed.addEventListener('touchend', handleDragEnd, { signal: abortSignal })\n\n // Handle mouse leaving the window\n document.addEventListener('mouseleave', handleDragEnd, { signal: abortSignal })\n\n return state\n}\n\n/**\n * Clean up drag state (cancel any pending animations)\n */\nexport const cleanupDrag = (state: DragState): void => {\n if (state.momentumId !== null) {\n cancelAnimationFrame(state.momentumId)\n state.momentumId = null\n }\n}\n","import type { SliderSettings, SliderState } from './types.js'\n\n/**\n * Marquee state - tracks cloned slides for marquee mode\n */\nexport interface MarqueeState {\n initialized: boolean\n clonedSlides: HTMLElement[]\n styleElement: HTMLStyleElement | null\n}\n\n/**\n * Create initial marquee state\n */\nexport const createMarqueeState = (): MarqueeState => ({\n initialized: false,\n clonedSlides: [],\n styleElement: null\n})\n\n/**\n * Inject CSS keyframes for smooth marquee animation\n */\nconst injectMarqueeKeyframes = (): HTMLStyleElement => {\n // Check if keyframes already exist\n const existingStyle = document.getElementById('lazer-marquee-keyframes')\n if (existingStyle) {\n return existingStyle as HTMLStyleElement\n }\n\n const style = document.createElement('style')\n style.id = 'lazer-marquee-keyframes'\n style.textContent = `\n @keyframes lazer-marquee-scroll {\n from {\n transform: translateX(0);\n }\n to {\n transform: translateX(calc(-1 * var(--lazer-marquee-distance) * 1px));\n }\n }\n `\n document.head.appendChild(style)\n return style\n}\n\n/**\n * Clone all slides and append for seamless marquee looping\n */\nexport const setupMarqueeClones = (\n settings: SliderSettings,\n marqueeState: MarqueeState\n): void => {\n if (marqueeState.initialized) return\n\n // Inject CSS keyframes\n marqueeState.styleElement = injectMarqueeKeyframes()\n\n // Ensure feed has proper CSS for transform animation\n // Note: Parent container should have overflow: hidden for marquee effect\n settings.feed.style.display = 'flex'\n settings.feed.style.willChange = 'transform'\n\n // Clone all original slides and append to create seamless loop\n settings.slides.forEach((slide) => {\n const clone = slide.cloneNode(true) as HTMLElement\n clone.setAttribute('data-lazer-marquee-clone', 'true')\n clone.setAttribute('aria-hidden', 'true')\n settings.feed.appendChild(clone)\n marqueeState.clonedSlides.push(clone)\n })\n\n marqueeState.initialized = true\n}\n\n/**\n * Remove marquee cloned slides from the DOM\n */\nexport const cleanupMarqueeClones = (marqueeState: MarqueeState): void => {\n if (!marqueeState.initialized) return\n\n marqueeState.clonedSlides.forEach((clone) => {\n clone.remove()\n })\n\n marqueeState.clonedSlides = []\n marqueeState.initialized = false\n}\n\n/**\n * Setup marquee CSS animation for smooth hardware-accelerated scrolling\n */\nconst setupMarqueeCss = (\n settings: SliderSettings,\n state: SliderState\n): void => {\n const scrollWidth = settings.feed.scrollWidth\n const speed = settings.marqueeSpeed ?? 50 // pixels per second\n const direction = settings.marqueeDirection ?? 'left'\n const distance = scrollWidth / 2\n\n // Set CSS custom property for the exact pixel distance\n settings.feed.style.setProperty('--lazer-marquee-distance', String(distance))\n\n // Calculate duration: distance / speed\n const duration = distance / speed\n const animationDirection = direction === 'right' ? 'reverse' : 'normal'\n\n // Apply smooth CSS animation with hardware acceleration\n settings.feed.style.animation = `lazer-marquee-scroll ${duration}s linear infinite ${animationDirection}`\n settings.feed.style.animationPlayState = state.marqueePaused ? 'paused' : 'running'\n}\n\n/**\n * Start marquee animation using smooth CSS animations\n */\nexport const startMarquee = (\n settings: SliderSettings,\n state: SliderState\n): void => {\n // Setup CSS animation for smooth hardware-accelerated scrolling\n setupMarqueeCss(settings, state)\n}\n\n/**\n * Stop marquee animation\n */\nexport const stopMarquee = (state: SliderState, settings: SliderSettings): void => {\n // Remove animation\n settings.feed.style.animation = ''\n settings.feed.style.animationPlayState = ''\n\n // Reset transform and CSS custom property\n settings.feed.style.transform = ''\n settings.feed.style.willChange = ''\n settings.feed.style.removeProperty('--lazer-marquee-distance')\n}\n\n/**\n * Pause marquee animation (temporary, resumes on mouse leave)\n */\nexport const pauseMarquee = (state: SliderState, settings: SliderSettings): void => {\n state.marqueePaused = true\n settings.feed.style.animationPlayState = 'paused'\n}\n\n/**\n * Resume marquee animation after pause\n */\nexport const resumeMarquee = (state: SliderState, settings: SliderSettings): void => {\n state.marqueePaused = false\n settings.feed.style.animationPlayState = 'running'\n}\n\n/**\n * Setup marquee mode (clones + animation)\n */\nexport const setupMarquee = (\n settings: SliderSettings,\n state: SliderState,\n marqueeState: MarqueeState\n): void => {\n if (!settings.marquee) return\n\n setupMarqueeClones(settings, marqueeState)\n startMarquee(settings, state)\n}\n\n/**\n * Attach marquee pause-on-hover event listeners\n */\nexport const attachMarqueeEventListeners = (\n settings: SliderSettings,\n state: SliderState,\n signal: AbortSignal\n): void => {\n if (!settings.marquee || settings.pauseOnHover === false) return\n\n settings.feed.addEventListener(\n 'mouseenter',\n () => pauseMarquee(state, settings),\n { signal }\n )\n settings.feed.addEventListener(\n 'mouseleave',\n () => resumeMarquee(state, settings),\n { signal }\n )\n settings.feed.addEventListener(\n 'touchstart',\n () => pauseMarquee(state, settings),\n { passive: true, signal }\n )\n settings.feed.addEventListener(\n 'touchend',\n () => resumeMarquee(state, settings),\n { signal }\n )\n}\n","import type {\n SliderSettings,\n SliderState,\n SliderDirection,\n EasingFunction,\n Slider,\n DragState\n} from './types.js'\nimport { easeOutExpo } from './easing.js'\nimport {\n initAria,\n toggleControlVisibility,\n updateActiveThumb,\n setupKeyboardNavigation\n} from './accessibility.js'\nimport { setupDragToScroll, cleanupDrag } from './drag.js'\nimport {\n createMarqueeState,\n setupMarquee,\n startMarquee,\n stopMarquee,\n pauseMarquee,\n resumeMarquee,\n cleanupMarqueeClones,\n attachMarqueeEventListeners\n} from './marquee.js'\n\n/**\n * Animation duration configuration\n */\nconst ANIMATION = {\n MIN_DURATION: 400,\n MAX_DURATION: 1000,\n SPEED_FACTOR: 1.5,\n SCROLL_END_DELAY: 50,\n THUMB_UPDATE_DELAY: 500\n} as const\n\n/**\n * Desktop breakpoint for responsive slidesPerScroll\n */\nconst DESKTOP_BREAKPOINT = '(min-width: 64rem)'\n\n/**\n * Create a slider instance\n */\nexport const createSlider = (settings: SliderSettings): Slider => {\n // Validate required settings\n if (!settings.feed) {\n throw new Error('lazer-slider: feed element is required')\n }\n\n if (!settings.slides?.length) {\n throw new Error('lazer-slider: slides array is required and must not be empty')\n }\n\n // Initialize state\n const state: SliderState = {\n currentSlideIndex: 0,\n isScrolling: false,\n ticking: false,\n cachedFeedRect: null,\n lastWidth: 0,\n updateThumbTimeout: null,\n scrollEndTimeout: null,\n abortController: new AbortController(),\n autoplayIntervalId: null,\n autoplayPaused: false,\n marqueeAnimationId: null,\n marqueePaused: false,\n marqueeLastTimestamp: 0\n }\n\n // Drag state (initialized if drag is enabled)\n let dragState: DragState | null = null\n\n // Loop state - tracks cloned slides for infinite loop\n const loopState = {\n initialized: false,\n clonedSlides: [] as HTMLElement[],\n realSlides: [...settings.slides] as HTMLElement[],\n clonesPerSide: 0\n }\n\n // Marquee state - tracks cloned slides for marquee mode\n const marqueeState = createMarqueeState()\n\n // Default easing function\n const easing = settings.easing ?? easeOutExpo\n\n /**\n * Get cached feed bounding rect (invalidated on resize)\n */\n const getFeedRect = (): DOMRect => {\n const currentWidth = settings.feed.clientWidth\n if (!state.cachedFeedRect || state.lastWidth !== currentWidth) {\n state.cachedFeedRect = settings.feed.getBoundingClientRect()\n state.lastWidth = currentWidth\n }\n return state.cachedFeedRect\n }\n\n /**\n * Get only visible slides (not hidden via CSS)\n */\n const getVisibleSlides = (): HTMLElement[] => {\n return settings.slides.filter((slide) => slide.offsetParent !== null)\n }\n\n /**\n * Check if we're on desktop based on breakpoint\n */\n const isDesktop = (): boolean => {\n return window.matchMedia(DESKTOP_BREAKPOINT).matches\n }\n\n /**\n * Get the number of slides to clone per side for infinite loop\n */\n const getLoopClonesCount = (): number => {\n const perView = isDesktop()\n ? settings.desktopSlidesPerView\n : settings.mobileSlidesPerView\n\n // Clone at least 1 slide, or the number of visible slides\n if (!perView || perView === 'auto') {\n return 1\n }\n return Math.ceil(perView)\n }\n\n /**\n * Setup cloned slides for infinite loop functionality\n * Clones first N slides to the end and last N slides to the beginning\n */\n const setupLoopClones = (): void => {\n if (!settings.loop || loopState.initialized) return\n\n const realSlides = loopState.realSlides\n const clonesCount = getLoopClonesCount()\n loopState.clonesPerSide = clonesCount\n\n // Clone last N slides and prepend to beginning\n for (let i = realSlides.length - clonesCount; i < realSlides.length; i++) {\n const slide = realSlides[i]\n if (!slide) continue\n\n const clone = slide.cloneNode(true) as HTMLElement\n clone.setAttribute('data-lazer-clone', 'prepend')\n clone.setAttribute('aria-hidden', 'true')\n settings.feed.insertBefore(clone, settings.feed.firstChild)\n loopState.clonedSlides.push(clone)\n }\n\n // Clone first N slides and append to end\n for (let i = 0; i < clonesCount; i++) {\n const slide = realSlides[i]\n if (!slide) continue\n\n const clone = slide.cloneNode(true) as HTMLElement\n clone.setAttribute('data-lazer-clone', 'append')\n clone.setAttribute('aria-hidden', 'true')\n settings.feed.appendChild(clone)\n loopState.clonedSlides.push(clone)\n }\n\n // Set initial scroll position to first real slide (after prepended clones)\n requestAnimationFrame(() => {\n const firstRealSlide = realSlides[0]\n if (firstRealSlide) {\n settings.feed.scrollLeft = firstRealSlide.offsetLeft\n }\n })\n\n loopState.initialized = true\n }\n\n /**\n * Handle repositioning when reaching clone boundaries\n * Silently jumps to the corresponding real slide without animation\n */\n const handleLoopReposition = (direction: SliderDirection): void => {\n if (!settings.loop || !loopState.initialized) return\n\n const realSlides = loopState.realSlides\n const totalRealSlides = realSlides.length\n\n if (direction === 'next') {\n const firstRealSlide = realSlides[0]\n if (firstRealSlide) {\n settings.feed.scrollLeft = firstRealSlide.offsetLeft\n }\n state.currentSlideIndex = 0\n } else {\n const lastRealSlide = realSlides[totalRealSlides - 1]\n if (lastRealSlide) {\n settings.feed.scrollLeft = lastRealSlide.offsetLeft\n }\n state.currentSlideIndex = totalRealSlides - 1\n }\n }\n\n /**\n * Remove cloned slides from the DOM\n */\n const cleanupLoopClones = (): void => {\n if (!loopState.initialized) return\n\n loopState.clonedSlides.forEach((clone) => {\n clone.remove()\n })\n\n loopState.clonedSlides = []\n loopState.initialized = false\n loopState.clonesPerSide = 0\n }\n\n /**\n * Apply slide widths based on slidesPerView settings\n */\n const applySlideWidths = (): void => {\n const perView = isDesktop()\n ? settings.desktopSlidesPerView\n : settings.mobileSlidesPerView\n\n const gap = settings.slideGap ?? 0\n\n // Set gap on the feed container\n if (gap > 0) {\n settings.feed.style.gap = `${gap}px`\n }\n\n // If 'auto' or not set, let CSS handle widths (natural sizing)\n if (!perView || perView === 'auto') {\n // Reset any previously set widths\n settings.slides.forEach((slide) => {\n slide.style.flex = ''\n slide.style.minWidth = ''\n })\n return\n }\n\n const totalGapWidth = gap * (perView - 1)\n const slideWidth = `calc((100% - ${totalGapWidth}px) / ${perView})`\n\n settings.slides.forEach((slide) => {\n slide.style.flex = `0 0 ${slideWidth}`\n slide.style.minWidth = slideWidth\n })\n }\n\n /**\n * Update scrollbar thumb width based on visible content\n */\n const updateScrollbar = (): void => {\n if (!settings.scrollbarThumb) return\n\n const feedRect = getFeedRect()\n const thumbWidth = (feedRect.width / settings.feed.scrollWidth) * 100\n settings.scrollbarThumb.style.width = `${thumbWidth}%`\n }\n\n /**\n * Update scrollbar thumb position based on scroll\n */\n const updateScrollbarPosition = (): void => {\n if (!settings.scrollbarThumb || !settings.scrollbarTrack) return\n\n const trackWidth = settings.scrollbarTrack.getBoundingClientRect().width\n const thumbWidth = settings.scrollbarThumb.getBoundingClientRect().width\n const totalTransform = trackWidth - thumbWidth\n\n const maxScroll = settings.feed.scrollWidth - settings.feed.clientWidth\n const scrollProgress = maxScroll > 0 ? settings.feed.scrollLeft / maxScroll : 0\n\n settings.scrollbarThumb.style.transform = `translateX(${totalTransform * scrollProgress}px)`\n }\n\n /**\n * Update visibility of navigation controls based on scroll position\n */\n const updateControlsVisibility = (): void => {\n const feedRect = getFeedRect()\n const isAtStart = settings.feed.scrollLeft <= 1 // Allow 1px tolerance\n const isAtEnd =\n settings.feed.scrollLeft + feedRect.width >= settings.feed.scrollWidth - 1\n const shouldHideScrollbar = settings.feed.scrollWidth <= feedRect.width\n\n // Hide/show scrollbar track\n if (settings.scrollbarTrack) {\n settings.scrollbarTrack.style.display = shouldHideScrollbar ? 'none' : 'block'\n }\n\n // When loop is enabled, always show both buttons (unless scrollbar should be hidden)\n if (settings.loop) {\n toggleControlVisibility(\n settings.prevSlideButton,\n !shouldHideScrollbar,\n settings.feed\n )\n toggleControlVisibility(\n settings.nextSlideButton,\n !shouldHideScrollbar,\n settings.feed\n )\n return\n }\n\n // Hide/show navigation buttons based on position\n toggleControlVisibility(\n settings.prevSlideButton,\n !isAtStart && !shouldHideScrollbar,\n settings.feed\n )\n toggleControlVisibility(\n settings.nextSlideButton,\n !isAtEnd && !shouldHideScrollbar,\n settings.feed\n )\n }\n\n /**\n * Calculate current slide index based on viewport position\n */\n const updateCurrentSlideIndex = (): void => {\n const feedRect = getFeedRect()\n\n // Use real slides for index calculation when loop is enabled\n const slidesToCheck = loopState.initialized\n ? loopState.realSlides\n : getVisibleSlides()\n\n // Find slides that are visible in the viewport\n const viewportVisibleSlides = slidesToCheck.filter((slide) => {\n const slideRect = slide.getBoundingClientRect()\n const tolerance = 20 // px tolerance for edge detection\n return (\n slideRect.right > feedRect.left + tolerance &&\n slideRect.left < feedRect.right - tolerance\n )\n })\n\n if (viewportVisibleSlides.length && viewportVisibleSlides[0]) {\n const newIndex = slidesToCheck.indexOf(viewportVisibleSlides[0])\n if (newIndex !== -1) {\n state.currentSlideIndex = newIndex\n settings.onScroll?.({\n currentScroll: settings.feed.scrollLeft,\n currentSlideIndex: state.currentSlideIndex\n })\n }\n }\n }\n\n /**\n * Smooth scroll to a target position using requestAnimationFrame\n */\n const smoothScrollTo = (\n target: number,\n customEasing: EasingFunction = easing,\n onComplete?: () => void\n ): void => {\n const start = settings.feed.scrollLeft\n const distance = Math.abs(target - start)\n\n // Dynamic duration based on distance\n const duration = Math.min(\n ANIMATION.MAX_DURATION,\n Math.max(ANIMATION.MIN_DURATION, distance / ANIMATION.SPEED_FACTOR)\n )\n\n const startTime = performance.now()\n\n const animateScroll = (currentTime: number): void => {\n const elapsed = (currentTime - startTime) / duration\n const progress = Math.min(elapsed, 1)\n const ease = customEasing(progress)\n\n settings.feed.scrollLeft = start + (target - start) * ease\n\n if (progress < 1) {\n requestAnimationFrame(animateScroll)\n } else {\n settings.feed.scrollLeft = target\n onComplete?.()\n }\n }\n\n requestAnimationFrame(animateScroll)\n }\n\n /**\n * Handle thumbnail click navigation\n */\n const handleThumbClick = (thumb: HTMLElement): void => {\n if (!settings.thumbs) return\n\n const index = settings.thumbs.indexOf(thumb)\n if (index === -1 || !settings.slides[index]) return\n\n state.currentSlideIndex = index\n updateActiveThumb(settings.thumbs, index)\n state.isScrolling = true\n\n // Clear existing timeout\n if (state.updateThumbTimeout) {\n clearTimeout(state.updateThumbTimeout)\n }\n\n // Reset scrolling flag after animation\n state.updateThumbTimeout = setTimeout(() => {\n state.isScrolling = false\n }, ANIMATION.THUMB_UPDATE_DELAY)\n\n smoothScrollTo(settings.slides[index].offsetLeft)\n }\n\n /**\n * Handle navigation button click (prev/next)\n */\n const handleNavButtonClick = (direction: SliderDirection): void => {\n const realSlides = loopState.initialized ? loopState.realSlides : getVisibleSlides()\n const slidesToScroll = isDesktop()\n ? settings.desktopSlidesPerScroll ?? 1\n : settings.mobileSlidesPerScroll ?? 1\n const totalRealSlides = realSlides.length\n\n // Update current index first\n updateCurrentSlideIndex()\n\n let targetSlide: HTMLElement | undefined\n let needsReposition = false\n\n if (direction === 'prev') {\n if (settings.loop && loopState.initialized && state.currentSlideIndex === 0) {\n\n const prependedClones = loopState.clonedSlides.filter(\n (clone) => clone.getAttribute('data-lazer-clone') === 'prepend'\n )\n targetSlide = prependedClones[prependedClones.length - 1]\n needsReposition = true\n } else {\n state.currentSlideIndex = Math.max(0, state.currentSlideIndex - slidesToScroll)\n targetSlide = realSlides[state.currentSlideIndex]\n }\n } else {\n if (settings.loop && loopState.initialized && state.currentSlideIndex >= totalRealSlides - 1) {\n const appendedClones = loopState.clonedSlides.filter(\n (clone) => clone.getAttribute('data-lazer-clone') === 'append'\n )\n targetSlide = appendedClones[0]\n needsReposition = true\n } else {\n state.currentSlideIndex = Math.min(\n totalRealSlides - 1,\n state.currentSlideIndex + slidesToScroll\n )\n targetSlide = realSlides[state.currentSlideIndex]\n }\n }\n\n if (!targetSlide) return\n\n // Fire scroll start callback\n settings.onScrollStart?.({\n currentScroll: settings.feed.scrollLeft,\n target: targetSlide,\n direction\n })\n\n if (needsReposition) {\n // Scroll to clone, then silently reposition to the real slide\n smoothScrollTo(targetSlide.offsetLeft, easing, () => {\n handleLoopReposition(direction)\n })\n } else {\n smoothScrollTo(targetSlide.offsetLeft)\n }\n }\n\n /**\n * Update all scroll-related state\n */\n const updateScrollPosition = (): void => {\n updateScrollbarPosition()\n updateControlsVisibility()\n updateCurrentSlideIndex()\n\n // Update thumbs only when not programmatically scrolling\n if (!state.isScrolling) {\n updateActiveThumb(settings.thumbs, state.currentSlideIndex)\n }\n\n // Clear existing timeout\n if (state.scrollEndTimeout) {\n clearTimeout(state.scrollEndTimeout)\n }\n\n // Fire scroll end callback after scroll settles\n state.scrollEndTimeout = setTimeout(() => {\n state.isScrolling = false\n settings.onScrollEnd?.({\n currentScroll: settings.feed.scrollLeft,\n currentSlideIndex: state.currentSlideIndex\n })\n }, ANIMATION.SCROLL_END_DELAY)\n }\n\n /**\n * Throttled scroll event handler\n */\n const handleFeedScroll = (): void => {\n if (!state.ticking) {\n requestAnimationFrame(() => {\n updateScrollPosition()\n state.ticking = false\n })\n state.ticking = true\n }\n }\n\n /**\n * Handle window resize\n */\n const handleWindowResize = (): void => {\n state.cachedFeedRect = null\n refresh()\n }\n\n /**\n * Attach all event listeners\n */\n const attachEventListeners = (): void => {\n const { signal } = state.abortController\n\n // Resize handler (not abortable, cleaned up manually)\n window.addEventListener('resize', handleWindowResize)\n\n // Scroll handler\n settings.feed.addEventListener('scroll', handleFeedScroll, {\n passive: true,\n signal\n })\n\n // Navigation button handlers\n if (settings.prevSlideButton) {\n settings.prevSlideButton.addEventListener(\n 'click',\n () => handleNavButtonClick('prev'),\n { signal }\n )\n }\n\n if (settings.nextSlideButton) {\n settings.nextSlideButton.addEventListener(\n 'click',\n () => handleNavButtonClick('next'),\n { signal }\n )\n }\n\n // Thumbnail handlers\n if (settings.thumbs?.length) {\n settings.thumbs[0]?.classList.add('active')\n settings.thumbs.forEach((thumb) => {\n thumb.addEventListener('click', () => handleThumbClick(thumb), { signal })\n })\n }\n\n // Keyboard navigation\n setupKeyboardNavigation(\n settings.feed,\n () => handleNavButtonClick('prev'),\n () => handleNavButtonClick('next'),\n signal\n )\n\n // Drag-to-scroll\n if (settings.enableDragToScroll) {\n dragState = setupDragToScroll({\n feed: settings.feed,\n slides: settings.slides,\n abortSignal: signal,\n smoothScrollTo,\n onDragEnd: () => {\n updateCurrentSlideIndex()\n updateActiveThumb(settings.thumbs, state.currentSlideIndex)\n }\n })\n }\n\n // Autoplay pause on hover/touch\n if (settings.autoplay && settings.pauseOnHover !== false) {\n settings.feed.addEventListener(\n 'mouseenter',\n pauseAutoplay,\n { signal }\n )\n settings.feed.addEventListener(\n 'mouseleave',\n resumeAutoplay,\n { signal }\n )\n settings.feed.addEventListener(\n 'touchstart',\n pauseAutoplay,\n { passive: true, signal }\n )\n settings.feed.addEventListener(\n 'touchend',\n resumeAutoplay,\n { signal }\n )\n }\n\n // Marquee pause on hover/touch\n attachMarqueeEventListeners(settings, state, signal)\n }\n\n /**\n * Start autoplay\n */\n const startAutoplay = (): void => {\n if (state.autoplayIntervalId) return;\n\n const interval = settings.autoplayInterval ?? 3000\n state.autoplayIntervalId = setInterval(() => {\n if (!state.autoplayPaused) {\n handleNavButtonClick('next')\n }\n }, interval)\n }\n\n /**\n * Stop autoplay\n */\n const stopAutoplay = (): void => {\n if (state.autoplayIntervalId) {\n clearInterval(state.autoplayIntervalId)\n state.autoplayIntervalId = null\n }\n }\n\n /**\n * Pause autoplay (temporary, resumes on mouse leave)\n */\n const pauseAutoplay = (): void => {\n state.autoplayPaused = true\n }\n\n /**\n * Resume autoplay after pause\n */\n const resumeAutoplay = (): void => {\n state.autoplayPaused = false\n }\n\n /**\n * Navigate to a specific slide by index\n */\n const goToIndex = (index: number): void => {\n // Use real slides when loop is enabled, otherwise use visible slides\n const slides = loopState.initialized ? loopState.realSlides : getVisibleSlides()\n const safeIndex = Math.max(0, Math.min(index, slides.length - 1))\n const targetSlide = slides[safeIndex]\n\n if (!targetSlide) return\n\n state.currentSlideIndex = safeIndex\n updateActiveThumb(settings.thumbs, safeIndex)\n smoothScrollTo(targetSlide.offsetLeft)\n }\n\n /**\n * Refresh slider state (recalculate dimensions, update controls)\n */\n const refresh = (): void => {\n state.cachedFeedRect = null\n applySlideWidths()\n updateScrollbar()\n updateControlsVisibility()\n\n // Recalculate marquee animation if in marquee mode\n if (settings.marquee && !state.marqueePaused) {\n startMarquee(settings, state)\n }\n }\n\n /**\n * Clean up all event listeners and timers\n */\n const unload = (): void => {\n // Stop autoplay and marquee\n stopAutoplay()\n stopMarquee(state, settings)\n\n // Abort all event listeners\n state.abortController.abort()\n\n // Remove resize listener\n window.removeEventListener('resize', handleWindowResize)\n\n // Clear timeouts\n if (state.updateThumbTimeout) {\n clearTimeout(state.updateThumbTimeout)\n }\n if (state.scrollEndTimeout) {\n clearTimeout(state.scrollEndTimeout)\n }\n\n // Clean up drag state\n if (dragState) {\n cleanupDrag(dragState)\n }\n\n // Clean up loop clones and marquee clones\n cleanupLoopClones()\n cleanupMarqueeClones(marqueeState)\n\n // Reset cached rect\n state.cachedFeedRect = null\n }\n\n // Initialize slider\n initAria(settings)\n applySlideWidths()\n\n // Marquee mode takes precedence over loop/autoplay\n if (settings.marquee) {\n setupMarquee(settings, state, marqueeState)\n } else {\n setupLoopClones()\n // Start autoplay if enabled (only in non-marquee mode)\n if (settings.autoplay) {\n startAutoplay()\n }\n }\n\n updateControlsVisibility()\n attachEventListeners()\n updateScrollbar()\n\n // Unified play/pause that works for both autoplay and marquee\n const play = (): void => {\n if (settings.marquee) {\n if (state.marqueePaused) {\n resumeMarquee(state, settings)\n } else {\n startMarquee(settings, state)\n }\n } else if (settings.autoplay) {\n startAutoplay()\n }\n }\n\n const pause = (): void => {\n if (settings.marquee) {\n pauseMarquee(state, settings)\n } else {\n stopAutoplay()\n }\n }\n\n return {\n goToIndex,\n refresh,\n unload,\n play,\n pause,\n next: () => handleNavButtonClick('next'),\n prev: () => handleNavButtonClick('prev')\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED