@carouseldeck/react-18 0.0.0-stage → 1.3.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pedro Sousa
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,35 @@
1
- # Temporary Holding Version
1
+ # @carouseldeck/react-18
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ `@carouseldeck/react` built for React 18. It has the same components, props and
4
+ types, from the same source.
5
+
6
+ On React 18, install it in place of `@carouseldeck/react` and import from
7
+ `@carouseldeck/react-18`. It is published at the same version as
8
+ `@carouseldeck/react`, from 1.2.0.
9
+
10
+ ## How it is built
11
+
12
+ The source in `packages/react/src` is written for React 19, and this package
13
+ does not change it. The React 19 package ships the same code it did before,
14
+ at the same size. This package's build (`vite.config.ts`) adapts the source to
15
+ React 18:
16
+
17
+ - `src/react.ts` stands in for `react`. `use` reads a context with
18
+ `useContext`, a context renders as React 18's provider, and
19
+ `useLayoutEffect` is `useEffect` on the server, where React 18 warns about
20
+ it. In development, it reports a React other than 18.
21
+ - `src/jsx-runtime.ts` stands in for `react/jsx-runtime`. `inert` renders as
22
+ React 18 needs it. The deck's `<style>` elements render just before the
23
+ viewport, one per deck, because React 18 does not hoist them, and its CSS
24
+ is set as HTML, because React 18's server renderer escapes `>`.
25
+ - Every component in the source is wrapped in `forwardRef` (`src/with-ref.ts`),
26
+ so a `ref` reaches its element as it does in React 19.
27
+
28
+ The package has no `react-server` entry: React 18 has no server components.
29
+
30
+ ## Tests
31
+
32
+ `pnpm test:react-18` builds this package, then type-checks a consumer
33
+ (`test/consumer.tsx`) with React 18's types. It then runs the React package's
34
+ tests on React 18 against the build (`vitest.react-18.config.ts` at the repo
35
+ root), with this package's own tests in `test/`.
@@ -0,0 +1,33 @@
1
+ import type { Effect } from './index.js';
2
+ /**
3
+ * Fans the slides along an arc around the focal slide while the viewport
4
+ * scrolls, snaps and drags natively (ADR-0006). Pass it to `Deck.Viewport`'s
5
+ * `effect`:
6
+ *
7
+ * ```tsx
8
+ * import { curve } from '@carouseldeck/react/curve';
9
+ *
10
+ * <Deck.Viewport effect={curve}>…</Deck.Viewport>
11
+ * ```
12
+ *
13
+ * The slides keep their place in the deck, their size, gap and alignment, and
14
+ * snap as they would without the curve, in pages too. Each slide's content,
15
+ * its children, turns about its centre and drops across the axis to sit on a
16
+ * circle under the focal slide, and the slide fades with its distance from
17
+ * it, following its progress. Give each slide one child that fills it, styled
18
+ * as the slide is seen: a slide's own background stays in place, and several
19
+ * children each turn about their own centre. The circle's radius, in slides,
20
+ * is `--deck-curve-radius`, 4 by default and never 0 or less: set it in CSS
21
+ * on the viewport, per breakpoint if needed. A slide a radius away or more has
22
+ * turned a quarter and faded out.
23
+ *
24
+ * Each slide contains its layout, so its turned content is drawn on the arc
25
+ * but adds nothing the viewport can scroll to, and focus never scrolls the
26
+ * viewport after it. The viewport clips across the axis, so the arc never
27
+ * shows a scrollbar: give it padding there to show more of the arc. A
28
+ * vertical deck's arc bends toward the inline end, and a right-to-left deck's
29
+ * mirrors. Under reduced motion the content stays flat
30
+ * and the slides only fade.
31
+ */
32
+ export declare const curve: Effect;
33
+ //# sourceMappingURL=curve.d.ts.map
package/dist/curve.js ADDED
@@ -0,0 +1,12 @@
1
+ "use client";
2
+ //#region ../react/src/curve.ts
3
+ var e = "[data-carouseldeck-effect=curve]>[data-carouseldeck-slide]", t = `${e}>*`, n = "var(--carouseldeck-curve-progress)", r = "var(--carouseldeck-curve-radius)", i = "var(--carouseldeck-curve-x)", a = "var(--carouseldeck-curve-turn)", o = "var(--carouseldeck-curve-drop)", s = "var(--carouseldeck-curve-sign)", c = {
4
+ name: "curve",
5
+ css: `:where(${e}){--carouseldeck-curve-progress:var(--deck-slide-progress,0);--carouseldeck-curve-radius:max(var(--deck-curve-radius,4),0.001);--carouseldeck-curve-x:clamp(-1,${n}/${r},1);--carouseldeck-curve-turn:asin(${i});--carouseldeck-curve-drop:calc(${r}*(1 - sqrt(1 - ${i}*${i})));--carouseldeck-curve-sign:1;opacity:calc(1 - max(${i},-1*${i}))}:where(${e}:dir(rtl)){--carouseldeck-curve-sign:-1}:where(${t}){transform:rotate(90deg) translateX(calc(${o}*100%)) rotate(-90deg) rotate(calc(${s}*${a}))}:where([data-orientation=vertical]${t}){transform:rotate(-90deg) translateY(calc(${s}*${o}*100%)) rotate(90deg) rotate(calc(-1*${s}*${a}))}@media (prefers-reduced-motion:reduce){:where(${t}){transform:none}}`,
6
+ layout: (e) => ({
7
+ viewport: e === "vertical" ? { overflowX: "hidden" } : { overflowY: "hidden" },
8
+ slide: { contain: "layout" }
9
+ })
10
+ };
11
+ //#endregion
12
+ export { c as curve };
package/dist/fade.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ import type { Effect } from './index.js';
2
+ /**
3
+ * Crossfades the slides in place while the viewport scrolls, snaps and drags
4
+ * natively (ADR-0006). Pass it to `Deck.Viewport`'s `effect`:
5
+ *
6
+ * ```tsx
7
+ * import { fade } from '@carouseldeck/react/fade';
8
+ *
9
+ * <Deck.Viewport effect={fade}>…</Deck.Viewport>
10
+ * ```
11
+ *
12
+ * The slides are stacked with `position: sticky` over an empty snap target
13
+ * per slide, which gives the viewport its scroll length and snap points. Each
14
+ * slide's opacity follows its progress, 1 at the focal position and 0 a slide
15
+ * away; under reduced motion it cuts from one slide to the next halfway
16
+ * instead. Only the focal slide can be seen, so every other slide is inert.
17
+ *
18
+ * Each slide fills the viewport and snaps at its start: a fade shows one slide
19
+ * at a time, so slide size, alignment and group snapping (pages) do not apply.
20
+ * A horizontal deck is as tall as its tallest slide; a vertical one needs a
21
+ * height, as any vertical deck does.
22
+ */
23
+ export declare const fade: Effect;
24
+ //# sourceMappingURL=fade.d.ts.map
package/dist/fade.js ADDED
@@ -0,0 +1,32 @@
1
+ "use client";
2
+ //#region ../react/src/stacked.ts
3
+ var e = (e, t) => {
4
+ let n = e === "vertical", r = `repeat(${Math.max(t, 1)}, 100%)`, i = n ? "gridRow" : "gridColumn", a = n ? "gridColumn" : "gridRow";
5
+ return {
6
+ viewport: {
7
+ display: "grid",
8
+ gridTemplateColumns: n ? "100%" : r,
9
+ gridTemplateRows: n ? r : "100%"
10
+ },
11
+ slide: {
12
+ [a]: "1",
13
+ [i]: "1 / -1",
14
+ [n ? "alignSelf" : "justifySelf"]: "start",
15
+ [n ? "height" : "width"]: `calc(100% / ${Math.max(t, 1)})`,
16
+ position: "sticky",
17
+ [n ? "top" : "insetInlineStart"]: 0,
18
+ scrollSnapAlign: "none"
19
+ },
20
+ target: (e) => ({
21
+ [a]: "1",
22
+ [i]: String(e + 1),
23
+ scrollSnapAlign: "start"
24
+ })
25
+ };
26
+ }, t = "[data-carouseldeck-effect=fade]>[data-carouseldeck-slide]", n = (e) => `--carouseldeck-fade-progress:var(--deck-slide-progress,${e})`, r = "var(--carouseldeck-fade-progress)", i = `--carouseldeck-fade-distance:min(max(${r},-1*${r}),1)`, a = {
27
+ name: "fade",
28
+ css: `:where(${t}){${n(1)};${i};opacity:calc(1 - var(--carouseldeck-fade-distance))}:where(${t}[data-focal]){${n(0)}}@media (prefers-reduced-motion:reduce){:where(${t}){opacity:clamp(0,min(0.5 + ${r},0.501 - ${r})*1000,1)}}`,
29
+ layout: e
30
+ };
31
+ //#endregion
32
+ export { a as fade };
@@ -0,0 +1,218 @@
1
+ import { type ComponentProps, type CSSProperties, type Ref } from 'react';
2
+ import { type Orientation } from '@carouseldeck/core';
3
+ export type { Orientation };
4
+ /** What `Deck.Root`'s `handleRef` exposes: moves for event handlers that
5
+ * should not round-trip through state. Each fires `onIndexChange` when the deck
6
+ * settles on a new snap point. */
7
+ export interface RootHandle {
8
+ /** Scrolls to a snap point, clamped to the snap points there are. */
9
+ scrollTo(index: number): void;
10
+ /** Scrolls one snap point on. */
11
+ next(): void;
12
+ /** Scrolls one snap point back. */
13
+ prev(): void;
14
+ }
15
+ interface RootBaseProps extends ComponentProps<'div'> {
16
+ /** The deck's moves; `ref` is the region element, as on every primitive. */
17
+ handleRef?: Ref<RootHandle>;
18
+ /** Whether a mouse can drag the deck, settling on a snap point when it lets
19
+ * go. A drag never clicks what it started on. Touch, pen and trackpad
20
+ * always scroll natively. Defaults to true. */
21
+ drag?: boolean;
22
+ /** Called once each time the focal slide changes, with its slide index,
23
+ * or -1 when no slide snaps: when a scroll settles, never during one, and
24
+ * not for where the deck starts. */
25
+ onFocalChange?: (slide: number) => void;
26
+ /** Whether clicking a slide brings it to the focal position, as near as
27
+ * the scroll range allows; with pages, its page. A click that ends a mouse
28
+ * drag does not, nor does a keyboard click (Enter or Space on a control),
29
+ * nor a programmatic `element.click()` (detail 0): call `scrollTo` on the
30
+ * handle instead. Defaults to false. */
31
+ clickToFocus?: boolean;
32
+ /** The axis the deck scrolls along. A vertical deck needs a height, set on
33
+ * `Deck.Viewport` in CSS. Horizontal follows the writing direction: in a
34
+ * right-to-left document the deck starts at the right and Next moves left.
35
+ * Defaults to horizontal. */
36
+ orientation?: Orientation;
37
+ /** Whether scrolling past the last snap point arrives at the first, and
38
+ * back, with no visible jump: Prev and Next are then never disabled. Each
39
+ * slide's children mount once: `Deck.Viewport` moves the slide nearest the
40
+ * viewport onto empty, inert, `aria-hidden` shells that carry the snap
41
+ * points on each side of it, by `translate` (ADR-0013). A deck whose set
42
+ * of slides is too narrow for a shell to have room beside its own slide
43
+ * falls back to rendering full copies instead (clone and jump, ADR-0006,
44
+ * as built in ADR-0009): a copy renders the slide's children again, so
45
+ * their state is their own and an `id` in a slide repeats; `useSlide`
46
+ * tells them whether they are in one. Indexes, Dots and Counter count the
47
+ * slides' snap points only, never a shell's or a copy's. A deck whose
48
+ * slides all fit does not loop and renders neither. Defaults to false. */
49
+ loop?: boolean;
50
+ /** Moves the deck one snap point on every this many milliseconds, counted
51
+ * from when it comes to rest, and stops at the last, unless the deck
52
+ * loops, or where a step leaves the deck where it was, as when a
53
+ * controlled parent refuses it. A pointer over the deck or a hidden
54
+ * document pauses it; focus entering the deck, other than on
55
+ * `Deck.AutoplayToggle`, or the user moving it stops it until the toggle
56
+ * starts it again, and so does a preference for reduced motion, from the
57
+ * start. Pair it with `Deck.AutoplayToggle` (WCAG 2.2.2). Off by default. */
58
+ autoplay?: number;
59
+ }
60
+ /** Controlled like a React input's `value`: the deck scrolls to `index` when
61
+ * it changes. A scroll that settles elsewhere calls `onIndexChange`, and the
62
+ * deck returns to `index` unless the parent takes the new one. */
63
+ interface ControlledProps {
64
+ /** The snap point to rest at. */
65
+ index: number;
66
+ defaultIndex?: never;
67
+ /** Called once each time the viewport settles on a new snap point other
68
+ * than `index`; never for a move to `index`. Without it, every scroll returns
69
+ * to `index`, and development warns. */
70
+ onIndexChange?: (index: number) => void;
71
+ }
72
+ interface UncontrolledProps {
73
+ index?: never;
74
+ /** The snap point to start at, clamped to the snap points there are.
75
+ * Read once, on mount. */
76
+ defaultIndex?: number;
77
+ /** Called once each time the viewport settles on a new snap point. */
78
+ onIndexChange?: (index: number) => void;
79
+ }
80
+ export type RootProps = RootBaseProps & (ControlledProps | UncontrolledProps);
81
+ /** One deck: a labelled carousel region holding a viewport, its controls and
82
+ * a live region announcing the slide the user moves it to. */
83
+ export declare function Root({ index, defaultIndex, onIndexChange, handleRef, drag, onFocalChange, clickToFocus, orientation, loop, autoplay, children, onFocus, onPointerEnter, onPointerLeave, onPointerDown, onKeyDown, onWheel, ...props }: RootProps): import("react").JSX.Element;
84
+ /** What `useSlide` reports about the slide it is called in. */
85
+ export interface UseSlideResult {
86
+ /** The index of the slide, also in a loop's copy of it. */
87
+ index: number;
88
+ /** Which side of the slides a loop's copy is on; undefined in a slide. */
89
+ copy: 'before' | 'after' | undefined;
90
+ }
91
+ /** The slide a component is rendered in: its index, and whether it is a
92
+ * loop's copy, so stateful content, such as a video player, can render
93
+ * differently in a copy, which is inert and never focal. Each slide mounts
94
+ * once: `copy` is undefined unless the deck's set of slides is too narrow
95
+ * for shells and falls back to rendering copies (ADR-0013). Throws outside
96
+ * a `Deck.Slide`. */
97
+ export declare function useSlide(): UseSlideResult;
98
+ /**
99
+ * A transition built on progress while the viewport keeps scrolling natively
100
+ * (GLOSSARY.md), passed to `Deck.Viewport`'s `effect`. Each effect is its own
101
+ * entry point, such as `@carouseldeck/react/fade`, so a deck that imports none
102
+ * ships none of their code.
103
+ */
104
+ export interface Effect {
105
+ /** Names the effect's stylesheet, so a page holds one copy of it. */
106
+ name: string;
107
+ /** The effect's appearance, read from `--deck-slide-progress` and the
108
+ * slides' data attributes. Zero-specificity `:where()` rules, so consumer
109
+ * CSS overrides any of them (ADR-0003). */
110
+ css: string;
111
+ /** The structural styles the effect needs, set inline as the deck's own
112
+ * are, for a deck of `count` slides: with loop, a small deck's three sets
113
+ * of copies, or, with shells (ADR-0013), all five sets of them instead,
114
+ * each a shell rather than a copy. With `target`, the viewport holds an
115
+ * empty snap target per slide, per copy or per shell, in the order they
116
+ * run, after the slides, styled by it: the slides stack in one place, the
117
+ * engine measures each by its target, and every slide but the focal one
118
+ * is inert, as it cannot be seen. */
119
+ layout?: (orientation: Orientation, count: number) => {
120
+ viewport?: CSSProperties;
121
+ slide?: CSSProperties;
122
+ target?: (index: number) => CSSProperties;
123
+ };
124
+ }
125
+ export interface ViewportProps extends ComponentProps<'div'> {
126
+ /** An effect, imported from its own entry point, such as `fade` from
127
+ * `@carouseldeck/react/fade`. */
128
+ effect?: Effect;
129
+ }
130
+ /** The native scroll container. Its children are the deck's slides; with
131
+ * `loop`, two in-flow spacers and empty shells carrying their snap points on
132
+ * each side of them (ADR-0013), or, where the set of slides is too narrow
133
+ * for a shell to have room beside its own slide, a copy of every slide on
134
+ * each side instead; an `effect` may add a snap target after them for each
135
+ * slide, copy or shell. */
136
+ export declare function Viewport({ style, children, effect, ...props }: ViewportProps): import("react").JSX.Element;
137
+ /** One slide, labelled "n of m". Its size and alignment are consumer CSS.
138
+ * With loop, it mounts once: shells carry its snap points on each side, and
139
+ * `translate` moves it onto whichever is nearest the viewport (ADR-0013); a
140
+ * deck too narrow for shells copies it instead.
141
+ * For CSS to read, it carries `--deck-slide-index`, its index, and
142
+ * `--deck-slide-count`, the number of slides, copies not counted, from the
143
+ * first render; `--deck-slide-progress`, its signed distance from the focal
144
+ * position in slides, from the first render too, server HTML included,
145
+ * counted from the slide the deck starts at; and once mounted, `data-in-view` while any of
146
+ * it is in view. Progress and in-view are kept up to date as the deck scrolls
147
+ * without a React render. Where an effect stacks the slides, as fade does, every slide but the focal
148
+ * one is inert. */
149
+ export declare function Slide({ style, ...props }: ComponentProps<'div'>): import("react").JSX.Element;
150
+ /** What `useDeck` reports about the deck it is called in. */
151
+ export interface UseDeckResult {
152
+ /** The current index: the snap point the deck rests at, a page when it
153
+ * snaps in pages. Until the viewport is measured, the index it starts at,
154
+ * clamped to the slides, as Counter shows it. */
155
+ index: number;
156
+ /** The snap points, so the pages; null until the viewport is measured, as
157
+ * on the server. */
158
+ count: number | null;
159
+ /** Whether `Deck.Root` has `loop`. */
160
+ loop: boolean;
161
+ /** Whether every slide fits, so there is nowhere to go: Prev, Next, Dots,
162
+ * Counter and AutoplayToggle are then absent. False until the viewport is
163
+ * measured. */
164
+ fits: boolean;
165
+ /** Whether `prev()` can move the deck: when `Deck.Prev` is enabled. */
166
+ canPrev: boolean;
167
+ /** Whether `next()` can move the deck: when `Deck.Next` is enabled. */
168
+ canNext: boolean;
169
+ /** Scrolls to a snap point, clamped to the snap points there are. */
170
+ scrollTo(index: number): void;
171
+ /** Scrolls one snap point on. */
172
+ next(): void;
173
+ /** Scrolls one snap point back. */
174
+ prev(): void;
175
+ }
176
+ /** The deck a component is rendered in: its state, as Prev and Next read it,
177
+ * and Dots and Counter once the viewport is measured, and its moves, so a
178
+ * consumer can build those controls from their own components. A move stops
179
+ * autoplay, as a built-in control's does. Like the primitives, it never
180
+ * re-renders while the deck scrolls (ADR-0003): it re-renders whenever
181
+ * `Deck.Root` does, as when the deck settles somewhere new, its snap points
182
+ * change or, with `autoplay`, autoplay starts, stops or pauses. Throws
183
+ * outside a `Deck.Root`. */
184
+ export declare function useDeck(): UseDeckResult;
185
+ /** Moves the deck one snap point back. Disabled at the first unless the deck
186
+ * loops; absent when every slide fits. Marked `data-carouseldeck-prev` for CSS
187
+ * to select. */
188
+ export declare function Prev(props: ComponentProps<'button'>): import("react").JSX.Element | null;
189
+ /** Moves the deck one snap point on. Disabled at the last unless the deck
190
+ * loops; absent when every slide fits. Marked `data-carouseldeck-next` for CSS
191
+ * to select. */
192
+ export declare function Next(props: ComponentProps<'button'>): import("react").JSX.Element | null;
193
+ /** A labelled group of buttons, one per page: one per snap point, so with
194
+ * several slides in a page a dot stands for the page, not a slide (ADR-0004).
195
+ * The current one carries `aria-current`; the group's data attributes are the
196
+ * deck's state, which a consumer's cannot overwrite. Absent when every slide
197
+ * fits. */
198
+ export declare function Dots(props: Omit<ComponentProps<'div'>, 'children'>): import("react").JSX.Element | null;
199
+ /** The current page and the total, as "3 / 10": counts pages, as Dots do.
200
+ * Absent when every slide fits. Not a live region: each slide is already
201
+ * labelled "n of m". Its data attributes are the deck's state, which
202
+ * a consumer's cannot overwrite. */
203
+ export declare function Counter(props: Omit<ComponentProps<'span'>, 'children'>): import("react").JSX.Element | null;
204
+ /**
205
+ * Stops and starts `Deck.Root`'s `autoplay`, so motion can always be stopped
206
+ * (WCAG 2.2.2). Its name is the action it takes, "Stop slide rotation" or
207
+ * "Start slide rotation", rather than `aria-pressed` (APG carousel); it is
208
+ * marked `data-playing` while autoplay is on, paused by a pointer or not.
209
+ * Children replace the visible name: give both states, shown by
210
+ * `data-playing` in CSS. Place it first among the deck's controls, ahead of
211
+ * the slides, so keyboard users reach it before the moving content. Focus on
212
+ * it does not stop autoplay, so it can be pressed. Starting autoplay where it
213
+ * stopped at the last snap point rewinds to the first. A consumer's
214
+ * `onClick` runs first and can cancel the toggle with `preventDefault()`.
215
+ * Absent without `autoplay`, and when every slide fits.
216
+ */
217
+ export declare function AutoplayToggle({ onClick, children, ...props }: ComponentProps<'button'>): import("react").JSX.Element | null;
218
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,535 @@
1
+ "use client";
2
+ import { Children as e, Fragment as t, createContext as n, forwardRef as r, isValidElement as i, useContext as a, useEffect as o, useEffect as s, useImperativeHandle as c, useLayoutEffect as l, useRef as u, useState as d, version as f } from "react";
3
+ import { COPY as p, SHELL as m, SNAP_TARGET as h, SPACER as g, createDeck as _ } from "@carouseldeck/core";
4
+ import { Fragment as v, jsx as y, jsxs as b } from "react/jsx-runtime";
5
+ //#region src/with-ref.ts
6
+ function x(e, t) {
7
+ let n = r((t, n) => e(n === null ? t : {
8
+ ...t,
9
+ ref: n
10
+ }));
11
+ return n.displayName = t, n;
12
+ }
13
+ //#endregion
14
+ //#region src/react.ts
15
+ process.env.NODE_ENV !== "production" && !f.startsWith("18.") && console.error(`@carouseldeck/react-18 is for React 18, and this is React ${f}. Use @carouseldeck/react with React 19.`);
16
+ var S = (e) => n(e).Provider, C = (e) => a(e._context), w = typeof window > "u" ? o : l, T = (e) => typeof e == "object" && !!e && e.type === "style";
17
+ function E(e) {
18
+ return (t, n, r) => {
19
+ if (typeof t != "string") return e(t, n, r);
20
+ let i = n;
21
+ if ("inert" in i) {
22
+ let e = i.inert === "" || !!i.inert;
23
+ i = {
24
+ ...i,
25
+ inert: e ? "" : void 0
26
+ };
27
+ }
28
+ if (t === "style" && "precedence" in i) {
29
+ let { children: e, ...t } = i;
30
+ delete t.href, delete t.precedence;
31
+ let n = String(e).replace(/<\/style/gi, "<\\/style");
32
+ i = {
33
+ ...t,
34
+ dangerouslySetInnerHTML: { __html: n }
35
+ };
36
+ }
37
+ if ("data-carouseldeck-viewport" in i && Array.isArray(i.children)) {
38
+ let e = i.children, n = e.map((e) => T(e) ? e : null), a = e.map((e) => T(e) ? null : e);
39
+ return b(v, { children: [...n, b(t, {
40
+ ...i,
41
+ children: a
42
+ })] }, r);
43
+ }
44
+ return e(t, i, r);
45
+ };
46
+ }
47
+ var D = E(y), ee = E(b), O = S(null);
48
+ //#endregion
49
+ //#region ../react/src/slides-in.ts
50
+ function k(n, r) {
51
+ for (let a of e.toArray(n)) {
52
+ if (!i(a)) continue;
53
+ if (a.type === r) return e.toArray(a.props.children).length;
54
+ if (typeof a.type != "string" && a.type !== t) continue;
55
+ let n = k(a.props.children, r);
56
+ if (n !== null) return n;
57
+ }
58
+ return null;
59
+ }
60
+ //#endregion
61
+ //#region ../react/src/index.tsx
62
+ var A = (e, t) => Math.min(Math.max(e, 0), t - 1), j = -.9985;
63
+ function M(e, t) {
64
+ let n = e;
65
+ for (; n <= j;) n += t;
66
+ for (; n > j + t;) n -= t;
67
+ return n;
68
+ }
69
+ var te = S(null);
70
+ function N(e) {
71
+ let t = C(te);
72
+ if (!t) throw Error(`Deck.${e} must be inside Deck.Root`);
73
+ return t;
74
+ }
75
+ function P({ index: e, defaultIndex: t, onIndexChange: n, handleRef: r, drag: i = !0, onFocalChange: a, clickToFocus: o = !1, orientation: l = "horizontal", loop: f = !1, autoplay: p, children: m, onFocus: h, onPointerEnter: g, onPointerLeave: v, onPointerDown: y, onKeyDown: b, onWheel: x, ...S }) {
76
+ let [T] = d(() => {
77
+ let n = e ?? t ?? 0;
78
+ return Number.isFinite(n) ? n : 0;
79
+ });
80
+ process.env.NODE_ENV !== "production" && ie(e, t, n);
81
+ let [E, j] = d({
82
+ index: T,
83
+ count: null,
84
+ slide: null,
85
+ focal: null,
86
+ copies: !1
87
+ }), M = u(null), N = u(null), P = u(n), F = u(a), I = u(e), L = u(null), R = u(l), [ae, oe] = d(""), [z, B] = d(!0), [se, V] = d(!1), [U, ce] = d(!1), W = u(!1), G = u(!1), K = u(!1), q = u(null);
88
+ w(() => {
89
+ P.current = n, F.current = a, I.current = e;
90
+ }), w(() => {
91
+ let e = M.current;
92
+ if (!e) throw Error("Deck.Root must contain a Deck.Viewport");
93
+ let t, n, r = _(e, {
94
+ index: T,
95
+ orientation: R.current,
96
+ onChange(r) {
97
+ j(r);
98
+ let i = I.current;
99
+ t !== void 0 && r.index !== t && (i === void 0 || r.index !== A(i, r.count)) && (L.current = r.index, P.current?.(r.index)), t !== void 0 && r.index !== t && (G.current = !1, oe(r.slide === -1 || K.current ? "" : `Slide ${r.slide + 1} of ${re(e)}`)), t = r.index, n !== void 0 && r.focal !== n && F.current?.(r.focal), n = r.focal;
100
+ }
101
+ });
102
+ return N.current = r, () => {
103
+ r.destroy(), N.current = null;
104
+ };
105
+ }, [T]), w(() => {
106
+ N.current?.setDrag(i);
107
+ }, [i]), w(() => {
108
+ N.current?.setClickToFocus(o);
109
+ }, [o]), w(() => {
110
+ R.current = l, N.current?.setOrientation(l);
111
+ }, [l]);
112
+ let J = u(e);
113
+ w(() => {
114
+ let t = N.current, n = e !== J.current;
115
+ n && (K.current = !1, q.current = null), J.current = e;
116
+ let r = L.current;
117
+ if (L.current = null, e !== void 0 && E.count !== null && t) {
118
+ let i = A(e, E.count), a = t.target();
119
+ i !== (a ?? E.index) && (i !== r || a === null) && t.scrollTo(e, n ? "direct" : "short");
120
+ }
121
+ }, [
122
+ e,
123
+ E.index,
124
+ E.count
125
+ ]);
126
+ let Y = p !== void 0;
127
+ w(() => {
128
+ if (!Y) return;
129
+ let e = matchMedia("(prefers-reduced-motion: reduce)"), t = () => {
130
+ e.matches && B(!1);
131
+ };
132
+ return t(), e.addEventListener("change", t), () => e.removeEventListener("change", t);
133
+ }, [Y]), s(() => {
134
+ if (!Y) return;
135
+ let e = () => ce(document.visibilityState === "hidden");
136
+ return e(), document.addEventListener("visibilitychange", e), () => document.removeEventListener("visibilitychange", e);
137
+ }, [Y]);
138
+ let X = Y && z && !se && !U && E.count !== null && !H(E.count);
139
+ s(() => {
140
+ if (!X) return;
141
+ let e, t = () => {
142
+ G.current = !0, q.current = null, B(!1);
143
+ }, n = () => {
144
+ let r = N.current;
145
+ if (r) {
146
+ if (r.target() !== null || W.current || document.visibilityState === "hidden") {
147
+ e = setTimeout(n, p);
148
+ return;
149
+ }
150
+ if (q.current === E.index) {
151
+ t();
152
+ return;
153
+ }
154
+ q.current = E.index, K.current = !0, r.next(), r.target() === null && t();
155
+ }
156
+ };
157
+ return e = setTimeout(n, p), () => clearTimeout(e);
158
+ }, [
159
+ X,
160
+ p,
161
+ E.index
162
+ ]);
163
+ let le = () => {
164
+ if (z) {
165
+ B(!1);
166
+ return;
167
+ }
168
+ q.current = null, G.current && (G.current = !1, K.current = !0, N.current?.scrollTo(0)), B(!0);
169
+ }, Z = ({ target: e }) => {
170
+ let t = e;
171
+ t.closest("[data-carouseldeck-autoplay-toggle]") || (M.current?.contains(t) ? $() : Q());
172
+ }, Q = () => (K.current = !1, q.current = null, N.current), $ = () => (Y && B(!1), Q());
173
+ c(r, () => ({
174
+ scrollTo: (e) => Q()?.scrollTo(e),
175
+ next: () => Q()?.next(),
176
+ prev: () => Q()?.prev()
177
+ }), []);
178
+ let de = C(O), fe = E.count === null ? k(m, ue) ?? de : null;
179
+ return /* @__PURE__ */ D(O, {
180
+ value: null,
181
+ children: /* @__PURE__ */ D(te, {
182
+ value: {
183
+ ...E,
184
+ slides: fe,
185
+ initialIndex: T,
186
+ orientation: l,
187
+ loop: f,
188
+ viewportRef: M,
189
+ engineRef: N,
190
+ hasAutoplay: Y,
191
+ playing: z,
192
+ togglePlaying: le,
193
+ userMove: $
194
+ },
195
+ children: /* @__PURE__ */ ee("div", {
196
+ role: "region",
197
+ "aria-roledescription": "carousel",
198
+ "data-index": E.index,
199
+ ...S,
200
+ onFocus: (e) => {
201
+ h?.(e);
202
+ let t = e.target;
203
+ for (; t && t.parentElement !== M.current;) t = t.parentElement;
204
+ t && !t.contains(e.relatedTarget) && N.current?.target() != null && Q(), Y && !e.target.closest("[data-carouseldeck-autoplay-toggle]") && B(!1);
205
+ },
206
+ onPointerEnter: (e) => {
207
+ g?.(e), W.current = !0, Y && V(!0);
208
+ },
209
+ onPointerLeave: (e) => {
210
+ v?.(e), W.current = !1, Y && V(!1);
211
+ },
212
+ onPointerDown: (e) => {
213
+ y?.(e), Z(e);
214
+ },
215
+ onKeyDown: (e) => {
216
+ b?.(e), Z(e);
217
+ },
218
+ onWheel: (e) => {
219
+ x?.(e), Z(e);
220
+ },
221
+ children: [m, /* @__PURE__ */ D("div", {
222
+ "aria-live": X ? "off" : "polite",
223
+ "aria-atomic": "true",
224
+ "data-carouseldeck-live": "",
225
+ style: ne,
226
+ children: ae
227
+ })]
228
+ })
229
+ })
230
+ });
231
+ }
232
+ var ne = {
233
+ position: "absolute",
234
+ width: 1,
235
+ height: 1,
236
+ margin: -1,
237
+ padding: 0,
238
+ overflow: "hidden",
239
+ clip: "rect(0 0 0 0)",
240
+ whiteSpace: "nowrap",
241
+ border: 0
242
+ }, re = (e) => [...e.children].filter((e) => !e.hasAttribute(h) && !e.hasAttribute(p) && !e.hasAttribute(m) && !e.hasAttribute(g)).length;
243
+ function ie(e, t, n) {
244
+ let r = e !== void 0, [i] = d(r), a = r && t !== void 0, o = r !== i, c = r && n === void 0, l = u(/* @__PURE__ */ new Set());
245
+ s(() => {
246
+ let e = (e, t) => {
247
+ l.current.has(e) || (l.current.add(e), console.error(t));
248
+ };
249
+ a && e("both", "Deck.Root takes either index or defaultIndex, not both. Pass index with onIndexChange for a controlled deck, or defaultIndex for an uncontrolled one."), o && e("switched", `Deck.Root is changing ${i ? "a controlled deck to be uncontrolled" : "an uncontrolled deck to be controlled"}. A deck should not switch between the two: choose index or defaultIndex for its whole life.`), c && e("readOnly", "Deck.Root was given index without onIndexChange, so a scroll will return the deck to index. Pass onIndexChange to follow the scroll, or defaultIndex for an uncontrolled deck.");
250
+ }, [
251
+ a,
252
+ o,
253
+ c,
254
+ i
255
+ ]);
256
+ }
257
+ var F = S(null);
258
+ function I() {
259
+ let e = C(F);
260
+ if (!e) throw Error("Deck.useSlide must be inside Deck.Slide");
261
+ return {
262
+ index: e.index,
263
+ copy: e.copy
264
+ };
265
+ }
266
+ var L = {
267
+ horizontal: {
268
+ display: "flex",
269
+ overflowX: "auto",
270
+ scrollSnapType: "x mandatory"
271
+ },
272
+ vertical: {
273
+ display: "flex",
274
+ flexDirection: "column",
275
+ overflowY: "auto",
276
+ scrollSnapType: "y mandatory"
277
+ }
278
+ };
279
+ function R({ style: n, children: r, effect: a, ...o }) {
280
+ let { viewportRef: s, engineRef: c, orientation: l, loop: u, count: d, initialIndex: f, copies: p } = N("Viewport"), m = l === "vertical", h = e.toArray(r), g = u && !H(d), _ = g && !p, v = g && p, y = a?.layout?.(l, h.length)?.target !== void 0, b = h.length * (v ? 3 : _ && y ? 5 : 1), x = a?.layout?.(l, b), S = x?.target, C = A(f, h.length), T = _ && !y, E = (e) => T ? Math.max(-2, Math.min(2, Math.floor((C - e) / h.length + .5))) : 0, O = _ && y, k = h.map((e) => i(e) ? e.key : "").join();
281
+ w(() => {
282
+ c.current?.refresh();
283
+ }, [
284
+ c,
285
+ h.length,
286
+ v,
287
+ _,
288
+ y,
289
+ k
290
+ ]);
291
+ let j = (e) => h.map((t, n) => {
292
+ let r = E(n);
293
+ return /* @__PURE__ */ D(F, {
294
+ value: {
295
+ index: n,
296
+ count: h.length,
297
+ stacked: y,
298
+ style: e === "before" ? {
299
+ ...x?.slide,
300
+ order: -1
301
+ } : x?.slide,
302
+ progress: O ? M(n - C, h.length) : n - C + (e === "after" ? h.length : e ? -h.length : r * h.length),
303
+ placed: !O && r !== 0 ? r * h.length : void 0,
304
+ copy: e
305
+ },
306
+ children: t
307
+ }, i(t) ? t.key : n);
308
+ });
309
+ return /* @__PURE__ */ ee("div", {
310
+ ref: s,
311
+ tabIndex: 0,
312
+ "data-carouseldeck-viewport": "",
313
+ "data-orientation": l,
314
+ "data-carouseldeck-effect": a?.name,
315
+ "data-carouseldeck-loop": g ? _ ? "shells" : "copies" : void 0,
316
+ style: {
317
+ ...L[l],
318
+ ...x?.viewport,
319
+ ...n
320
+ },
321
+ ...o,
322
+ children: [
323
+ /* @__PURE__ */ D("style", {
324
+ href: "carouseldeck-slide",
325
+ precedence: "carouseldeck",
326
+ children: B
327
+ }),
328
+ a && /* @__PURE__ */ D("style", {
329
+ href: `carouseldeck-${a.name}`,
330
+ precedence: "carouseldeck",
331
+ children: a.css
332
+ }),
333
+ j(),
334
+ v && /* @__PURE__ */ D(t, { children: j("after") }, "after"),
335
+ v && /* @__PURE__ */ D(t, { children: j("before") }, "before"),
336
+ _ && !y && /* @__PURE__ */ D("div", {
337
+ "data-carouseldeck-spacer": "before",
338
+ "aria-hidden": "true",
339
+ style: z(!0, h.length)
340
+ }),
341
+ _ && !y && /* @__PURE__ */ D("div", {
342
+ "data-carouseldeck-spacer": "after",
343
+ "aria-hidden": "true",
344
+ style: z(!1, h.length)
345
+ }),
346
+ _ && ae.flatMap((e) => Array.from({ length: h.length }, (t, n) => /* @__PURE__ */ D("div", {
347
+ "data-carouseldeck-shell": e,
348
+ "data-carouseldeck-snap-target": y ? "" : void 0,
349
+ "aria-hidden": "true",
350
+ inert: !0,
351
+ style: y ? S?.((e + 2) * h.length + n) : {
352
+ ...oe,
353
+ [m ? "insetBlockStart" : "insetInlineStart"]: `${((e + 2) * h.length + n) * 100}%`
354
+ }
355
+ }, `shell:${e}:${n}`))),
356
+ S && !O && Array.from({ length: b }, (e, t) => /* @__PURE__ */ D("div", {
357
+ "aria-hidden": "true",
358
+ "data-carouseldeck-snap-target": "",
359
+ style: S(t)
360
+ }, `target-${t}`))
361
+ ]
362
+ });
363
+ }
364
+ var ae = [
365
+ -2,
366
+ -1,
367
+ 0,
368
+ 1,
369
+ 2
370
+ ], oe = {
371
+ position: "absolute",
372
+ margin: 0,
373
+ padding: 0,
374
+ border: 0,
375
+ scale: "none",
376
+ rotate: "none",
377
+ translate: "none",
378
+ opacity: 0,
379
+ pointerEvents: "none",
380
+ scrollSnapAlign: "none"
381
+ }, z = (e, t) => ({
382
+ ...e && { order: -1 },
383
+ flexGrow: 0,
384
+ flexShrink: 0,
385
+ margin: 0,
386
+ padding: 0,
387
+ border: 0,
388
+ scale: "none",
389
+ rotate: "none",
390
+ translate: "none",
391
+ opacity: 0,
392
+ pointerEvents: "none",
393
+ scrollSnapAlign: "none",
394
+ ...e ? { "--carouseldeck-spacer-estimate": `${2 * t * 100}%` } : { flexBasis: `${2 * t * 100}%` }
395
+ }), B = ":where([data-carouseldeck-slide]){width:100%;scroll-snap-align:start}:where([data-orientation=vertical]>[data-carouseldeck-slide]){height:100%}:where([data-carouseldeck-viewport]){scrollbar-width:none}:where([data-carouseldeck-loop=shells]){position:relative}:where([data-carouseldeck-loop=shells]){overflow-anchor:none}:where([data-carouseldeck-placed]){translate:calc(var(--carouseldeck-placed) * 100%) 0}:where([data-carouseldeck-placed]:dir(rtl)){translate:calc(var(--carouseldeck-placed) * -100%) 0}:where([data-orientation=vertical]>[data-carouseldeck-placed]){translate:0 calc(var(--carouseldeck-placed) * 100%)}@supports (scroll-initial-target: nearest) {:where([data-carouseldeck-spacer=\"before\"]){flex-basis:var(--carouseldeck-spacer-estimate)}}@supports not (scrollbar-width:none){:where([data-carouseldeck-viewport])::-webkit-scrollbar{display:none}}", se = { scrollInitialTarget: "nearest" };
396
+ function V({ style: e, ...t }) {
397
+ let n = N("Slide"), r = C(F);
398
+ if (!r) throw Error("Deck.Slide must be inside Deck.Viewport");
399
+ let { index: i, count: a, stacked: o, copy: s } = r, c = !s && i === A(n.initialIndex, a), l = !s && (n.slide === null ? c : i === n.slide), u = !s && (n.focal === null ? c : i === n.focal), d = s !== void 0 || o && !u, { viewportRef: f } = n;
400
+ return w(() => {
401
+ if (!d) return;
402
+ let e = f.current, t = document.activeElement?.closest("[data-carouseldeck-slide][inert]");
403
+ e && t?.parentElement === e && e.focus({ preventScroll: !0 });
404
+ }, [d, f]), /* @__PURE__ */ D("div", {
405
+ role: "group",
406
+ "aria-roledescription": "slide",
407
+ "aria-label": `${i + 1} of ${a}`,
408
+ "data-carouseldeck-slide": "",
409
+ "data-carouseldeck-copy": s,
410
+ "data-carouseldeck-placed": r.placed === void 0 ? void 0 : "",
411
+ "aria-hidden": s ? !0 : void 0,
412
+ "data-current": l ? "" : void 0,
413
+ "data-focal": u ? "" : void 0,
414
+ inert: d,
415
+ style: {
416
+ flexShrink: 0,
417
+ "--deck-slide-index": i,
418
+ "--deck-slide-count": a,
419
+ "--deck-slide-progress": r.progress,
420
+ ...r.placed !== void 0 && { "--carouseldeck-placed": r.placed },
421
+ ...c && se,
422
+ ...r.style,
423
+ ...e
424
+ },
425
+ ...t
426
+ });
427
+ }
428
+ var H = (e) => e !== null && e <= 1;
429
+ function U(e) {
430
+ let t = N(e), { count: n, slides: r, loop: i, userMove: a } = t, o = n === null && r ? A(t.initialIndex, r) : t.index, s = H(n);
431
+ return {
432
+ index: o,
433
+ count: n,
434
+ loop: i,
435
+ fits: s,
436
+ canPrev: !s && (i || o !== 0),
437
+ canNext: !s && (i || n === null || o < n - 1),
438
+ scrollTo: (e) => a()?.scrollTo(e),
439
+ next: () => a()?.next(),
440
+ prev: () => a()?.prev()
441
+ };
442
+ }
443
+ function ce() {
444
+ return U("useDeck");
445
+ }
446
+ function W({ atEnd: e, step: t, label: n, onClick: r, disabled: i, children: a, ...o }) {
447
+ return /* @__PURE__ */ D("button", {
448
+ type: "button",
449
+ ...o,
450
+ disabled: e || i,
451
+ "data-disabled": e || i ? "" : void 0,
452
+ onClick: (e) => {
453
+ r?.(e), e.defaultPrevented || t();
454
+ },
455
+ children: a ?? n
456
+ });
457
+ }
458
+ function G(e) {
459
+ let { fits: t, canPrev: n, prev: r } = U("Prev");
460
+ return t ? null : /* @__PURE__ */ D(Q, {
461
+ ...e,
462
+ "data-carouseldeck-prev": "",
463
+ atEnd: !n,
464
+ step: r,
465
+ label: "Previous"
466
+ });
467
+ }
468
+ function K(e) {
469
+ let { fits: t, canNext: n, next: r } = U("Next");
470
+ return t ? null : /* @__PURE__ */ D(Q, {
471
+ ...e,
472
+ "data-carouseldeck-next": "",
473
+ atEnd: !n,
474
+ step: r,
475
+ label: "Next"
476
+ });
477
+ }
478
+ function q(e) {
479
+ let { index: t, count: n, slides: r, initialIndex: i, userMove: a } = N(e);
480
+ return n !== null || r === null ? {
481
+ index: t,
482
+ count: n,
483
+ userMove: a
484
+ } : {
485
+ index: A(i, r),
486
+ count: r,
487
+ userMove: a
488
+ };
489
+ }
490
+ function J(e) {
491
+ let { index: t, count: n, userMove: r } = q("Dots");
492
+ return H(n) ? null : /* @__PURE__ */ D("div", {
493
+ role: "group",
494
+ "aria-label": "Choose page",
495
+ ...e,
496
+ "data-carouseldeck-dots": "",
497
+ "data-index": n === null ? void 0 : t,
498
+ "data-count": n ?? void 0,
499
+ children: Array.from({ length: n ?? 0 }, (e, n) => /* @__PURE__ */ D("button", {
500
+ type: "button",
501
+ "aria-label": `Go to page ${n + 1}`,
502
+ "aria-current": n === t ? "true" : void 0,
503
+ "data-index": n,
504
+ onClick: () => r()?.scrollTo(n)
505
+ }, n))
506
+ });
507
+ }
508
+ function Y(e) {
509
+ let { index: t, count: n } = q("Counter");
510
+ if (H(n)) return null;
511
+ let r = n !== null;
512
+ return /* @__PURE__ */ D("span", {
513
+ ...e,
514
+ "data-carouseldeck-counter": "",
515
+ "data-index": r ? t : void 0,
516
+ "data-count": r ? n : void 0,
517
+ children: r && `${t + 1} / ${n}`
518
+ });
519
+ }
520
+ function X({ onClick: e, children: t, ...n }) {
521
+ let { hasAutoplay: r, playing: i, togglePlaying: a, count: o } = N("AutoplayToggle");
522
+ return !r || H(o) ? null : /* @__PURE__ */ D("button", {
523
+ type: "button",
524
+ ...n,
525
+ "data-carouseldeck-autoplay-toggle": "",
526
+ "data-playing": i ? "" : void 0,
527
+ onClick: (t) => {
528
+ e?.(t), t.defaultPrevented || a();
529
+ },
530
+ children: t ?? (i ? "Stop slide rotation" : "Start slide rotation")
531
+ });
532
+ }
533
+ var le = x(P, "Root"), ue = x(R, "Viewport"), Z = x(V, "Slide"), Q = x(W, "StepButton"), $ = x(G, "Prev"), de = x(K, "Next"), fe = x(J, "Dots"), pe = x(Y, "Counter"), me = x(X, "AutoplayToggle");
534
+ //#endregion
535
+ export { me as AutoplayToggle, pe as Counter, fe as Dots, de as Next, $ as Prev, le as Root, Z as Slide, ue as Viewport, ce as useDeck, I as useSlide };
package/dist/theme.css ADDED
@@ -0,0 +1,184 @@
1
+ /*
2
+ * @carouseldeck/react/theme.css: an optional appearance for a deck's controls,
3
+ * Prev, Next, Dots, Counter and AutoplayToggle. Slides and their geometry
4
+ * stay your CSS (ADR-0003), and a deck works without this file.
5
+ *
6
+ * Every rule is wrapped in :where(), so any selector of yours beats it. Every
7
+ * themed value is a custom property below; set one on the deck or any
8
+ * ancestor to override its default.
9
+ *
10
+ * --deck-control-color: #1a1a1a
11
+ * Text of Prev, Next, the counter and the stopped autoplay toggle.
12
+ * --deck-control-background: #ffffff
13
+ * Background of Prev, Next and the autoplay toggle.
14
+ * --deck-control-hover-background: #f0f0f0
15
+ * Their background under a pointer.
16
+ * --deck-control-border: 1px solid #767676
17
+ * Their border.
18
+ * --deck-control-radius: 0.375rem
19
+ * Their corner radius.
20
+ * --deck-control-padding: 0.375rem 0.75rem
21
+ * Their padding.
22
+ * --deck-control-font-size: 0.875rem
23
+ * Their font size, and the counter's.
24
+ * --deck-control-disabled-opacity: 0.4
25
+ * Opacity of Prev or Next while disabled, at either end.
26
+ * --deck-control-active-background: var(--deck-accent, #0b5cd5)
27
+ * Background of the autoplay toggle while autoplay plays, paused by a
28
+ * pointer or not; stopped, it looks like Prev and Next.
29
+ * --deck-control-active-color: #ffffff
30
+ * Its text while autoplay plays.
31
+ * --deck-dot-color: #767676
32
+ * A dot that is not the current page.
33
+ * --deck-dot-hover-color: #1a1a1a
34
+ * A dot that is not the current page, under a pointer.
35
+ * --deck-dot-size: 0.625rem
36
+ * The diameter of the dot drawn.
37
+ * --deck-dot-current-width: 1.25rem
38
+ * The width of the current page's dot, wider than the others so it
39
+ * stands out by shape as well as colour.
40
+ * --deck-dot-radius: 9999px
41
+ * A dot's corner radius; past half its side it rounds a dot to a circle
42
+ * and the current one to a pill.
43
+ * --deck-dot-target-size: 1.5rem
44
+ * The square a dot answers clicks in, the dot centred in it.
45
+ * --deck-dot-gap: 0.25rem
46
+ * Space between dot targets.
47
+ * --deck-accent: #0b5cd5
48
+ * The current page's dot, the focus ring and the playing toggle.
49
+ * --deck-focus-width: 2px
50
+ * Width of the focus ring a keyboard user sees.
51
+ * --deck-focus-offset: 2px
52
+ * Space between a control and its focus ring.
53
+ * --deck-transition-duration: 150ms
54
+ * How long hover and state changes fade; none under reduced motion.
55
+ */
56
+
57
+ :where(
58
+ [data-carouseldeck-prev],
59
+ [data-carouseldeck-next],
60
+ [data-carouseldeck-autoplay-toggle]
61
+ ) {
62
+ padding: var(--deck-control-padding, 0.375rem 0.75rem);
63
+ border: var(--deck-control-border, 1px solid #767676);
64
+ border-radius: var(--deck-control-radius, 0.375rem);
65
+ background-color: var(--deck-control-background, #ffffff);
66
+ color: var(--deck-control-color, #1a1a1a);
67
+ font: inherit;
68
+ font-size: var(--deck-control-font-size, 0.875rem);
69
+ }
70
+
71
+ :where(
72
+ [data-carouseldeck-prev],
73
+ [data-carouseldeck-next],
74
+ [data-carouseldeck-autoplay-toggle]
75
+ ):where(:hover:not(:disabled)) {
76
+ background-color: var(--deck-control-hover-background, #f0f0f0);
77
+ }
78
+
79
+ :where([data-carouseldeck-autoplay-toggle][data-playing]) {
80
+ background-color: var(
81
+ --deck-control-active-background,
82
+ var(--deck-accent, #0b5cd5)
83
+ );
84
+ color: var(--deck-control-active-color, #ffffff);
85
+ }
86
+
87
+ :where([data-carouseldeck-prev], [data-carouseldeck-next]):where(:disabled) {
88
+ opacity: var(--deck-control-disabled-opacity, 0.4);
89
+ }
90
+
91
+ :where([data-carouseldeck-dots]) {
92
+ display: flex;
93
+ gap: var(--deck-dot-gap, 0.25rem);
94
+ }
95
+
96
+ :where([data-carouseldeck-dots] > button) {
97
+ box-sizing: border-box;
98
+ inline-size: var(--deck-dot-target-size, 1.5rem);
99
+ block-size: var(--deck-dot-target-size, 1.5rem);
100
+ padding: calc(
101
+ (var(--deck-dot-target-size, 1.5rem) - var(--deck-dot-size, 0.625rem)) / 2
102
+ );
103
+ border: 0;
104
+ border-radius: var(--deck-dot-radius, 9999px);
105
+ background-color: var(--deck-dot-color, #767676);
106
+ background-clip: content-box;
107
+ }
108
+
109
+ :where([data-carouseldeck-dots] > button[aria-current='true']) {
110
+ inline-size: calc(
111
+ var(--deck-dot-target-size, 1.5rem) - var(--deck-dot-size, 0.625rem) +
112
+ var(--deck-dot-current-width, 1.25rem)
113
+ );
114
+ background-color: var(--deck-accent, #0b5cd5);
115
+ }
116
+
117
+ :where([data-carouseldeck-dots] > button:not([aria-current='true']):hover) {
118
+ background-color: var(--deck-dot-hover-color, #1a1a1a);
119
+ }
120
+
121
+ /* Forced colours paint every background the page colour, dots included:
122
+ * draw them in system colours instead, the current and a hovered one
123
+ * highlighted. The playing toggle is highlighted too, so it still differs
124
+ * from the stopped one. */
125
+ @media (forced-colors: active) {
126
+ :where([data-carouseldeck-dots] > button) {
127
+ forced-color-adjust: none;
128
+ background-color: CanvasText;
129
+ outline-color: CanvasText;
130
+ }
131
+
132
+ :where(
133
+ [data-carouseldeck-dots] > button[aria-current='true'],
134
+ [data-carouseldeck-dots] > button:hover
135
+ ) {
136
+ background-color: Highlight;
137
+ }
138
+
139
+ :where([data-carouseldeck-autoplay-toggle][data-playing]) {
140
+ forced-color-adjust: none;
141
+ border-color: ButtonText;
142
+ background-color: Highlight;
143
+ color: HighlightText;
144
+ outline-color: CanvasText;
145
+ }
146
+ }
147
+
148
+ :where([data-carouseldeck-counter]) {
149
+ color: var(--deck-control-color, #1a1a1a);
150
+ font-size: var(--deck-control-font-size, 0.875rem);
151
+ }
152
+
153
+ :where(
154
+ [data-carouseldeck-prev],
155
+ [data-carouseldeck-next],
156
+ [data-carouseldeck-autoplay-toggle],
157
+ [data-carouseldeck-dots] > button
158
+ ) {
159
+ transition:
160
+ background-color var(--deck-transition-duration, 150ms),
161
+ color var(--deck-transition-duration, 150ms),
162
+ opacity var(--deck-transition-duration, 150ms);
163
+ }
164
+
165
+ :where(
166
+ [data-carouseldeck-prev],
167
+ [data-carouseldeck-next],
168
+ [data-carouseldeck-autoplay-toggle],
169
+ [data-carouseldeck-dots] > button
170
+ ):where(:focus-visible) {
171
+ outline: var(--deck-focus-width, 2px) solid var(--deck-accent, #0b5cd5);
172
+ outline-offset: var(--deck-focus-offset, 2px);
173
+ }
174
+
175
+ @media (prefers-reduced-motion: reduce) {
176
+ :where(
177
+ [data-carouseldeck-prev],
178
+ [data-carouseldeck-next],
179
+ [data-carouseldeck-autoplay-toggle],
180
+ [data-carouseldeck-dots] > button
181
+ ) {
182
+ transition: none;
183
+ }
184
+ }
package/package.json CHANGED
@@ -1,6 +1,58 @@
1
1
  {
2
2
  "name": "@carouseldeck/react-18",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "1.3.0",
4
+ "description": "@carouseldeck/react built for React 18: headless, composable carousel primitives on native scroll snap.",
5
+ "license": "MIT",
6
+ "homepage": "https://carousel.deck.cool",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/pedrosousa13/deck-cool.git",
10
+ "directory": "decks/carouseldeck/packages/react-18"
11
+ },
12
+ "type": "module",
13
+ "sideEffects": [
14
+ "*.css"
15
+ ],
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "default": "./dist/index.js"
20
+ },
21
+ "./fade": {
22
+ "types": "./dist/fade.d.ts",
23
+ "default": "./dist/fade.js"
24
+ },
25
+ "./curve": {
26
+ "types": "./dist/curve.d.ts",
27
+ "default": "./dist/curve.js"
28
+ },
29
+ "./theme.css": {
30
+ "default": "./dist/theme.css"
31
+ }
32
+ },
33
+ "files": [
34
+ "dist"
35
+ ],
36
+ "publishConfig": {
37
+ "access": "public",
38
+ "provenance": true
39
+ },
40
+ "dependencies": {
41
+ "@carouseldeck/core": "1.3.0"
42
+ },
43
+ "peerDependencies": {
44
+ "react": ">=18 <19",
45
+ "react-dom": ">=18 <19"
46
+ },
47
+ "devDependencies": {
48
+ "@types/react": "18.3.31",
49
+ "@types/react-dom": "18.3.7",
50
+ "react": "18.3.1",
51
+ "react-dom": "18.3.1",
52
+ "vite": "8.3.1",
53
+ "@carouseldeck/react": "1.3.0"
54
+ },
55
+ "scripts": {
56
+ "build": "vite build"
57
+ }
6
58
  }