@silverlila/slider 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/core.d.ts ADDED
@@ -0,0 +1,114 @@
1
+ import { ScrollAnimation, Axis } from 'use-scroller/core';
2
+ export { Axis, ScrollAnimation } from 'use-scroller/core';
3
+
4
+ type Align = 'start' | 'center' | 'end';
5
+ interface SliderOptions {
6
+ /** Scroll axis. Default `'x'`. */
7
+ axis?: Axis;
8
+ /** Where a snapped slide sits in the viewport. Default `'start'`. */
9
+ align?: Align;
10
+ /** Slides per snap: an integer >= 1, or `'auto'` for as many as fully fit. Default `1`. */
11
+ slidesPerMove?: number | 'auto';
12
+ /** Infinite loop through clones. Default `false`. */
13
+ loop?: boolean;
14
+ /** Wrap from the last snap to the first (and back) by scrolling across. Ignored with `loop`. */
15
+ rewind?: boolean;
16
+ /** Mouse and touch dragging via use-scroller's `dragScroll`. Default `true`. */
17
+ draggable?: boolean;
18
+ /** Snap shown at creation, clamped into range. Default `0`. */
19
+ startIndex?: number;
20
+ /** Animation for `next`/`prev`/`scrollTo`. Default `{ type: 'tween' }`. */
21
+ animation?: ScrollAnimation;
22
+ }
23
+ interface SliderState {
24
+ /** Active snap index; the target index while a navigation is pending. */
25
+ index: number;
26
+ snapCount: number;
27
+ /** Real slides only, clones excluded. */
28
+ slideCount: number;
29
+ canPrev: boolean;
30
+ canNext: boolean;
31
+ /** 0..1 along the snaps, following the real scroll position. */
32
+ progress: number;
33
+ /** Copies of the slide set needed on each side; 0 unless looping. */
34
+ loopCopies: number;
35
+ }
36
+ interface NavigateOptions {
37
+ /** Overrides `SliderOptions.animation` for this call. */
38
+ animation?: ScrollAnimation;
39
+ }
40
+ interface Slider {
41
+ readonly viewport: HTMLElement;
42
+ readonly options: Readonly<Required<Omit<SliderOptions, 'startIndex'>>>;
43
+ getState(): SliderState;
44
+ subscribe(listener: (state: SliderState) => void): () => void;
45
+ next(options?: NavigateOptions): void;
46
+ prev(options?: NavigateOptions): void;
47
+ scrollTo(index: number, options?: NavigateOptions): void;
48
+ destroy(): void;
49
+ }
50
+
51
+ /**
52
+ * Timed advancing for a slider, with the pauses WCAG 2.2.2 users expect.
53
+ *
54
+ * Browsers leave focus on a button after a mouse click, so only focus that matches
55
+ * `:focus-visible` (keyboard focus) pauses; otherwise clicking Next or the toggle would keep
56
+ * autoplay paused until the user clicked somewhere else.
57
+ *
58
+ * `play()` clears the current hover and focus pauses, so a Play button starts it even though
59
+ * pressing it put the pointer and focus inside the slider.
60
+ */
61
+
62
+ interface AutoplayOptions {
63
+ /** Milliseconds between moves; must be > 0. Default `4000`. */
64
+ delay?: number;
65
+ /** Element whose hover and keyboard focus pause autoplay. Default `slider.viewport`. */
66
+ root?: HTMLElement;
67
+ /** Pause while the pointer is over `root`. Default `true`. */
68
+ pauseOnHover?: boolean;
69
+ /** Stop for good on `pointerdown`, `wheel` or `keydown` on the viewport. Default `true`. */
70
+ stopOnInteraction?: boolean;
71
+ }
72
+ interface Autoplay {
73
+ play(): void;
74
+ stop(): void;
75
+ isPlaying(): boolean;
76
+ /** Called with the new playing state on change only. */
77
+ subscribe(listener: (playing: boolean) => void): () => void;
78
+ destroy(): void;
79
+ }
80
+ /**
81
+ * Advances `slider` every `delay` ms, back to the first snap at the end. Pauses while hovered,
82
+ * while keyboard focus is inside `root`, while a pointer is pressed on the viewport and while the
83
+ * tab is hidden. Starts stopped under `prefers-reduced-motion: reduce`.
84
+ */
85
+ declare function autoplay(slider: Slider, options?: AutoplayOptions): Autoplay;
86
+
87
+ /**
88
+ * Renders a looping slider's clones into its viewport for vanilla use: `loopCopies` copies of the
89
+ * real slide set on each side, re-rendered whenever `loopCopies` or `slideCount` changes.
90
+ * Returns a cleanup that stops rendering and removes every clone.
91
+ */
92
+ declare function renderClones(slider: Slider): () => void;
93
+
94
+ /**
95
+ * Writes `--slide-progress` (signed distance from the aligned spot, in slide steps, positive for
96
+ * upcoming slides) and `--slide-distance` (its absolute value) on every child of the viewport,
97
+ * clones included, whenever the scroll position or the layout changes. It follows the scroll
98
+ * position rather than slider state because a loop recentre moves the position without changing
99
+ * the state. Returns a cleanup that removes both properties.
100
+ */
101
+ declare function slideProgress(slider: Slider): () => void;
102
+
103
+ declare function createSlider(viewport: HTMLElement, options?: SliderOptions): Slider;
104
+
105
+ /**
106
+ * Keeps a thumbnail slider on the main slider's index and lets a click on thumbnail slide `i`
107
+ * move the main slider to snap `i`. The thumbnail matching the main index carries
108
+ * `aria-current="true"` and `data-selected`. Assumes the main slider shows one slide per snap.
109
+ * Returns a cleanup that stops syncing and removes both attributes.
110
+ */
111
+ declare function syncSliders(main: Slider, thumbs: Slider): () => void;
112
+
113
+ export { autoplay, createSlider, renderClones, slideProgress, syncSliders };
114
+ export type { Align, Autoplay, AutoplayOptions, NavigateOptions, Slider, SliderOptions, SliderState };
package/dist/core.js ADDED
@@ -0,0 +1,14 @@
1
+ import {
2
+ autoplay,
3
+ createSlider,
4
+ renderClones,
5
+ slideProgress,
6
+ syncSliders
7
+ } from "./chunk-OAWJ54V3.js";
8
+ export {
9
+ autoplay,
10
+ createSlider,
11
+ renderClones,
12
+ slideProgress,
13
+ syncSliders
14
+ };
@@ -0,0 +1,117 @@
1
+ import { SliderOptions, AutoplayOptions, SliderState, Slider as Slider$1, NavigateOptions } from './core.js';
2
+ export { Align, Autoplay, autoplay, createSlider, renderClones, slideProgress, syncSliders } from './core.js';
3
+ import { Axis } from 'use-scroller/core';
4
+ export { Axis, ScrollAnimation } from 'use-scroller/core';
5
+ import * as react from 'react';
6
+ import { ComponentProps, ReactNode } from 'react';
7
+
8
+ interface UseSliderOptions extends Omit<SliderOptions, 'startIndex'> {
9
+ /** Snap shown first when the index is uncontrolled. Default `0`. */
10
+ defaultIndex?: number;
11
+ /** Controlled snap index: the slider scrolls to it whenever it changes. */
12
+ index?: number;
13
+ /** Called whenever the active snap changes, never for the initial one. */
14
+ onIndexChange?: (index: number) => void;
15
+ /** Advance on a timer: `true` for the defaults, or the options to use. Off by default. */
16
+ autoplay?: boolean | Omit<AutoplayOptions, 'root'>;
17
+ /** Write `--slide-progress` and `--slide-distance` on every slide as it scrolls. */
18
+ slideProgress?: boolean;
19
+ }
20
+ /** A running autoplay: whether it is playing, and the switches to change that. */
21
+ interface AutoplayControls {
22
+ isPlaying: boolean;
23
+ play(): void;
24
+ stop(): void;
25
+ }
26
+ type DiscreteState = Omit<SliderState, 'progress'>;
27
+ interface UseSliderResult extends DiscreteState {
28
+ /** Callback ref for the scroll container; the engine is created once it is attached. */
29
+ viewportRef: (element: HTMLElement | null) => void;
30
+ /** Callback ref for the element wrapping the slider and its controls. */
31
+ rootRef: (element: HTMLElement | null) => void;
32
+ /** The engine, or `null` until the viewport is mounted. */
33
+ slider: Slider$1 | null;
34
+ /** `null` while the `autoplay` option is off or the engine does not exist yet. */
35
+ autoplay: AutoplayControls | null;
36
+ /** Each navigation method does nothing while `slider` is `null`. */
37
+ next(options?: NavigateOptions): void;
38
+ prev(options?: NavigateOptions): void;
39
+ scrollTo(index: number, options?: NavigateOptions): void;
40
+ }
41
+ /** Headless slider: attach `viewportRef` to a scroll container and build any markup around it. */
42
+ declare function useSlider(options?: UseSliderOptions): UseSliderResult;
43
+
44
+ interface SliderContextValue extends UseSliderResult {
45
+ /** The viewport's `id`, for `aria-controls` on the controls. */
46
+ viewportId: string;
47
+ axis: Axis;
48
+ }
49
+ /** The enclosing `<Slider>`'s state and navigation. Throws outside one. */
50
+ declare function useSliderContext(): SliderContextValue;
51
+
52
+ type SliderProps = UseSliderOptions & {
53
+ /** Slides per view (`--slider-per-view`); `'auto'` sizes slides by content. Default `1`. */
54
+ perView?: number | 'auto';
55
+ /** Space between slides, written to `--slider-gap`. A number is in pixels. */
56
+ gap?: number | string;
57
+ /** Accessible name of the carousel region. Default `'Slides'`. */
58
+ label?: string;
59
+ } & Omit<ComponentProps<'div'>, 'children' | 'onChange' | keyof UseSliderOptions> & {
60
+ children: ReactNode;
61
+ };
62
+ declare function SliderRoot({ axis, align, slidesPerMove, loop, rewind, draggable, animation, defaultIndex, index, onIndexChange, autoplay, slideProgress, perView, gap, label, style, ref, children, ...props }: SliderProps): react.JSX.Element;
63
+ /**
64
+ * The scroll container. Its children must be `<Slider.Slide>` elements (or components rendering
65
+ * one); in loop mode each is also rendered as hidden, inert clones on both sides.
66
+ */
67
+ declare function SliderViewport({ ref, children, ...props }: ComponentProps<'div'>): react.JSX.Element;
68
+ /** One slide. Must be rendered inside `<Slider.Viewport>`. */
69
+ declare function SliderSlide(props: ComponentProps<'div'>): react.JSX.Element;
70
+ declare function SliderPrev({ onClick, children, ...props }: ComponentProps<'button'>): react.JSX.Element;
71
+ declare function SliderNext({ onClick, children, ...props }: ComponentProps<'button'>): react.JSX.Element;
72
+ /** One button per snap; the active one carries `aria-current="true"` and `data-selected`. */
73
+ declare function SliderDots(props: Omit<ComponentProps<'div'>, 'children'>): react.JSX.Element;
74
+ type SliderAutoplayToggleProps = Omit<ComponentProps<'button'>, 'children'> & {
75
+ /** The button's content, or a function of the playing state. Default `'Pause'` / `'Play'`. */
76
+ children?: ReactNode | ((isPlaying: boolean) => ReactNode);
77
+ };
78
+ /** Stops and restarts autoplay. Renders nothing when the slider has no `autoplay`. */
79
+ declare function SliderAutoplayToggle({ onClick, children, ...props }: SliderAutoplayToggleProps): react.JSX.Element;
80
+ type SliderThumbsProps = Omit<ComponentProps<'div'>, 'children'> & {
81
+ /** Thumbnails per view; `'auto'` sizes them by content. Default `'auto'`. */
82
+ perView?: number | 'auto';
83
+ /** Space between thumbnails. A number is in pixels. */
84
+ gap?: number | string;
85
+ /** Default `'x'`. */
86
+ axis?: Axis;
87
+ /** `<Slider.Thumb>` elements, one per main slide. */
88
+ children: ReactNode;
89
+ };
90
+ /**
91
+ * A strip of thumbnails that is a slider of its own, kept on the enclosing slider's index:
92
+ * clicking thumbnail `i` moves the slider to snap `i`, and the matching thumbnail carries
93
+ * `aria-current="true"` and `data-selected`. Assumes the main slider shows one slide per snap.
94
+ */
95
+ declare function SliderThumbs({ perView, gap, axis, style, children, ...props }: SliderThumbsProps): react.JSX.Element;
96
+ /** One thumbnail button. Must be rendered inside `<Slider.Thumbs>`. */
97
+ declare function SliderThumb(props: ComponentProps<'button'>): react.JSX.Element;
98
+ declare const Slider: typeof SliderRoot & {
99
+ Viewport: typeof SliderViewport;
100
+ Slide: typeof SliderSlide;
101
+ Prev: typeof SliderPrev;
102
+ Next: typeof SliderNext;
103
+ Dots: typeof SliderDots;
104
+ AutoplayToggle: typeof SliderAutoplayToggle;
105
+ Thumbs: typeof SliderThumbs;
106
+ Thumb: typeof SliderThumb;
107
+ };
108
+
109
+ /**
110
+ * Subscribes to a slice of a slider's state. The component re-renders only when the selected
111
+ * value changes by `isEqual`, so a selector returning an object should pass a shallow comparison.
112
+ * With `slider === null` (not mounted yet) it reads the initial state.
113
+ */
114
+ declare function useSliderState<T>(slider: Slider$1 | null, selector: (state: SliderState) => T, isEqual?: (a: T, b: T) => boolean): T;
115
+
116
+ export { AutoplayOptions, NavigateOptions, Slider, SliderOptions, SliderState, useSlider, useSliderContext, useSliderState };
117
+ export type { AutoplayControls, SliderAutoplayToggleProps, SliderProps, SliderThumbsProps, UseSliderOptions, UseSliderResult };
package/dist/index.js ADDED
@@ -0,0 +1,457 @@
1
+ 'use client'
2
+ import {
3
+ autoplay,
4
+ createSlider,
5
+ renderClones,
6
+ slideProgress,
7
+ syncSliders
8
+ } from "./chunk-OAWJ54V3.js";
9
+
10
+ // src/react/context.ts
11
+ import { createContext, use } from "react";
12
+ var SliderContext = createContext(null);
13
+ function useSliderContext() {
14
+ const context = use(SliderContext);
15
+ if (context === null) throw new Error("Slider components must be used inside <Slider>");
16
+ return context;
17
+ }
18
+ var SlideContext = createContext(null);
19
+ var ThumbContext = createContext(null);
20
+
21
+ // src/react/slider.tsx
22
+ import {
23
+ Children,
24
+ isValidElement,
25
+ use as use2,
26
+ useCallback as useCallback3,
27
+ useEffect as useEffect2,
28
+ useId,
29
+ useMemo as useMemo2
30
+ } from "react";
31
+
32
+ // src/react/use-slider.ts
33
+ import {
34
+ useCallback as useCallback2,
35
+ useEffect,
36
+ useLayoutEffect,
37
+ useMemo,
38
+ useRef as useRef2,
39
+ useState
40
+ } from "react";
41
+
42
+ // src/react/use-slider-state.ts
43
+ import { useCallback, useRef, useSyncExternalStore } from "react";
44
+ var INITIAL_STATE = {
45
+ index: 0,
46
+ snapCount: 0,
47
+ slideCount: 0,
48
+ canPrev: false,
49
+ canNext: false,
50
+ progress: 0,
51
+ loopCopies: 0
52
+ };
53
+ function ignore() {
54
+ }
55
+ function useSliderState(slider, selector, isEqual = Object.is) {
56
+ const last = useRef(null);
57
+ const subscribe = useCallback(
58
+ (onChange) => slider ? slider.subscribe(onChange) : ignore,
59
+ [slider]
60
+ );
61
+ function select(state) {
62
+ const next = selector(state);
63
+ const previous = last.current;
64
+ if (previous !== null && isEqual(previous.value, next)) return previous.value;
65
+ last.current = { value: next };
66
+ return next;
67
+ }
68
+ return useSyncExternalStore(
69
+ subscribe,
70
+ () => select(slider ? slider.getState() : INITIAL_STATE),
71
+ () => select(INITIAL_STATE)
72
+ );
73
+ }
74
+
75
+ // src/react/use-slider.ts
76
+ function useSlider(options = {}) {
77
+ const { axis, align, slidesPerMove, loop, rewind, draggable, animation, index } = options;
78
+ const { autoplay: autoplayOption, slideProgress: tracksProgress = false } = options;
79
+ const [viewport, setViewport] = useState(null);
80
+ const [rootElement, setRootElement] = useState(null);
81
+ const [slider, setSlider] = useState(null);
82
+ const stableAnimation = useShallowStable(animation);
83
+ const latest = useRef2(options);
84
+ const previousIndex = useRef2(null);
85
+ const live = useRef2(null);
86
+ useLayoutEffect(() => {
87
+ latest.current = options;
88
+ });
89
+ useLayoutEffect(() => {
90
+ if (viewport === null) return;
91
+ const start = latest.current;
92
+ const engine = createSlider(viewport, {
93
+ axis,
94
+ align,
95
+ slidesPerMove,
96
+ loop,
97
+ rewind,
98
+ draggable,
99
+ animation: stableAnimation,
100
+ startIndex: previousIndex.current ?? start.index ?? start.defaultIndex
101
+ });
102
+ let reported = engine.getState().index;
103
+ engine.subscribe((state) => {
104
+ var _a, _b;
105
+ if (state.index === reported) return;
106
+ reported = state.index;
107
+ (_b = (_a = latest.current).onIndexChange) == null ? void 0 : _b.call(_a, state.index);
108
+ });
109
+ live.current = engine;
110
+ setSlider(engine);
111
+ return () => {
112
+ previousIndex.current = engine.getState().index;
113
+ live.current = null;
114
+ engine.destroy();
115
+ setSlider(null);
116
+ };
117
+ }, [viewport, axis, align, slidesPerMove, loop, rewind, draggable, stableAnimation]);
118
+ const discrete = useSliderState(slider, selectDiscrete, shallowEqual);
119
+ const autoplayControls = useAutoplay(slider, live, rootElement, autoplayOption);
120
+ useEffect(() => {
121
+ if (!isLive(slider, live) || !tracksProgress) return;
122
+ return slideProgress(slider);
123
+ }, [slider, live, tracksProgress]);
124
+ useEffect(() => {
125
+ const engine = live.current;
126
+ if (engine === null || index === void 0) return;
127
+ if (index !== engine.getState().index) engine.scrollTo(index);
128
+ }, [index, slider]);
129
+ const next = useCallback2((navigate) => slider == null ? void 0 : slider.next(navigate), [slider]);
130
+ const prev = useCallback2((navigate) => slider == null ? void 0 : slider.prev(navigate), [slider]);
131
+ const scrollTo = useCallback2(
132
+ (target, navigate) => slider == null ? void 0 : slider.scrollTo(target, navigate),
133
+ [slider]
134
+ );
135
+ return useMemo(
136
+ () => ({
137
+ ...discrete,
138
+ viewportRef: setViewport,
139
+ rootRef: setRootElement,
140
+ slider,
141
+ autoplay: autoplayControls,
142
+ next,
143
+ prev,
144
+ scrollTo
145
+ }),
146
+ [discrete, slider, autoplayControls, next, prev, scrollTo]
147
+ );
148
+ }
149
+ function useAutoplay(slider, live, rootElement, option) {
150
+ const settings = option === true ? {} : option || null;
151
+ const enabled = settings !== null;
152
+ const delay = settings == null ? void 0 : settings.delay;
153
+ const pauseOnHover = settings == null ? void 0 : settings.pauseOnHover;
154
+ const stopOnInteraction = settings == null ? void 0 : settings.stopOnInteraction;
155
+ const [controls, setControls] = useState(null);
156
+ useLayoutEffect(() => {
157
+ if (!isLive(slider, live) || !enabled) return;
158
+ const player = autoplay(slider, {
159
+ delay,
160
+ pauseOnHover,
161
+ stopOnInteraction,
162
+ root: rootElement ?? slider.viewport
163
+ });
164
+ const publish = (isPlaying) => setControls({ isPlaying, play: () => player.play(), stop: () => player.stop() });
165
+ publish(player.isPlaying());
166
+ player.subscribe(publish);
167
+ return () => {
168
+ player.destroy();
169
+ setControls(null);
170
+ };
171
+ }, [slider, live, enabled, delay, pauseOnHover, stopOnInteraction, rootElement]);
172
+ return controls;
173
+ }
174
+ function isLive(slider, live) {
175
+ return slider !== null && slider === live.current;
176
+ }
177
+ function selectDiscrete(state) {
178
+ return {
179
+ index: state.index,
180
+ snapCount: state.snapCount,
181
+ slideCount: state.slideCount,
182
+ canPrev: state.canPrev,
183
+ canNext: state.canNext,
184
+ loopCopies: state.loopCopies
185
+ };
186
+ }
187
+ function useShallowStable(value) {
188
+ const stable = useRef2(value);
189
+ if (!shallowEqual(stable.current, value)) stable.current = value;
190
+ return stable.current;
191
+ }
192
+ function shallowEqual(a, b) {
193
+ if (a === b) return true;
194
+ if (a === void 0 || b === void 0) return false;
195
+ const entries = Object.entries(a);
196
+ const other = new Map(Object.entries(b));
197
+ return entries.length === other.size && entries.every(([key, value]) => other.has(key) && Object.is(value, other.get(key)));
198
+ }
199
+
200
+ // src/react/slider.tsx
201
+ function SliderRoot({
202
+ axis,
203
+ align,
204
+ slidesPerMove,
205
+ loop,
206
+ rewind,
207
+ draggable,
208
+ animation,
209
+ defaultIndex,
210
+ index,
211
+ onIndexChange,
212
+ autoplay: autoplay2,
213
+ slideProgress: slideProgress2,
214
+ perView = 1,
215
+ gap,
216
+ label,
217
+ style,
218
+ ref,
219
+ children,
220
+ ...props
221
+ }) {
222
+ const result = useSlider({
223
+ axis,
224
+ align,
225
+ slidesPerMove,
226
+ loop,
227
+ rewind,
228
+ draggable,
229
+ animation,
230
+ defaultIndex,
231
+ index,
232
+ onIndexChange,
233
+ autoplay: autoplay2,
234
+ slideProgress: slideProgress2
235
+ });
236
+ const viewportId = useId();
237
+ const resolvedAxis = axis ?? "x";
238
+ const context = useMemo2(
239
+ () => ({ ...result, viewportId, axis: resolvedAxis }),
240
+ [result, viewportId, resolvedAxis]
241
+ );
242
+ const rootRef = useMergedRef(result.rootRef, ref);
243
+ const variables = {
244
+ "--slider-per-view": perView === "auto" ? void 0 : perView,
245
+ "--slider-gap": typeof gap === "number" ? `${gap}px` : gap,
246
+ "--slider-align": align,
247
+ ...style
248
+ };
249
+ return /* @__PURE__ */ React.createElement(SliderContext, { value: context }, /* @__PURE__ */ React.createElement(
250
+ "div",
251
+ {
252
+ ref: rootRef,
253
+ role: "region",
254
+ "aria-roledescription": "carousel",
255
+ "aria-label": label ?? "Slides",
256
+ "data-slider": "",
257
+ style: variables,
258
+ ...props
259
+ },
260
+ children
261
+ ));
262
+ }
263
+ function SliderViewport({ ref, children, ...props }) {
264
+ const { viewportRef, viewportId, axis, loopCopies } = useSliderContext();
265
+ const mergedRef = useMergedRef(viewportRef, ref);
266
+ const slides = Children.toArray(children);
267
+ const real = slides.map((child, i) => /* @__PURE__ */ React.createElement(SlideContext, { value: { index: i, clone: false }, key: keyOf(child, i) }, child));
268
+ const copies = (side) => Array.from(
269
+ { length: loopCopies },
270
+ (_, copy) => slides.map((child, i) => /* @__PURE__ */ React.createElement(SlideContext, { value: { index: i, clone: true }, key: `${side}-${copy}-${keyOf(child, i)}` }, child))
271
+ ).flat();
272
+ return /* @__PURE__ */ React.createElement(
273
+ "div",
274
+ {
275
+ ref: mergedRef,
276
+ id: viewportId,
277
+ "data-slider-viewport": "",
278
+ "data-axis": axis,
279
+ tabIndex: 0,
280
+ ...props
281
+ },
282
+ [...copies("before"), ...real, ...copies("after")]
283
+ );
284
+ }
285
+ function SliderSlide(props) {
286
+ const { slideCount } = useSliderContext();
287
+ const slide = use2(SlideContext);
288
+ if (slide === null) throw new Error("<Slider.Slide> must be rendered inside <Slider.Viewport>");
289
+ if (slide.clone) {
290
+ return /* @__PURE__ */ React.createElement(
291
+ "div",
292
+ {
293
+ "data-slider-slide": "",
294
+ "data-slider-clone": slide.index,
295
+ "aria-hidden": "true",
296
+ inert: true,
297
+ ...props,
298
+ id: void 0
299
+ }
300
+ );
301
+ }
302
+ return /* @__PURE__ */ React.createElement(
303
+ "div",
304
+ {
305
+ "data-slider-slide": "",
306
+ role: "group",
307
+ "aria-roledescription": "slide",
308
+ "aria-label": `${slide.index + 1} of ${slideCount}`,
309
+ ...props
310
+ }
311
+ );
312
+ }
313
+ function SliderPrev({ onClick, children = "Previous", ...props }) {
314
+ const { prev, canPrev, viewportId } = useSliderContext();
315
+ return /* @__PURE__ */ React.createElement(
316
+ "button",
317
+ {
318
+ type: "button",
319
+ "aria-controls": viewportId,
320
+ "aria-label": "Previous slide",
321
+ disabled: !canPrev,
322
+ ...props,
323
+ onClick: (event) => {
324
+ onClick == null ? void 0 : onClick(event);
325
+ if (!event.defaultPrevented) prev();
326
+ }
327
+ },
328
+ children
329
+ );
330
+ }
331
+ function SliderNext({ onClick, children = "Next", ...props }) {
332
+ const { next, canNext, viewportId } = useSliderContext();
333
+ return /* @__PURE__ */ React.createElement(
334
+ "button",
335
+ {
336
+ type: "button",
337
+ "aria-controls": viewportId,
338
+ "aria-label": "Next slide",
339
+ disabled: !canNext,
340
+ ...props,
341
+ onClick: (event) => {
342
+ onClick == null ? void 0 : onClick(event);
343
+ if (!event.defaultPrevented) next();
344
+ }
345
+ },
346
+ children
347
+ );
348
+ }
349
+ function SliderDots(props) {
350
+ const { snapCount, index, scrollTo, viewportId } = useSliderContext();
351
+ return /* @__PURE__ */ React.createElement("div", { role: "group", "aria-label": "Choose slide", "data-slider-dots": "", ...props }, Array.from({ length: snapCount }, (_, i) => /* @__PURE__ */ React.createElement(
352
+ "button",
353
+ {
354
+ key: i,
355
+ type: "button",
356
+ "data-slider-dot": "",
357
+ "aria-controls": viewportId,
358
+ "aria-label": `Go to slide ${i + 1}`,
359
+ "aria-current": i === index ? "true" : void 0,
360
+ "data-selected": i === index ? "" : void 0,
361
+ onClick: () => scrollTo(i)
362
+ }
363
+ )));
364
+ }
365
+ function SliderAutoplayToggle({ onClick, children, ...props }) {
366
+ const { autoplay: autoplay2, viewportId } = useSliderContext();
367
+ if (autoplay2 === null) return null;
368
+ const { isPlaying, play, stop } = autoplay2;
369
+ return /* @__PURE__ */ React.createElement(
370
+ "button",
371
+ {
372
+ type: "button",
373
+ "aria-controls": viewportId,
374
+ "aria-label": isPlaying ? "Stop automatic slide show" : "Start automatic slide show",
375
+ "data-playing": isPlaying ? "" : void 0,
376
+ ...props,
377
+ onClick: (event) => {
378
+ onClick == null ? void 0 : onClick(event);
379
+ if (event.defaultPrevented) return;
380
+ if (isPlaying) stop();
381
+ else play();
382
+ }
383
+ },
384
+ typeof children === "function" ? children(isPlaying) : children ?? (isPlaying ? "Pause" : "Play")
385
+ );
386
+ }
387
+ function SliderThumbs({
388
+ perView = "auto",
389
+ gap,
390
+ axis = "x",
391
+ style,
392
+ children,
393
+ ...props
394
+ }) {
395
+ const main = useSliderContext();
396
+ const thumbs = useSlider({ axis });
397
+ useEffect2(() => {
398
+ if (main.slider === null || thumbs.slider === null) return;
399
+ return syncSliders(main.slider, thumbs.slider);
400
+ }, [main.slider, thumbs.slider]);
401
+ const variables = {
402
+ "--slider-per-view": perView === "auto" ? "initial" : perView,
403
+ "--slider-gap": gap === void 0 ? "initial" : typeof gap === "number" ? `${gap}px` : gap,
404
+ "--slider-align": "start",
405
+ ...style
406
+ };
407
+ return /* @__PURE__ */ React.createElement("div", { "data-slider-thumbs": "", style: variables, ...props }, /* @__PURE__ */ React.createElement("div", { ref: thumbs.viewportRef, "data-slider-viewport": "", "data-axis": axis }, Children.toArray(children).map((child, i) => /* @__PURE__ */ React.createElement(ThumbContext, { value: i, key: keyOf(child, i) }, child))));
408
+ }
409
+ function SliderThumb(props) {
410
+ const { viewportId } = useSliderContext();
411
+ const index = use2(ThumbContext);
412
+ if (index === null) throw new Error("<Slider.Thumb> must be rendered inside <Slider.Thumbs>");
413
+ return /* @__PURE__ */ React.createElement(
414
+ "button",
415
+ {
416
+ type: "button",
417
+ "data-slider-thumb": "",
418
+ "aria-controls": viewportId,
419
+ "aria-label": `Go to slide ${index + 1}`,
420
+ ...props
421
+ }
422
+ );
423
+ }
424
+ var Slider = Object.assign(SliderRoot, {
425
+ Viewport: SliderViewport,
426
+ Slide: SliderSlide,
427
+ Prev: SliderPrev,
428
+ Next: SliderNext,
429
+ Dots: SliderDots,
430
+ AutoplayToggle: SliderAutoplayToggle,
431
+ Thumbs: SliderThumbs,
432
+ Thumb: SliderThumb
433
+ });
434
+ function keyOf(child, position) {
435
+ return isValidElement(child) && child.key !== null ? child.key : String(position);
436
+ }
437
+ function useMergedRef(own, ref) {
438
+ return useCallback3(
439
+ (element) => {
440
+ own(element);
441
+ if (typeof ref === "function") ref(element);
442
+ else if (ref) ref.current = element;
443
+ },
444
+ [own, ref]
445
+ );
446
+ }
447
+ export {
448
+ Slider,
449
+ autoplay,
450
+ createSlider,
451
+ renderClones,
452
+ slideProgress,
453
+ syncSliders,
454
+ useSlider,
455
+ useSliderContext,
456
+ useSliderState
457
+ };