Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@reelkit/react

Package Overview
Dependencies
Maintainers
1
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@reelkit/react - npm Package Compare versions

Comparing version
0.2.1
to
0.3.0
+25
dist/lib/SoundState.d.ts
import { ReactNode, FC } from 'react';
import { SoundController } from '@reelkit/core';
/** @internal */
export declare const SoundContext: import('react').Context<SoundController | null>;
interface SoundProviderProps {
children: ReactNode;
}
/**
* Context provider for {@link SoundController}.
*
* Wraps overlay content in reel-player, stories-player, and lightbox.
* Creates a single {@link SoundController} instance with `muted: true`
* and `disabled: false` as initial values.
*/
export declare const SoundProvider: FC<SoundProviderProps>;
/**
* Hook to access the current {@link SoundController} from context.
*
* Must be called inside a {@link SoundProvider} (automatically provided
* by overlay components). Useful in custom controls built via render props.
*
* @throws Error if called outside of a `SoundProvider`.
*/
export declare const useSoundState: () => SoundController;
export {};
import { ReactNode, FC } from 'react';
/** Swipe direction for the {@link SwipeToClose} gesture. */
export type SwipeToCloseDirection = 'up' | 'down';
/** Props for the {@link SwipeToClose} component. */
export interface SwipeToCloseProps {
/** Swipe direction that triggers the close gesture. */
direction: SwipeToCloseDirection;
/** When `true`, gesture handling is active. */
enabled?: boolean;
/** Content to wrap. */
children: ReactNode;
/**
* Fraction of the viewport height the user must swipe to trigger close.
* @default 0.2
*/
threshold?: number;
/** Optional CSS class forwarded to the outer `<div>`. */
className?: string;
/** Callback fired when the swipe exceeds the dismiss threshold. */
onClose: () => void;
}
/**
* Wraps its children in a touch-aware container that can be swiped
* to dismiss. Supports both upward (lightbox) and downward (stories)
* swipe directions.
*/
export declare const SwipeToClose: FC<SwipeToCloseProps>;
import { MutableRefObject } from 'react';
import { fullscreenSignal } from '@reelkit/core';
/**
* Options for the {@link useFullscreen} hook.
*
* @typeParam E - The HTML element type that will be toggled into fullscreen mode.
*/
export interface UseFullscreenProps<E extends HTMLElement> {
/** Ref to the DOM element that should enter fullscreen. */
ref: MutableRefObject<E | null>;
}
/**
* Tuple returned by {@link useFullscreen}.
*
* - `[0]` — The fullscreen signal. Read `.value` inside `Observe` for reactive updates.
* - `[1]` — Function to request fullscreen on the referenced element.
* - `[2]` — Function to exit fullscreen.
* - `[3]` — Toggle function that requests or exits fullscreen based on current state.
*/
export type UseFullscreenResult = [
fullscreen: typeof fullscreenSignal,
requestFullscreen: () => void,
exitFullscreen: () => void,
toggleFullscreen: () => void
];
/**
* React hook for managing the Fullscreen API with cross-browser support.
*
* Uses the core `fullscreenSignal` — a lazy singleton that subscribes to
* `fullscreenchange` events only when observed. No `useState` needed;
* read `fullscreen.value` inside an `Observe` component for reactive updates.
*
* Exits fullscreen automatically on unmount.
*/
export declare const useFullscreen: <E extends HTMLElement>(props: UseFullscreenProps<E>) => UseFullscreenResult;
+4
-1

@@ -42,3 +42,3 @@ /**

*/
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 { createSignal, createComputed, reaction, createDeferred, first, last, generate, abs, isNegative, clamp, lerp, extractRange, observeDomEvent, createDisposableList, type DisposableList, noop, captureFrame, createSharedVideo, createGestureController, type Signal, type ComputedSignal, type Subscribable, type Listener, type Dispose, type Deferred, type SharedVideoConfig, type SharedVideoInstance, type GestureController, type GestureControllerEvents, type GestureCommonEvent, type GestureEvent, observeMediaLoading, type MediaLoadingCallbacks, createSoundController, syncMutedToVideo, type SoundController, createContentLoadingController, type ContentLoadingController, createContentPreloader, type ContentPreloader, type ContentPreloaderConfig, getSlideProgress, slideTransition, flipTransition, cubeTransition, fadeTransition, zoomTransition, type TransitionTransformFn, type SlideTransformStyle, fullscreenSignal, requestFullscreen, exitFullscreen, createBodyLock, type BodyLock, } from '@reelkit/core';
export { Reel, defaultRangeExtractor, createDefaultKeyExtractorForLoop, type ReelProps, type ReelApi, } from './lib/Reel';

