@reelkit/react
Advanced tools
| import { Signal } from '@reelkit/core'; | ||
| /** | ||
| * Values exposed by a parent {@link Reel} component to its children | ||
| * via React context. Use {@link useReelContext} to access. | ||
| */ | ||
| export interface ReelContextValue { | ||
| /** The active slide index signal. */ | ||
| index: Signal<number>; | ||
| /** The total slide count signal. */ | ||
| count: Signal<number>; | ||
| /** Navigate to a specific slide. */ | ||
| goTo: (index: number, animate?: boolean) => Promise<void>; | ||
| } | ||
| /** @internal */ | ||
| export declare const ReelContext: import('react').Context<ReelContextValue | null>; | ||
| /** | ||
| * Returns the {@link ReelContextValue} from the nearest parent | ||
| * {@link Reel}, or `null` if none exists. | ||
| */ | ||
| export declare const useReelContext: () => ReelContextValue | null; |
+27
-2
| /** | ||
| * @module @reelkit/react | ||
| * | ||
| * React bindings for the Reelkit slider library. | ||
| * React bindings for the ReelKit slider library. | ||
| * | ||
@@ -17,7 +17,32 @@ * The main component is {@link Reel} — a virtualized, gesture-driven | ||
| * dependency. | ||
| * | ||
| * @example | ||
| * ```tsx | ||
| * import { useState } from 'react'; | ||
| * import { Reel, ReelIndicator } from '@reelkit/react'; | ||
| * | ||
| * function App() { | ||
| * return ( | ||
| * <Reel | ||
| * count={100} | ||
| * style={{ width: '100%', height: '100dvh' }} | ||
| * direction="vertical" | ||
| * enableWheel | ||
| * itemBuilder={(index, _inRange, size) => ( | ||
| * <div style={{ width: size[0], height: size[1] }}> | ||
| * Slide {index + 1} | ||
| * </div> | ||
| * )} | ||
| * > | ||
| * <ReelIndicator /> | ||
| * </Reel> | ||
| * ); | ||
| * } | ||
| * ``` | ||
| */ | ||
| export { createSignal, createComputed, reaction, createDeferred, first, last, isEmpty, generate, abs, isNegative, clamp, lerp, extractRange, observeDomEvent, type Signal, type ComputedSignal, type Subscribable, type Listener, type Dispose, type Deferred, } from '@reelkit/core'; | ||
| export { createSignal, createComputed, reaction, createDeferred, first, last, isEmpty, generate, abs, isNegative, clamp, lerp, extractRange, observeDomEvent, noop, captureFrame, createSharedVideo, createGestureController, type Signal, type ComputedSignal, type Subscribable, type Listener, type Dispose, type Deferred, type SharedVideoConfig, type SharedVideoInstance, type GestureController, type GestureControllerEvents, } from '@reelkit/core'; | ||
| export { Reel, defaultRangeExtractor, createDefaultKeyExtractorForLoop, type ReelProps, type ReelApi, } from './lib/Reel'; | ||
| export { ReelIndicator, type ReelIndicatorProps } from './lib/ReelIndicator'; | ||
| export { Observe, AnimatedObserve, type AnimatedValue } from './lib/Observe'; | ||
| export { ReelContext, useReelContext, type ReelContextValue, } from './lib/ReelContext'; | ||
| export { useBodyLock } from './lib/useBodyLock'; |
+272
-247
@@ -1,39 +0,39 @@ | ||
| import { reaction as U, animate as q, defaultRangeExtractor as F, first as T, last as P, createSliderController as I, clamp as X } from "@reelkit/core"; | ||
| import { abs as fe, clamp as he, createComputed as me, createDeferred as ge, createSignal as ye, defaultRangeExtractor as ve, extractRange as be, first as Se, generate as we, isEmpty as Ce, isNegative as Re, last as ze, lerp as pe, observeDomEvent as Ee, reaction as De } from "@reelkit/core"; | ||
| import { jsx as z, jsxs as Y } from "react/jsx-runtime"; | ||
| import { useReducer as V, useEffect as w, useRef as k, memo as O, useState as L, useLayoutEffect as _, useMemo as G } from "react"; | ||
| import { flushSync as K } from "react-dom"; | ||
| const J = ({ | ||
| import { reaction as K, animate as J, defaultRangeExtractor as Q, first as O, last as P, createSliderController as Z, createSignal as H, clamp as ee } from "@reelkit/core"; | ||
| import { abs as Ce, captureFrame as Se, clamp as Re, createComputed as xe, createDeferred as Ee, createGestureController as ze, createSharedVideo as De, createSignal as ke, defaultRangeExtractor as Me, extractRange as $e, first as Te, generate as We, isEmpty as Ae, isNegative as Be, last as Ne, lerp as je, noop as Ie, observeDomEvent as Ve, reaction as pe } from "@reelkit/core"; | ||
| import { jsx as h, jsxs as te } from "react/jsx-runtime"; | ||
| import { useReducer as U, useEffect as w, useRef as M, createContext as ne, useContext as G, memo as p, useState as V, useLayoutEffect as oe } from "react"; | ||
| import { flushSync as q } from "react-dom"; | ||
| const X = ({ | ||
| signals: t, | ||
| children: e | ||
| }) => { | ||
| const n = V(() => ({}), {})[1]; | ||
| return w(() => U(() => t, n), []), e(); | ||
| }, Q = ({ | ||
| const n = U(() => ({}), {})[1]; | ||
| return w(() => K(() => t, n), []), e(); | ||
| }, re = ({ | ||
| signal: t, | ||
| children: e | ||
| }) => { | ||
| const n = k(t.value.value), s = V(() => ({}), {})[1], r = k(!1), f = k(null), h = k(void 0); | ||
| return w(() => (r.current = !0, () => { | ||
| r.current = !1; | ||
| const n = M(t.value.value), r = U(() => ({}), {})[1], o = M(!1), u = M(null), d = M(void 0); | ||
| return w(() => (o.current = !0, () => { | ||
| o.current = !1; | ||
| }), []), w( | ||
| () => U( | ||
| () => K( | ||
| () => [t], | ||
| () => { | ||
| const { value: g, duration: C, done: b } = t.value; | ||
| if (f.current) { | ||
| f.current(), f.current = null; | ||
| const c = h.current; | ||
| c && (h.current = void 0, setTimeout(() => c(), 0)); | ||
| const { value: y, duration: R, done: C } = t.value; | ||
| if (u.current) { | ||
| u.current(), u.current = null; | ||
| const i = d.current; | ||
| i && (d.current = void 0, setTimeout(() => i(), 0)); | ||
| } | ||
| if (C > 0) { | ||
| h.current = b, f.current = q({ | ||
| if (R > 0) { | ||
| d.current = C, u.current = J({ | ||
| from: n.current, | ||
| to: g, | ||
| duration: C, | ||
| onUpdate: (c) => { | ||
| n.current = c, r.current && K(s); | ||
| to: y, | ||
| duration: R, | ||
| onUpdate: (i) => { | ||
| n.current = i, o.current && q(r); | ||
| }, | ||
| onComplete: () => { | ||
| f.current = null, h.current = void 0, setTimeout(() => b?.(), 0); | ||
| u.current = null, d.current = void 0, setTimeout(() => C?.(), 0); | ||
| } | ||
@@ -43,4 +43,4 @@ }); | ||
| } | ||
| h.current = void 0, n.current = g, requestAnimationFrame(() => { | ||
| r.current && K(s); | ||
| d.current = void 0, n.current = y, requestAnimationFrame(() => { | ||
| o.current && q(r); | ||
| }); | ||
@@ -51,117 +51,119 @@ } | ||
| ), e(n.current); | ||
| }, Z = (t) => t.toString(), se = (t, e) => (n, s) => { | ||
| const r = `${e ?? ""}${n}`; | ||
| return t === 2 && [0, 1].includes(n) && s === 0 ? `${r}_cloned` : r; | ||
| }, H = ({ | ||
| rangeExtractor: t = F, | ||
| }, L = ne(null), he = () => G(L), ce = (t) => t.toString(), me = (t, e) => (n, r) => { | ||
| const o = `${e ?? ""}${n}`; | ||
| return t === 2 && [0, 1].includes(n) && r === 0 ? `${o}_cloned` : o; | ||
| }, ie = ({ | ||
| rangeExtractor: t = Q, | ||
| initialIndex: e = 0, | ||
| direction: n = "vertical", | ||
| swipeDistanceFactor: s = 0.12, | ||
| loop: r = !1, | ||
| keyExtractor: f = Z, | ||
| useNavKeys: h = !0, | ||
| transitionDuration: g = 300, | ||
| enableWheel: C = !1, | ||
| wheelDebounceMs: b = 200, | ||
| ...c | ||
| swipeDistanceFactor: r = 0.12, | ||
| loop: o = !1, | ||
| keyExtractor: u = ce, | ||
| useNavKeys: d = !0, | ||
| transitionDuration: y = 300, | ||
| enableWheel: R = !1, | ||
| wheelDebounceMs: C = 200, | ||
| ...i | ||
| }) => { | ||
| const { size: N, apiRef: y } = c, m = N === void 0, [M, S] = L([0, 0]), d = m ? M : N, v = k(null); | ||
| v.current = { | ||
| ...c, | ||
| size: d, | ||
| const { size: B, apiRef: b } = i, m = B === void 0, [$, S] = V([0, 0]), f = m ? $ : B, g = M(null); | ||
| g.current = { | ||
| ...i, | ||
| size: f, | ||
| rangeExtractor: t, | ||
| initialIndex: e, | ||
| direction: n, | ||
| swipeDistanceFactor: s, | ||
| loop: r, | ||
| keyExtractor: f, | ||
| useNavKeys: h, | ||
| transitionDuration: g, | ||
| enableWheel: C, | ||
| wheelDebounceMs: b | ||
| swipeDistanceFactor: r, | ||
| loop: o, | ||
| keyExtractor: u, | ||
| useNavKeys: d, | ||
| transitionDuration: y, | ||
| enableWheel: R, | ||
| wheelDebounceMs: C | ||
| }; | ||
| const p = n === "horizontal", E = p ? T(d) : P(d), $ = k(null), [i, j] = L( | ||
| () => [ | ||
| I( | ||
| { | ||
| count: c.count, | ||
| initialIndex: e, | ||
| direction: n, | ||
| loop: r, | ||
| transitionDuration: g, | ||
| swipeDistanceFactor: s, | ||
| rangeExtractor: t, | ||
| enableWheel: C, | ||
| wheelDebounceMs: b | ||
| const z = n === "horizontal", D = z ? O(f) : P(f), k = M(null), [s, N, j] = V(() => { | ||
| const a = Z( | ||
| { | ||
| count: i.count, | ||
| initialIndex: e, | ||
| direction: n, | ||
| loop: o, | ||
| transitionDuration: y, | ||
| swipeDistanceFactor: r, | ||
| rangeExtractor: t, | ||
| enableWheel: R, | ||
| wheelDebounceMs: C | ||
| }, | ||
| { | ||
| onBeforeChange: (l, v, E) => { | ||
| g.current.beforeChange?.(l, v, E); | ||
| }, | ||
| { | ||
| onBeforeChange: (o, u, a) => { | ||
| v.current.beforeChange?.(o, u, a); | ||
| }, | ||
| onAfterChange: (o, u) => { | ||
| v.current.afterChange?.(o, u); | ||
| }, | ||
| onDragStart: (o) => { | ||
| v.current.onSlideDragStart?.(o); | ||
| }, | ||
| onDragEnd: (o) => { | ||
| v.current.onSlideDragEnd?.(o); | ||
| }, | ||
| onDragCanceled: (o) => { | ||
| v.current.onSlideDragCanceled?.(o); | ||
| } | ||
| onAfterChange: (l, v) => { | ||
| g.current.afterChange?.(l, v); | ||
| }, | ||
| onDragStart: (l) => { | ||
| g.current.onSlideDragStart?.(l); | ||
| }, | ||
| onDragEnd: (l) => { | ||
| g.current.onSlideDragEnd?.(l); | ||
| }, | ||
| onDragCanceled: (l) => { | ||
| g.current.onSlideDragCanceled?.(l); | ||
| } | ||
| ), | ||
| (o, u) => { | ||
| const { keyExtractor: a, itemBuilder: D, size: W } = v.current; | ||
| return /* @__PURE__ */ z("div", { "data-index": o, children: D(o, u, W) }, a(o, u)); | ||
| } | ||
| ] | ||
| )[0]; | ||
| ), x = { | ||
| index: a.state.index, | ||
| count: H(i.count), | ||
| goTo: a.goTo | ||
| }; | ||
| return [a, (l, v) => { | ||
| const { keyExtractor: E, itemBuilder: Y, size: _ } = g.current; | ||
| return /* @__PURE__ */ h("div", { "data-index": l, children: Y(l, v, _) }, E(l, v)); | ||
| }, x]; | ||
| })[0]; | ||
| w(() => { | ||
| i.updateConfig({ | ||
| count: c.count, | ||
| j.count.value = i.count, s.updateConfig({ | ||
| count: i.count, | ||
| direction: n, | ||
| loop: r, | ||
| transitionDuration: g, | ||
| swipeDistanceFactor: s, | ||
| loop: o, | ||
| transitionDuration: y, | ||
| swipeDistanceFactor: r, | ||
| rangeExtractor: t | ||
| }); | ||
| }, [ | ||
| c.count, | ||
| i.count, | ||
| n, | ||
| o, | ||
| y, | ||
| r, | ||
| g, | ||
| s, | ||
| t | ||
| ]), w(() => { | ||
| i.setPrimarySize(E); | ||
| }, [E]), w(() => { | ||
| h ? i.observe() : i.unobserve(); | ||
| }, [h]), w(() => { | ||
| if ($.current && (i.attach($.current), i.observe()), y != null) { | ||
| const o = { | ||
| next: () => i.next(), | ||
| prev: () => i.prev(), | ||
| goTo: (u, a) => i.goTo(u, a), | ||
| adjust: () => i.adjust(), | ||
| observe: () => i.observe(), | ||
| unobserve: () => i.unobserve() | ||
| s.setPrimarySize(D); | ||
| }, [D]), w(() => { | ||
| d ? s.observe() : s.unobserve(); | ||
| }, [d]), w(() => { | ||
| if (k.current && (s.attach(k.current), s.observe()), b != null) { | ||
| const a = { | ||
| next: () => s.next(), | ||
| prev: () => s.prev(), | ||
| goTo: (x, A) => s.goTo(x, A), | ||
| adjust: () => s.adjust(), | ||
| observe: () => s.observe(), | ||
| unobserve: () => s.unobserve() | ||
| }; | ||
| typeof y == "function" ? y(o) : y.current = o; | ||
| typeof b == "function" ? b(a) : b.current = a; | ||
| } | ||
| return () => { | ||
| i.detach(); | ||
| s.detach(); | ||
| }; | ||
| }, []), _(() => { | ||
| if (!m || !$.current) return; | ||
| const o = $.current, u = () => { | ||
| const D = o.clientWidth, W = o.clientHeight; | ||
| D > 0 && W > 0 && S( | ||
| (B) => B[0] === D && B[1] === W ? B : [D, W] | ||
| }, []), oe(() => { | ||
| if (!m || !k.current) return; | ||
| const a = k.current, x = () => { | ||
| const l = a.clientWidth, v = a.clientHeight; | ||
| l > 0 && v > 0 && S( | ||
| (E) => E[0] === l && E[1] === v ? E : [l, v] | ||
| ); | ||
| }, a = new ResizeObserver(u); | ||
| return a.observe(o), () => a.disconnect(); | ||
| }, A = new ResizeObserver(x); | ||
| return A.observe(a), () => A.disconnect(); | ||
| }, [m]); | ||
| const R = { | ||
| const I = { | ||
| userSelect: "none", | ||
@@ -171,20 +173,20 @@ WebkitUserSelect: "none", | ||
| overflow: "hidden", | ||
| ...m ? {} : { width: T(d), height: P(d) }, | ||
| ...c.style | ||
| }, { axisValue: A, indexes: x } = i.state, l = !m || E > 0; | ||
| return /* @__PURE__ */ Y("div", { ref: $, className: c.className, style: R, children: [ | ||
| l && /* @__PURE__ */ z(J, { signals: [x], children: () => /* @__PURE__ */ z( | ||
| ee, | ||
| ...m ? {} : { width: O(f), height: P(f) }, | ||
| ...i.style | ||
| }, { axisValue: c, indexes: T } = s.state, W = !m || D > 0; | ||
| return /* @__PURE__ */ h(L.Provider, { value: j, children: /* @__PURE__ */ te("div", { ref: k, className: i.className, style: I, children: [ | ||
| W && /* @__PURE__ */ h(X, { signals: [T], children: () => /* @__PURE__ */ h( | ||
| se, | ||
| { | ||
| primarySize: E, | ||
| isHorizontal: p, | ||
| axisValue: A, | ||
| length: x.value.length, | ||
| children: x.value.map(j) | ||
| primarySize: D, | ||
| isHorizontal: z, | ||
| axisValue: c, | ||
| length: T.value.length, | ||
| children: T.value.map(N) | ||
| } | ||
| ) }), | ||
| c.children | ||
| ] }); | ||
| }, ce = O( | ||
| H, | ||
| i.children | ||
| ] }) }); | ||
| }, ge = p( | ||
| ie, | ||
| (t, e) => ( | ||
@@ -195,3 +197,3 @@ // useEffect deps (controller config sync) | ||
| ) | ||
| ), ee = O( | ||
| ), se = p( | ||
| ({ | ||
@@ -201,5 +203,5 @@ children: t, | ||
| primarySize: n, | ||
| axisValue: s, | ||
| length: r | ||
| }) => /* @__PURE__ */ z(Q, { signal: s, children: (f) => /* @__PURE__ */ z( | ||
| axisValue: r, | ||
| length: o | ||
| }) => /* @__PURE__ */ h(re, { signal: r, children: (u) => /* @__PURE__ */ h( | ||
| "div", | ||
@@ -212,5 +214,5 @@ { | ||
| display: "flex", | ||
| transform: `translate${e ? "X" : "Y"}(${f}px)`, | ||
| transform: `translate${e ? "X" : "Y"}(${u}px)`, | ||
| flexDirection: e ? "row" : "column", | ||
| [e ? "width" : "height"]: r * n, | ||
| [e ? "width" : "height"]: o * n, | ||
| [e ? "height" : "width"]: "100%" | ||
@@ -221,108 +223,125 @@ }, | ||
| ) }) | ||
| ), te = (t) => { | ||
| ), F = (t) => { | ||
| const { | ||
| count: e, | ||
| active: n, | ||
| direction: s = "vertical", | ||
| visible: r = 5, | ||
| radius: f = 3, | ||
| gap: h = 4, | ||
| activeColor: g = "#fff", | ||
| inactiveColor: C = "rgba(255, 255, 255, 0.5)", | ||
| edgeScale: b = 0.5, | ||
| className: c, | ||
| style: N, | ||
| onDotClick: y | ||
| } = t, m = s === "vertical", M = f * 2, S = M + h, [d, v] = L(() => e <= r ? 0 : X(n - Math.floor(r / 2), 0, e - r)); | ||
| direction: r = "vertical", | ||
| visible: o = 5, | ||
| radius: u = 3, | ||
| gap: d = 4, | ||
| activeColor: y = "#fff", | ||
| inactiveColor: R = "rgba(255, 255, 255, 0.5)", | ||
| edgeScale: C = 0.5, | ||
| className: i, | ||
| style: B, | ||
| onDotClick: b | ||
| } = t, m = r === "vertical", $ = u * 2, S = $ + d, [f, g] = V(() => e <= o ? 0 : ee(n - Math.floor(o / 2), 0, e - o)); | ||
| w(() => { | ||
| if (e <= r) { | ||
| v(0); | ||
| if (e <= o) { | ||
| g(0); | ||
| return; | ||
| } | ||
| v((R) => n < R ? Math.max(0, n) : n >= R + r ? Math.min(e - r, n - r + 1) : R); | ||
| }, [n, e, r]); | ||
| const p = Math.min(d + r, e), E = d > 0; | ||
| let i = Math.min(r, e) * S; | ||
| e > r && (i += S * 2); | ||
| const j = G(() => { | ||
| const R = [], A = Math.max(0, d - 1), x = Math.min(e, p + 1); | ||
| for (let l = A; l < x; l++) { | ||
| const o = l === n; | ||
| let u = 1; | ||
| (l < d || l >= p) && (u = b); | ||
| let a; | ||
| l < d ? a = 0 : l >= p ? a = r + 1 : a = l - d + 1, !E && a > 0 && (a -= 1); | ||
| const D = a * S; | ||
| R.push( | ||
| /* @__PURE__ */ z( | ||
| "span", | ||
| { | ||
| "data-reel-indicator": l, | ||
| style: { | ||
| position: "absolute", | ||
| [m ? "top" : "left"]: D, | ||
| [m ? "left" : "top"]: 0, | ||
| width: S, | ||
| height: S, | ||
| display: "flex", | ||
| justifyContent: "center", | ||
| alignItems: "center", | ||
| transition: "top 0.2s ease, left 0.2s ease" | ||
| }, | ||
| onClick: y ? () => y(l) : void 0, | ||
| "data-testid": `indicator-dot-${l}`, | ||
| children: /* @__PURE__ */ z( | ||
| "span", | ||
| { | ||
| style: { | ||
| width: M, | ||
| height: M, | ||
| borderRadius: "50%", | ||
| backgroundColor: o ? g : C, | ||
| transition: "transform 0.2s ease, background-color 0.2s ease", | ||
| transform: `scale(${u})`, | ||
| cursor: y ? "pointer" : "default" | ||
| } | ||
| g((c) => n < c ? Math.max(0, n) : n >= c + o ? Math.min(e - o, n - o + 1) : c); | ||
| }, [n, e, o]); | ||
| const z = Math.min(f + o, e), D = f > 0; | ||
| let s = Math.min(o, e) * S; | ||
| e > o && (s += S * 2); | ||
| const N = [], j = Math.max(0, f - 1), I = Math.min(e, z + 1); | ||
| for (let c = j; c < I; c++) { | ||
| const T = c === n; | ||
| let W = 1; | ||
| (c < f || c >= z) && (W = C); | ||
| let a; | ||
| c < f ? a = 0 : c >= z ? a = o + 1 : a = c - f + 1, !D && a > 0 && (a -= 1); | ||
| const x = a * S; | ||
| N.push( | ||
| /* @__PURE__ */ h( | ||
| "span", | ||
| { | ||
| "data-reel-indicator": c, | ||
| style: { | ||
| position: "absolute", | ||
| [m ? "top" : "left"]: x, | ||
| [m ? "left" : "top"]: 0, | ||
| width: S, | ||
| height: S, | ||
| display: "flex", | ||
| justifyContent: "center", | ||
| alignItems: "center", | ||
| transition: "top 0.2s ease, left 0.2s ease" | ||
| }, | ||
| onClick: b ? () => b(c) : void 0, | ||
| "data-testid": `indicator-dot-${c}`, | ||
| children: /* @__PURE__ */ h( | ||
| "span", | ||
| { | ||
| style: { | ||
| width: $, | ||
| height: $, | ||
| borderRadius: "50%", | ||
| backgroundColor: T ? y : R, | ||
| transition: "transform 0.2s ease, background-color 0.2s ease", | ||
| transform: `scale(${W})`, | ||
| cursor: b ? "pointer" : "default" | ||
| } | ||
| ) | ||
| }, | ||
| l | ||
| ) | ||
| ); | ||
| } | ||
| return R; | ||
| }, [ | ||
| d, | ||
| p, | ||
| E, | ||
| r, | ||
| e, | ||
| n, | ||
| M, | ||
| S, | ||
| b, | ||
| g, | ||
| C, | ||
| m, | ||
| y | ||
| ]); | ||
| return /* @__PURE__ */ z( | ||
| } | ||
| ) | ||
| }, | ||
| c | ||
| ) | ||
| ); | ||
| } | ||
| return /* @__PURE__ */ h( | ||
| "div", | ||
| { | ||
| className: c, | ||
| className: i, | ||
| style: { | ||
| position: "relative", | ||
| overflow: "hidden", | ||
| [m ? "height" : "width"]: i, | ||
| [m ? "height" : "width"]: s, | ||
| [m ? "width" : "height"]: S, | ||
| ...N | ||
| ...B | ||
| }, | ||
| children: j | ||
| children: N | ||
| } | ||
| ); | ||
| }, ae = O(te), le = (t) => { | ||
| }, ae = (t) => { | ||
| const e = G(L), n = t.active !== void 0, r = t.count !== void 0; | ||
| if (!n && !e) | ||
| throw new Error( | ||
| 'ReelIndicator: "active" prop is required when rendered outside a <Reel> component.' | ||
| ); | ||
| if (!r && !e) | ||
| throw new Error( | ||
| 'ReelIndicator: "count" prop is required when rendered outside a <Reel> component.' | ||
| ); | ||
| if (n && r) | ||
| return /* @__PURE__ */ h( | ||
| F, | ||
| { | ||
| ...t, | ||
| active: t.active, | ||
| count: t.count | ||
| } | ||
| ); | ||
| const o = [ | ||
| !n && e.index, | ||
| !r && e.count | ||
| ].filter(Boolean), u = t.onDotClick ?? ((d) => { | ||
| e.goTo(d, !0); | ||
| }); | ||
| return /* @__PURE__ */ h(X, { signals: o, children: () => /* @__PURE__ */ h( | ||
| F, | ||
| { | ||
| ...t, | ||
| active: t.active ?? e.index.value, | ||
| count: t.count ?? e.count.value, | ||
| onDotClick: u | ||
| } | ||
| ) }); | ||
| }, ve = p(ae), ye = (t) => { | ||
| w(() => { | ||
| if (!t) return; | ||
| const e = document.body.style.overflow, n = document.body.style.paddingRight, s = window.innerWidth - document.documentElement.clientWidth; | ||
| return document.body.style.overflow = "hidden", s > 0 && (document.body.style.paddingRight = `${s}px`), () => { | ||
| const e = document.body.style.overflow, n = document.body.style.paddingRight, r = window.innerWidth - document.documentElement.clientWidth; | ||
| return document.body.style.overflow = "hidden", r > 0 && (document.body.style.paddingRight = `${r}px`), () => { | ||
| document.body.style.overflow = e, document.body.style.paddingRight = n; | ||
@@ -333,23 +352,29 @@ }; | ||
| export { | ||
| Q as AnimatedObserve, | ||
| J as Observe, | ||
| ce as Reel, | ||
| ae as ReelIndicator, | ||
| fe as abs, | ||
| he as clamp, | ||
| me as createComputed, | ||
| se as createDefaultKeyExtractorForLoop, | ||
| ge as createDeferred, | ||
| ye as createSignal, | ||
| ve as defaultRangeExtractor, | ||
| be as extractRange, | ||
| Se as first, | ||
| we as generate, | ||
| Ce as isEmpty, | ||
| Re as isNegative, | ||
| ze as last, | ||
| pe as lerp, | ||
| Ee as observeDomEvent, | ||
| De as reaction, | ||
| le as useBodyLock | ||
| re as AnimatedObserve, | ||
| X as Observe, | ||
| ge as Reel, | ||
| L as ReelContext, | ||
| ve as ReelIndicator, | ||
| Ce as abs, | ||
| Se as captureFrame, | ||
| Re as clamp, | ||
| xe as createComputed, | ||
| me as createDefaultKeyExtractorForLoop, | ||
| Ee as createDeferred, | ||
| ze as createGestureController, | ||
| De as createSharedVideo, | ||
| ke as createSignal, | ||
| Me as defaultRangeExtractor, | ||
| $e as extractRange, | ||
| Te as first, | ||
| We as generate, | ||
| Ae as isEmpty, | ||
| Be as isNegative, | ||
| Ne as last, | ||
| je as lerp, | ||
| Ie as noop, | ||
| Ve as observeDomEvent, | ||
| pe as reaction, | ||
| ye as useBodyLock, | ||
| he as useReelContext | ||
| }; |
@@ -8,7 +8,14 @@ import { CSSProperties } from 'react'; | ||
| export interface ReelIndicatorProps { | ||
| /** Total number of items in the slider. */ | ||
| count: number; | ||
| /** Index of the currently active item. */ | ||
| active: number; | ||
| /** | ||
| * Total number of items in the slider. | ||
| * Auto-connected from parent Reel when omitted. | ||
| */ | ||
| count?: number; | ||
| /** | ||
| * Index of the currently active item. | ||
| * Auto-connected from parent Reel when omitted. | ||
| * When provided, takes precedence over the context value. | ||
| */ | ||
| active?: number; | ||
| /** | ||
| * Axis along which dots are arranged. | ||
@@ -59,7 +66,2 @@ * @default 'vertical' | ||
| } | ||
| /** | ||
| * Instagram-style scrolling dot indicator. Shows a sliding window of | ||
| * normal-sized dots with smaller edge dots indicating overflow. | ||
| * The window slides smoothly as the active index changes. | ||
| */ | ||
| export declare const ReelIndicator: import('react').NamedExoticComponent<ReelIndicatorProps>; |
+2
-2
| { | ||
| "name": "@reelkit/react", | ||
| "version": "0.1.3", | ||
| "version": "0.2.0", | ||
| "type": "module", | ||
@@ -55,3 +55,3 @@ "sideEffects": false, | ||
| "dependencies": { | ||
| "@reelkit/core": "^0.1.2" | ||
| "@reelkit/core": ">=0.1.2" | ||
| }, | ||
@@ -58,0 +58,0 @@ "peerDependencies": { |
+11
-12
@@ -5,7 +5,7 @@ # @reelkit/react | ||
| <a href="https://www.npmjs.com/package/@reelkit/react"><img src="https://img.shields.io/npm/v/@reelkit/react?color=6366f1&label=npm" alt="npm" /></a> | ||
| <img src="https://img.shields.io/badge/gzip-2.6%20kB-6366f1" alt="Bundle size" /> | ||
| <img src="https://img.shields.io/badge/gzip-2.9%20kB-6366f1" alt="Bundle size" /> | ||
| <a href="https://github.com/KonstantinKai/reelkit"><img src="https://img.shields.io/github/stars/KonstantinKai/reelkit?style=social" alt="Star on GitHub" /></a> | ||
| </p> | ||
| React components and hooks for building TikTok/Instagram Reels-style sliders. Virtualized rendering, touch gestures, keyboard/wheel navigation — all in ~2.6 kB gzip. | ||
| React bindings for `@reelkit/core`. Drop in a `<Reel>` component, give it a slide count and a render function — it handles virtualization, gestures, and keyboard/wheel input. ~2.9 kB gzip. | ||
@@ -44,10 +44,9 @@ ## Installation | ||
| - **`<Reel>`** — virtualized slider component (only 3 slides in DOM) | ||
| - **`<ReelIndicator>`** — Instagram-style position dots | ||
| - **Auto-size** — measures container via ResizeObserver, no explicit size props needed | ||
| - **Touch gestures** — swipe with momentum and snap | ||
| - **Keyboard & wheel** — arrow keys and scroll navigation | ||
| - **Loop mode** — infinite circular scrolling | ||
| - **SSR ready** — works with Next.js, Remix, and any SSR setup | ||
| - **TypeScript** — full type safety | ||
| - `<Reel>` — virtualized slider, keeps only 3 slides in the DOM | ||
| - `<ReelIndicator>` — dot indicators that auto-connect to the parent `<Reel>` via context | ||
| - Measures its own size via ResizeObserver — no width/height props needed | ||
| - Swipe with momentum and snap, keyboard arrows, mouse wheel | ||
| - Loop mode for infinite circular scrolling | ||
| - SSR compatible (Next.js, Remix, etc.) | ||
| - Typed with TypeScript, no `@types` package needed | ||
@@ -86,7 +85,7 @@ ## API | ||
| Full API reference, interactive demos, and guides at **[reelkit.dev](https://reelkit.dev)**. | ||
| API reference, demos, and guides at **[reelkit.dev](https://reelkit.dev)**. | ||
| ## Support | ||
| If you find ReelKit useful, give it a star on GitHub — it helps others discover the project and keeps development going. | ||
| If ReelKit saved you some time, a star on GitHub would mean a lot — it's a small thing, but it really helps the project get noticed. | ||
@@ -93,0 +92,0 @@ [](https://github.com/KonstantinKai/reelkit) |
30579
9.03%10
11.11%714
11.56%94
-1.05%+ Added
- Removed
Updated