@@ -48,2 +48,5 @@ export { ReelIndicator, type ReelIndicatorProps } from './lib/ReelIndicator';

export { ReelContext, useReelContext, type ReelContextValue, } from './lib/ReelContext';
export { SwipeToClose, type SwipeToCloseProps, type SwipeToCloseDirection, } from './lib/SwipeToClose';
export { SoundProvider, useSoundState } from './lib/SoundState';
export { useBodyLock } from './lib/useBodyLock';
export { useFullscreen, type UseFullscreenProps, type UseFullscreenResult, } from './lib/useFullscreen';
+439
-273

@@ -1,39 +0,39 @@

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
import { reaction as X, animate as se, defaultRangeExtractor as ie, first as W, last as Y, createSliderController as ce, createSignal as O, slideTransition as ae, clamp as le, createGestureController as ue, abs as _, createSoundController as de, createBodyLock as fe, fullscreenSignal as F, noop as he, exitFullscreen as j, requestFullscreen as me } from "@reelkit/core";
import { abs as Fe, captureFrame as Ne, clamp as Ie, createBodyLock as Oe, createComputed as qe, createContentLoadingController as je, createContentPreloader as Ke, createDeferred as He, createDisposableList as Ue, createGestureController as Ge, createSharedVideo as We, createSignal as Ye, createSoundController as _e, cubeTransition as Je, defaultRangeExtractor as Qe, exitFullscreen as Xe, extractRange as Ze, fadeTransition as et, first as tt, flipTransition as nt, fullscreenSignal as rt, generate as ot, getSlideProgress as st, isNegative as it, last as ct, lerp as at, noop as lt, observeDomEvent as ut, observeMediaLoading as dt, reaction as ft, requestFullscreen as ht, slideTransition as mt, syncMutedToVideo as gt, zoomTransition as vt } from "@reelkit/core";
import { jsx as m, jsxs as ge } from "react/jsx-runtime";
import { useReducer as Z, useEffect as z, useRef as k, createContext as ee, useContext as K, memo as H, useState as $, useLayoutEffect as ve } from "react";
import { flushSync as J } from "react-dom";
const U = ({
signals: e,
children: t
}) => {
const n = U(() => ({}), {})[1];
return w(() => K(() => t, n), []), e();
}, re = ({
signal: t,
children: e
const n = Z(() => ({}), {})[1];
return z(() => X(() => e, n), []), t();
}, ye = ({
signal: e,
children: t
}) => {
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(
() => K(
() => [t],
const n = k(e.value.value), s = Z(() => ({}), {})[1], r = k(!1), i = k(null), a = k(void 0);
return z(() => (r.current = !0, () => {
r.current = !1;
}), []), z(
() => X(
() => [e],
() => {
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));
const { value: g, duration: v, done: S } = e.value;
if (i.current) {
i.current(), i.current = null;
const y = a.current;
y && (a.current = void 0, setTimeout(() => y(), 0));
}
if (R > 0) {
d.current = C, u.current = J({
if (v > 0) {
a.current = S, i.current = se({
from: n.current,
to: y,
duration: R,
onUpdate: (i) => {
n.current = i, o.current && q(r);
to: g,
duration: v,
onUpdate: (y) => {
n.current = y, r.current && J(s);
},
onComplete: () => {
u.current = null, d.current = void 0, setTimeout(() => C?.(), 0);
i.current = null, a.current = void 0, setTimeout(() => S?.(), 0);
}

@@ -43,4 +43,4 @@ });

}
d.current = void 0, n.current = y, requestAnimationFrame(() => {
o.current && q(r);
a.current = void 0, n.current = g, requestAnimationFrame(() => {
r.current && J(s);
});

@@ -50,120 +50,137 @@ }

[]
), e(n.current);
}, 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,
), t(n.current);
}, G = ee(null), pe = () => K(G), Se = (e) => e.toString(), xe = (e, t) => (n, s) => {
const r = `${t ?? ""}${n}`;
return e === 2 && [0, 1].includes(n) && s === 0 ? `${r}_cloned` : r;
}, be = ({
rangeExtractor: e = ie,
initialIndex: t = 0,
direction: n = "vertical",
swipeDistanceFactor: r = 0.12,
loop: o = !1,
keyExtractor: u = ce,
useNavKeys: d = !0,
transitionDuration: y = 300,
enableWheel: R = !1,
wheelDebounceMs: C = 200,
...i
swipeDistanceFactor: s = 0.12,
loop: r = !1,
keyExtractor: i = Se,
enableNavKeys: a = !0,
transitionDuration: g = 300,
enableWheel: v = !1,
wheelDebounceMs: S = 200,
transition: y = ae,
enableGestures: E = !0,
...c
}) => {
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,
const { size: C, apiRef: u } = c, f = C === void 0, [b, P] = $([0, 0]), D = f ? b : C, h = k(null);
h.current = {
...c,
size: D,
rangeExtractor: e,
initialIndex: t,
direction: n,
swipeDistanceFactor: r,
loop: o,
keyExtractor: u,
useNavKeys: d,
transitionDuration: y,
enableWheel: R,
wheelDebounceMs: C
swipeDistanceFactor: s,
loop: r,
keyExtractor: i,
enableNavKeys: a,
transitionDuration: g,
enableWheel: v,
wheelDebounceMs: S
};
const z = n === "horizontal", D = z ? O(f) : P(f), k = M(null), [s, N, j] = V(() => {
const a = Z(
const w = n === "horizontal", T = w ? W(D) : Y(D), R = k(null), [d, q, l] = $(() => {
const p = ce(
{
count: i.count,
initialIndex: e,
count: c.count,
initialIndex: t,
direction: n,
loop: o,
transitionDuration: y,
swipeDistanceFactor: r,
rangeExtractor: t,
enableWheel: R,
wheelDebounceMs: C
loop: r,
transitionDuration: g,
swipeDistanceFactor: s,
rangeExtractor: e,
enableWheel: v,
wheelDebounceMs: S,
enableGestures: E,
enableNavKeys: a
},
{
onBeforeChange: (l, v, E) => {
g.current.beforeChange?.(l, v, E);
onBeforeChange: (o, x, M) => {
h.current.beforeChange?.(o, x, M);
},
onAfterChange: (l, v) => {
g.current.afterChange?.(l, v);
onAfterChange: (o, x) => {
h.current.afterChange?.(o, x);
},
onDragStart: (l) => {
g.current.onSlideDragStart?.(l);
onDragStart: (o) => {
h.current.onSlideDragStart?.(o);
},
onDragEnd: (l) => {
g.current.onSlideDragEnd?.(l);
onDragEnd: (o) => {
h.current.onSlideDragEnd?.(o);
},
onDragCanceled: (l) => {
g.current.onSlideDragCanceled?.(l);
}
onDragCanceled: (o) => {
h.current.onSlideDragCanceled?.(o);
},
onTap: (o) => h.current.onTap?.(o),
onDoubleTap: (o) => h.current.onDoubleTap?.(o),
onLongPress: (o) => h.current.onLongPress?.(o),
onLongPressEnd: (o) => h.current.onLongPressEnd?.(o),
...c.onNavKeyPress ? {
onNavKeyPress: (o) => h.current.onNavKeyPress?.(o)
} : {}
}
), x = {
index: a.state.index,
count: H(i.count),
goTo: a.goTo
), A = {
index: p.state.index,
count: O(c.count),
goTo: p.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(() => {
j.count.value = i.count, s.updateConfig({
count: i.count,
return [p, (o, x) => {
const { keyExtractor: M, itemBuilder: re, size: oe } = h.current;
return /* @__PURE__ */ m("div", { "data-index": o, children: re(o, x, oe) }, M(o, x));
}, A];
})[0], N = k(!1);
z(() => {
if (!N.current) {
N.current = !0;
return;
}
l.count.value = c.count, d.updateConfig({
count: c.count,
direction: n,
loop: o,
transitionDuration: y,
swipeDistanceFactor: r,
rangeExtractor: t
loop: r,
transitionDuration: g,
swipeDistanceFactor: s,
rangeExtractor: e,
enableGestures: E,
enableNavKeys: a,
enableWheel: v
});
}, [
i.count,
c.count,
n,
o,
y,
r,
t
]), w(() => {
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()
g,
s,
e,
E,
a,
v
]), z(() => {
d.setPrimarySize(T);
}, [T]), z(() => {
if (R.current && (d.attach(R.current), d.observe()), u != null) {
const p = {
next: () => d.next(),
prev: () => d.prev(),
goTo: (A, B) => d.goTo(A, B),
adjust: () => d.adjust(),
observe: () => d.observe(),
unobserve: () => d.unobserve()
};
typeof b == "function" ? b(a) : b.current = a;
typeof u == "function" ? u(p) : u.current = p;
}
return () => {
s.detach();
};
}, []), 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]
return d.detach;
}, []), ve(() => {
if (!f || !R.current) return;
const p = R.current, A = () => {
const o = p.clientWidth, x = p.clientHeight;
o > 0 && x > 0 && P(
(M) => M[0] === o && M[1] === x ? M : [o, x]
);
}, A = new ResizeObserver(x);
return A.observe(a), () => A.disconnect();
}, [m]);
const I = {
}, B = new ResizeObserver(A);
return B.observe(p), () => B.disconnect();
}, [f]);
const V = {
userSelect: "none",

@@ -173,92 +190,120 @@ WebkitUserSelect: "none",

overflow: "hidden",
...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,
...f ? {} : { width: W(D), height: Y(D) },
...c.style
}, { axisValue: L, indexes: I } = d.state, ne = !f || T > 0;
return /* @__PURE__ */ m(G.Provider, { value: l, children: /* @__PURE__ */ ge("div", { ref: R, className: c.className, style: V, children: [
ne && /* @__PURE__ */ m(U, { signals: [I], children: () => /* @__PURE__ */ m(
Ce,
{
primarySize: D,
isHorizontal: z,
axisValue: c,
length: T.value.length,
children: T.value.map(N)
primarySize: T,
isHorizontal: w,
axisValue: L,
transitionFn: y,
currentRangeIndex: d.getRangeIndex(),
direction: n,
children: I.value.map(q)
}
) }),
i.children
c.children
] }) });
}, ge = p(
ie,
(t, e) => (
}, ze = H(
be,
(e, t) => (
// useEffect deps (controller config sync)
t.count === e.count && t.direction === e.direction && t.loop === e.loop && t.transitionDuration === e.transitionDuration && t.swipeDistanceFactor === e.swipeDistanceFactor && t.rangeExtractor === e.rangeExtractor && t.useNavKeys === e.useNavKeys && t.enableWheel === e.enableWheel && t.wheelDebounceMs === e.wheelDebounceMs && // JSX/render output
t.size?.[0] === e.size?.[0] && t.size?.[1] === e.size?.[1] && t.className === e.className && t.style === e.style && t.children === e.children
e.count === t.count && e.direction === t.direction && e.loop === t.loop && e.transitionDuration === t.transitionDuration && e.swipeDistanceFactor === t.swipeDistanceFactor && e.rangeExtractor === t.rangeExtractor && e.enableNavKeys === t.enableNavKeys && e.enableWheel === t.enableWheel && e.wheelDebounceMs === t.wheelDebounceMs && e.transition === t.transition && e.enableGestures === t.enableGestures && // JSX/render output
e.size?.[0] === t.size?.[0] && e.size?.[1] === t.size?.[1] && e.className === t.className && e.style === t.style && e.children === t.children
)
), se = p(
), Ce = H(
({
children: t,
isHorizontal: e,
children: e,
isHorizontal: t,
primarySize: n,
axisValue: r,
length: o
}) => /* @__PURE__ */ h(re, { signal: r, children: (u) => /* @__PURE__ */ h(
"div",
{
style: {
position: "absolute",
top: 0,
left: 0,
display: "flex",
transform: `translate${e ? "X" : "Y"}(${u}px)`,
flexDirection: e ? "row" : "column",
[e ? "width" : "height"]: o * n,
[e ? "height" : "width"]: "100%"
},
children: t
}
) })
), F = (t) => {
axisValue: s,
transitionFn: r,
currentRangeIndex: i,
direction: a
}) => {
const g = Array.isArray(e) ? e : [e];
return /* @__PURE__ */ m(ye, { signal: s, children: (v) => /* @__PURE__ */ m(
"div",
{
style: {
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%"
},
children: g.map((S, y) => {
const E = r(
v,
y,
i,
n,
a
), c = S?.key ?? y;
return /* @__PURE__ */ m(
"div",
{
style: {
position: "absolute",
top: 0,
left: 0,
[t ? "width" : "height"]: n,
[t ? "height" : "width"]: "100%",
backfaceVisibility: "hidden",
...E
},
children: S
},
c
);
})
}
) });
}
), Q = (e) => {
const {
count: e,
count: t,
active: n,
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 <= o) {
g(0);
direction: s = "vertical",
visible: r = 5,
radius: i = 3,
gap: a = 4,
activeColor: g = "#fff",
inactiveColor: v = "rgba(255, 255, 255, 0.5)",
edgeScale: S = 0.5,
className: y,
style: E,
onDotClick: c
} = e, C = s === "vertical", u = i * 2, f = u + a, [b, P] = $(() => t <= r ? 0 : le(n - Math.floor(r / 2), 0, t - r));
z(() => {
if (t <= r) {
P(0);
return;
}
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(
P((l) => n < l ? Math.max(0, n) : n >= l + r ? Math.min(t - r, n - r + 1) : l);
}, [n, t, r]);
const D = Math.min(b + r, t), h = b > 0;
let T = Math.min(r, t) * f;
t > r && (T += f * 2);
const R = [], d = Math.max(0, b - 1), q = Math.min(t, D + 1);
for (let l = d; l < q; l++) {
const N = l === n;
let V = 1;
(l < b || l >= D) && (V = S);
let L;
l < b ? L = 0 : l >= D ? L = r + 1 : L = l - b + 1, !h && L > 0 && (L -= 1);
const I = L * f;
R.push(
/* @__PURE__ */ m(
"span",
{
"data-reel-indicator": c,
"data-reel-indicator": l,
style: {
position: "absolute",
[m ? "top" : "left"]: x,
[m ? "left" : "top"]: 0,
width: S,
height: S,
[C ? "top" : "left"]: I,
[C ? "left" : "top"]: 0,
width: f,
height: f,
display: "flex",

@@ -269,15 +314,15 @@ justifyContent: "center",

},
onClick: b ? () => b(c) : void 0,
"data-testid": `indicator-dot-${c}`,
children: /* @__PURE__ */ h(
onClick: c ? () => c(l) : void 0,
"data-testid": `indicator-dot-${l}`,
children: /* @__PURE__ */ m(
"span",
{
style: {
width: $,
height: $,
width: u,
height: u,
borderRadius: "50%",
backgroundColor: T ? y : R,
backgroundColor: N ? g : v,
transition: "transform 0.2s ease, background-color 0.2s ease",
transform: `scale(${W})`,
cursor: b ? "pointer" : "default"
transform: `scale(${V})`,
cursor: c ? "pointer" : "default"
}

@@ -287,91 +332,212 @@ }

},
c
l
)
);
}
return /* @__PURE__ */ h(
return /* @__PURE__ */ m(
"div",
{
className: i,
className: y,
style: {
position: "relative",
overflow: "hidden",
[m ? "height" : "width"]: s,
[m ? "width" : "height"]: S,
...B
[C ? "height" : "width"]: T,
[C ? "width" : "height"]: f,
...E
},
children: N
children: R
}
);
}, ae = (t) => {
const e = G(L), n = t.active !== void 0, r = t.count !== void 0;
if (!n && !e)
}, De = (e) => {
const t = K(G), n = e.active !== void 0, s = e.count !== void 0;
if (!n && !t)
throw new Error(
'ReelIndicator: "active" prop is required when rendered outside a <Reel> component.'
);
if (!r && !e)
if (!s && !t)
throw new Error(
'ReelIndicator: "count" prop is required when rendered outside a <Reel> component.'
);
if (n && r)
return /* @__PURE__ */ h(
F,
if (n && s)
return /* @__PURE__ */ m(
Q,
{
...t,
active: t.active,
count: t.count
...e,
active: e.active,
count: e.count
}
);
const o = [
!n && e.index,
!r && e.count
].filter(Boolean), u = t.onDotClick ?? ((d) => {
e.goTo(d, !0);
const r = [
!n && t.index,
!s && t.count
].filter(Boolean), i = e.onDotClick ?? ((a) => {
t.goTo(a, !0);
});
return /* @__PURE__ */ h(X, { signals: o, children: () => /* @__PURE__ */ h(
F,
return /* @__PURE__ */ m(U, { signals: r, children: () => /* @__PURE__ */ m(
Q,
{
...t,
active: t.active ?? e.index.value,
count: t.count ?? e.count.value,
onDotClick: u
...e,
active: e.active ?? t.index.value,
count: e.count ?? t.count.value,
onDotClick: i
}
) });
}, ve = p(ae), ye = (t) => {
w(() => {
if (!t) return;
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;
}, ke = H(De), Pe = ({
direction: e,
enabled: t = !0,
onClose: n,
children: s,
threshold: r = 0.2,
className: i
}) => {
const a = k(null), g = k({ onClose: n, threshold: r });
g.current = { onClose: n, threshold: r };
const v = e === "down", S = v ? 1 : -1, [{ dragOffset: y, opacity: E, isTransitioning: c, controller: C }] = $(
() => {
const u = O(0), f = O(1), b = O(!1), P = { current: !1 }, D = () => {
b.value = !0, u.value = 0, f.value = 1, setTimeout(() => {
b.value = !1;
}, 300);
}, h = ue(
{ useTouchEventsOnly: !0 },
{
onVerticalDragStart: () => {
P.current = !1;
},
onVerticalDragUpdate: (w) => {
if (v ? w.primaryDistance > 0 : w.primaryDistance < 0) {
u.value = w.primaryDistance;
const R = window.innerHeight, d = Math.min(
_(w.primaryDistance) / (R * 0.3),
1
);
f.value = 1 - d * 0.8;
}
},
onVerticalDragEnd: (w) => {
P.current = !0;
const T = window.innerHeight, R = T * g.current.threshold;
(v ? w.primaryDistance > 0 : w.primaryDistance < 0) && _(w.primaryDistance) > R ? (b.value = !0, u.value = T * S, f.value = 0, setTimeout(() => g.current.onClose(), 300)) : D();
},
onDragEnd: () => {
!P.current && u.value !== 0 && D();
}
}
);
return {
dragOffset: u,
opacity: f,
isTransitioning: b,
controller: h
};
}
);
return z(() => {
const u = a.current;
if (!(!t || !u))
return C.attach(u), C.observe(), () => {
C.unobserve(), C.detach();
};
}, [t]), /* @__PURE__ */ m(U, { signals: [y, E, c], children: () => /* @__PURE__ */ m(
"div",
{
ref: a,
className: i,
style: {
transform: `translateY(${y.value}px)`,
opacity: E.value,
transition: c.value ? "transform 300ms ease-out, opacity 300ms ease-out" : "none",
width: "100%",
height: "100%"
},
children: s
}
) });
}, te = ee(null), Le = ({ children: e }) => {
const t = $(() => de())[0];
return /* @__PURE__ */ m(te.Provider, { value: t, children: e });
}, Me = () => {
const e = K(te);
if (!e)
throw new Error("useSoundState must be used within SoundProvider");
return e;
}, $e = (e) => {
const [t] = $(fe);
z(() => {
if (e)
return t.lock();
}, [e]);
}, Ve = (e) => {
const [t] = $(() => {
const n = () => {
e.ref.current !== null && (F.value && j(), me(e.ref.current).catch((i) => {
console.log(
`Error attempting to enable full-screen mode: ${i.message} (${i.name})`
);
}));
}, s = () => {
j().catch((i) => {
console.log(
`Error attempting to exit full-screen mode: ${i.message} (${i.name})`
);
});
};
}, [t]);
return [n, s, () => {
F.value ? s() : n();
}];
});
return z(() => {
const n = F.observe(he);
return () => {
n(), F.value && j();
};
}, []), [F, ...t];
};
export {
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
ye as AnimatedObserve,
U as Observe,
ze as Reel,
G as ReelContext,
ke as ReelIndicator,
Le as SoundProvider,
Pe as SwipeToClose,
Fe as abs,
Ne as captureFrame,
Ie as clamp,
Oe as createBodyLock,
qe as createComputed,
je as createContentLoadingController,
Ke as createContentPreloader,
xe as createDefaultKeyExtractorForLoop,
He as createDeferred,
Ue as createDisposableList,
Ge as createGestureController,
We as createSharedVideo,
Ye as createSignal,
_e as createSoundController,
Je as cubeTransition,
Qe as defaultRangeExtractor,
Xe as exitFullscreen,
Ze as extractRange,
et as fadeTransition,
tt as first,
nt as flipTransition,
rt as fullscreenSignal,
ot as generate,
st as getSlideProgress,
it as isNegative,
ct as last,
at as lerp,
lt as noop,
ut as observeDomEvent,
dt as observeMediaLoading,
ft as reaction,
ht as requestFullscreen,
mt as slideTransition,
gt as syncMutedToVideo,
$e as useBodyLock,
Ve as useFullscreen,
pe as useReelContext,
Me as useSoundState,
vt as zoomTransition
};
import { MutableRefObject, CSSProperties, ReactNode } from 'react';
import { defaultRangeExtractor, RangeExtractor } from '@reelkit/core';
import { defaultRangeExtractor, RangeExtractor, TransitionTransformFn, GestureCommonEvent, GestureEvent } from '@reelkit/core';
/**

@@ -10,11 +10,2 @@ * Props for the {@link Reel} virtualized one-item slider component.

/**
* Render function called for each visible slide.
*
* @param index - The absolute slide index (0-based).
* @param indexInRange - Position within the current visible range.
* @param size - The `[width, height]` dimensions of the slider.
* @returns A React element representing the slide content.
*/
itemBuilder: (index: number, indexInRange: number, size: [number, number]) => ReactNode;
/**
* Ref or callback to access the imperative {@link ReelApi}. Accepts either

@@ -64,3 +55,3 @@ * a `MutableRefObject` or a callback function.

*/
useNavKeys?: boolean;
enableNavKeys?: boolean;
/**

@@ -76,3 +67,38 @@ * Enable mouse wheel navigation.

wheelDebounceMs?: number;
/** Optional CSS class name for the root container element. */
className?: string;
/** Optional inline styles for the root container element. */
style?: CSSProperties;
/**
* Whether gesture (touch/mouse drag) navigation is enabled.
* When `false`, navigation is only possible via the API (`next`, `prev`, `goTo`).
* @default true
*/
enableGestures?: boolean;
/** Optional children rendered after the slider content (e.g. indicators, overlays). */
children?: ReactNode;
/**
* Transition effect for slide animations.
* Import a built-in transition (`slideTransition`, `cubeTransition`, etc.)
* or pass a custom {@link TransitionTransformFn}. Only the imported
* transition ships in the bundle (tree-shakeable).
*
* @default slideTransition
*/
transition?: TransitionTransformFn;
/**
* Custom function to determine which slide indices are rendered.
* Defaults to the built-in extractor that returns current ± 1 overscan.
*/
rangeExtractor?: RangeExtractor;
/**
* Render function called for each visible slide.
*
* @param index - The absolute slide index (0-based).
* @param indexInRange - Position within the current visible range.
* @param size - The `[width, height]` dimensions of the slider.
* @returns A React element representing the slide content.
*/
itemBuilder: (index: number, indexInRange: number, size: [number, number]) => ReactNode;
/**
* Called after a slide transition completes and the index is updated.

@@ -115,12 +141,27 @@ * @param index - The new active slide index.

/**
* Custom function to determine which slide indices are rendered.
* Defaults to the built-in extractor that returns current ± 1 overscan.
* Fired on a single tap (no drag, no long press). Requires `enableGestures`.
* @param event - Gesture event with position and element info.
*/
rangeExtractor?: RangeExtractor;
/** Optional CSS class name for the root container element. */
className?: string;
/** Optional inline styles for the root container element. */
style?: CSSProperties;
/** Optional children rendered after the slider content (e.g. indicators, overlays). */
children?: ReactNode;
onTap?: (event: GestureCommonEvent) => void;
/**
* Fired on double-tap. Requires `enableGestures`.
* @param event - Gesture event with position and element info.
*/
onDoubleTap?: (event: GestureCommonEvent) => void;
/**
* Fired when a long press is detected. Requires `enableGestures`.
* @param event - Gesture event with position and element info.
*/
onLongPress?: (event: GestureCommonEvent) => void;
/**
* Fired when the pointer is released after a long press. Requires `enableGestures`.
* @param event - Gesture event with position, element, and drag info.
*/
onLongPressEnd?: (event: GestureEvent) => void;
/**
* Fired when a navigation key is pressed (arrow keys). When provided,
* replaces the default prev/next slide behavior.
* @param increment - Direction: `-1` for prev, `1` for next.
*/
onNavKeyPress?: (increment: -1 | 1) => void;
}

@@ -148,3 +189,4 @@ /**

/**
* Navigate to a specific slide index.
* Navigate to a specific slide index. Returns a promise that
* resolves when the transition completes.
* @param index - Target slide index.

@@ -169,3 +211,3 @@ * @param animate - When `true`, animates the transition.

*/
export declare const Reel: import('react').MemoExoticComponent<({ rangeExtractor, initialIndex, direction, swipeDistanceFactor, loop, keyExtractor, useNavKeys, transitionDuration, enableWheel, wheelDebounceMs, ...props }: ReelProps) => import("react/jsx-runtime").JSX.Element>;
export declare const Reel: import('react').MemoExoticComponent<({ rangeExtractor, initialIndex, direction, swipeDistanceFactor, loop, keyExtractor, enableNavKeys, transitionDuration, enableWheel, wheelDebounceMs, transition, enableGestures, ...props }: ReelProps) => import("react/jsx-runtime").JSX.Element>;
export { defaultRangeExtractor };
/**
* Locks the document body scroll when `locked` is `true`. Adds
* `overflow: hidden` to `document.body` and compensates for the
* disappearing scrollbar by adding equivalent `padding-right`,
* preventing layout shift. Original styles are restored on cleanup
* or when `locked` becomes `false`.
* Locks the document body scroll when `locked` is `true`.
*
* Uses the core {@link createBodyLock} utility with reference counting,
* so multiple concurrent callers can each lock/unlock independently.
* Restores all original styles and scroll position on cleanup.
*
* @param locked - Whether body scroll should be locked.

@@ -9,0 +9,0 @@ */

{
"name": "@reelkit/react",
"version": "0.2.1",
"version": "0.3.0",
"type": "module",

@@ -55,8 +55,8 @@ "sideEffects": false,

"dependencies": {
"@reelkit/core": ">=0.1.2"
"@reelkit/core": "^0.3.0"
},
"peerDependencies": {
"react": ">=17.0.0",
"react-dom": ">=17.0.0"
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
}
}

@@ -5,7 +5,8 @@ # @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.9%20kB-6366f1" alt="Bundle size" />
<img src="https://img.shields.io/badge/gzip-3.9%20kB-6366f1" alt="Bundle size" />
<img src="https://img.shields.io/badge/coverage-96%25-brightgreen" alt="Coverage" />
<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 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.
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. ~3.9 kB gzip.

@@ -65,3 +66,3 @@ ## Installation

| `transitionDuration` | `number` | `300` | Animation duration in ms |
| `useNavKeys` | `boolean` | `true` | Enable keyboard navigation |
| `enableNavKeys` | `boolean` | `true` | Enable keyboard navigation |
| `enableWheel` | `boolean` | `false` | Enable mouse wheel navigation |

@@ -68,0 +69,0 @@ | `wheelDebounceMs` | `number` | `200` | Wheel debounce duration |