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-lightbox

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-lightbox - npm Package Compare versions

Comparing version
0.2.1
to
0.3.0
+7
dist/lib/lightboxFadeTransition.d.ts
import { TransitionTransformFn } from '@reelkit/react';
/**
* Lightbox crossfade transition with a subtle horizontal nudge.
* The current slide fades out while the next fades in. A small
* translateX offset adds depth to the crossfade.
*/
export declare const lightboxFadeTransition: TransitionTransformFn;
import { TransitionTransformFn } from '@reelkit/react';
/**
* Lightbox zoom-in transition. Incoming slides scale from 70% to 100%
* while fading in. Off-screen slides (distance >= 1) are fully hidden.
*/
export declare const lightboxZoomTransition: TransitionTransformFn;
+1
-1

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

.rk-lightbox-container{position:fixed;inset:0;background-color:#000;z-index:9999;display:flex;align-items:center;justify-content:center}.rk-lightbox-slide{display:flex;align-items:center;justify-content:center;flex-shrink:0}.rk-lightbox-slide.rk-transition-fade{opacity:.3;transition:opacity .3s ease-out}.rk-lightbox-slide.rk-transition-fade.rk-active{opacity:1}.rk-lightbox-slide.rk-transition-zoom-in{transform:scale(.8);opacity:.5;transition:transform .3s ease-out,opacity .3s ease-out}.rk-lightbox-slide.rk-transition-zoom-in.rk-active{transform:scale(1);opacity:1}.rk-lightbox-img{width:100%;height:100%;object-fit:contain;-webkit-user-select:none;user-select:none;-webkit-user-drag:none}.rk-lightbox-controls-left{position:absolute;top:16px;left:16px;z-index:10;display:flex;align-items:center;gap:12px}.rk-lightbox-counter{color:#fff;font-size:14px;font-weight:500;background:#00000080;padding:6px 12px;border-radius:20px;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.rk-lightbox-btn{display:flex;align-items:center;justify-content:center;width:36px;height:36px;border:none;background:#00000080;color:#fff;border-radius:50%;cursor:pointer;transition:background-color .2s,transform .2s;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.rk-lightbox-btn:hover{background:#fff3;transform:scale(1.05)}.rk-lightbox-btn:active{transform:scale(.95)}.rk-lightbox-close{position:absolute;top:16px;right:16px;z-index:10;display:flex;align-items:center;justify-content:center;width:40px;height:40px;border:none;background:#00000080;color:#fff;border-radius:50%;cursor:pointer;transition:background-color .2s,transform .2s;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.rk-lightbox-close:hover{background:#fff3;transform:scale(1.05)}.rk-lightbox-close:active{transform:scale(.95)}.rk-lightbox-nav{position:absolute;top:50%;transform:translateY(-50%);z-index:10;display:flex;align-items:center;justify-content:center;width:48px;height:48px;border:none;background:#00000080;color:#fff;border-radius:50%;cursor:pointer;transition:background-color .2s,transform .2s,opacity .2s;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);opacity:.7}.rk-lightbox-nav:hover{background:#fff3;opacity:1;transform:translateY(-50%) scale(1.05)}.rk-lightbox-nav:active{transform:translateY(-50%) scale(.95)}.rk-lightbox-nav-prev{left:16px}.rk-lightbox-nav-next{right:16px}.rk-lightbox-info{position:absolute;bottom:0;left:0;right:0;padding:24px;background:linear-gradient(transparent,#000c);color:#fff;pointer-events:none}.rk-lightbox-title{margin:0 0 8px;font-size:18px;font-weight:600}.rk-lightbox-description{margin:0;font-size:14px;opacity:.8;line-height:1.5}.rk-lightbox-swipe-hint{position:absolute;bottom:24px;left:50%;transform:translate(-50%);color:#ffffff80;font-size:12px;padding:8px 16px;background:#0000004d;border-radius:20px;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);pointer-events:none;animation:rk-lightboxFadeInOut 3s ease-in-out forwards}@keyframes rk-lightboxFadeInOut{0%{opacity:0}20%{opacity:1}80%{opacity:1}to{opacity:0}}.rk-lightbox-container:has(.rk-lightbox-swipe-hint) .rk-lightbox-info{padding-bottom:64px}.rk-lightbox-video-container{position:relative;display:flex;align-items:center;justify-content:center;background-color:#000;overflow:hidden}.rk-lightbox-video-element{width:100%;height:100%;object-fit:contain;object-position:center}.rk-lightbox-video-element::-internal-media-controls-overlay-cast-button{display:none}.rk-lightbox-video-poster{position:absolute;inset:0;width:100%;height:100%;object-position:center;z-index:2;opacity:0;transition:opacity .3s ease;pointer-events:none}.rk-lightbox-video-poster.rk-visible{opacity:1}.rk-lightbox-video-loader{position:absolute;inset:0;z-index:3;pointer-events:none;background:linear-gradient(90deg,transparent 0%,rgba(255,255,255,.15) 50%,transparent 100%);background-size:200% 100%;animation:rk-lightbox-video-wave 1.5s ease-in-out infinite;opacity:0;transition:opacity .3s ease}.rk-lightbox-video-loader.rk-visible{opacity:1}@keyframes rk-lightbox-video-wave{0%{background-position:200% 0}to{background-position:-200% 0}}
.rk-lightbox-container{position:fixed;inset:0;background-color:#000;z-index:9999;display:flex;align-items:center;justify-content:center;overscroll-behavior:none}.rk-lightbox-slide{display:flex;align-items:center;justify-content:center;flex-shrink:0}.rk-lightbox-img{width:100%;height:100%;object-fit:contain;-webkit-user-select:none;user-select:none;-webkit-user-drag:none}.rk-lightbox-container:before{content:"";position:absolute;top:0;left:0;right:0;height:80px;background:linear-gradient(rgba(0,0,0,.6),transparent);pointer-events:none;z-index:9}.rk-lightbox-controls-left{position:absolute;top:16px;left:16px;z-index:10;display:flex;align-items:center;gap:12px}.rk-lightbox-counter{color:#fff;font-size:14px;font-weight:500;background:#00000080;padding:6px 12px;border-radius:20px;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.rk-lightbox-btn{display:flex;align-items:center;justify-content:center;width:36px;height:36px;border:none;background:#00000080;color:#fff;border-radius:50%;cursor:pointer;transition:background-color .2s,transform .2s;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.rk-lightbox-btn:hover{background:#fff3;transform:scale(1.05)}.rk-lightbox-btn:active{transform:scale(.95)}.rk-lightbox-spinner{position:absolute;top:22px;right:72px;z-index:10;width:28px;height:28px;border:3px solid rgba(255,255,255,.2);border-top-color:#fff;border-radius:50%;animation:rk-lightbox-spin .8s linear infinite}@keyframes rk-lightbox-spin{to{transform:rotate(360deg)}}.rk-lightbox-img-error{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:10;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;color:#fff6}.rk-lightbox-img-error-text{font-size:13px;letter-spacing:.02em}.rk-lightbox-close{position:absolute;top:16px;right:16px;z-index:10;display:flex;align-items:center;justify-content:center;width:40px;height:40px;border:none;background:#00000080;color:#fff;border-radius:50%;cursor:pointer;transition:background-color .2s,transform .2s;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.rk-lightbox-close:hover{background:#fff3;transform:scale(1.05)}.rk-lightbox-close:active{transform:scale(.95)}.rk-lightbox-nav{position:absolute;top:50%;transform:translateY(-50%);z-index:10;display:flex;align-items:center;justify-content:center;width:48px;height:48px;border:none;background:#00000080;color:#fff;border-radius:50%;cursor:pointer;transition:background-color .2s,transform .2s,opacity .2s;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);opacity:.7}.rk-lightbox-nav:hover{background:#fff3;opacity:1;transform:translateY(-50%) scale(1.05)}.rk-lightbox-nav:active{transform:translateY(-50%) scale(.95)}.rk-lightbox-nav-prev{left:16px}.rk-lightbox-nav-next{right:16px}.rk-lightbox-info{position:absolute;bottom:0;left:0;right:0;padding:24px;background:linear-gradient(transparent,#000c);color:#fff;pointer-events:none}.rk-lightbox-title{margin:0 0 8px;font-size:18px;font-weight:600}.rk-lightbox-description{margin:0;font-size:14px;opacity:.8;line-height:1.5}.rk-lightbox-swipe-hint{position:absolute;bottom:24px;left:50%;transform:translate(-50%);color:#ffffff80;font-size:12px;padding:8px 16px;background:#0000004d;border-radius:20px;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);pointer-events:none;animation:rk-lightboxFadeInOut 3s ease-in-out forwards}@keyframes rk-lightboxFadeInOut{0%{opacity:0}20%{opacity:1}80%{opacity:1}to{opacity:0}}.rk-lightbox-container:has(.rk-lightbox-swipe-hint) .rk-lightbox-info{padding-bottom:64px}.rk-lightbox-video-container{position:relative;display:flex;align-items:center;justify-content:center;background-color:#000;overflow:hidden}.rk-lightbox-video-element{width:100%;height:100%;object-fit:contain;object-position:center}.rk-lightbox-video-element::-internal-media-controls-overlay-cast-button{display:none}.rk-lightbox-video-poster{position:absolute;inset:0;width:100%;height:100%;object-position:center;z-index:2;opacity:0;transition:opacity .3s ease;pointer-events:none}.rk-lightbox-video-poster.rk-visible{opacity:1}

@@ -45,4 +45,6 @@ /**

export { LightboxOverlay } from './lib/LightboxOverlay';
export { lightboxFadeTransition } from './lib/lightboxFadeTransition';
export { lightboxZoomTransition } from './lib/lightboxZoomTransition';
export type { LightboxOverlayProps, LightboxItem, TransitionType, ReelProxyProps, } from './lib/LightboxOverlay';
export type { LightboxControlsRenderProps, NavigationRenderProps, InfoRenderProps, } from './lib/types';
export type { ControlsRenderProps, SlideRenderProps, NavigationRenderProps, InfoRenderProps, } from './lib/types';
export { CloseButton, Counter, FullscreenButton, SoundButton, } from './lib/LightboxControls';

@@ -54,3 +56,1 @@ export type { CloseButtonProps, CounterProps, FullscreenButtonProps, SoundButtonProps, } from './lib/LightboxControls';

export type { UseVideoSlideRendererResult } from './lib/useVideoSlideRenderer';
export { useFullscreen } from './lib/useFullscreen';
export type { UseFullscreenProps, UseFullscreenResult, } from './lib/useFullscreen';

@@ -1,465 +0,496 @@

import { jsx as n, jsxs as k, Fragment as q } from "react/jsx-runtime";
import { useState as y, useRef as L, useEffect as w, useCallback as p, useLayoutEffect as ie, useMemo as ce } from "react";
import { createPortal as ae } from "react-dom";
import { createSignal as S, createGestureController as ue, Observe as Q, useBodyLock as de, Reel as he, createSharedVideo as me, noop as fe, captureFrame as ge } from "@reelkit/react";
import { X as ve, Minimize as be, Maximize as pe, VolumeX as we, Volume2 as xe, ChevronLeft as ke, ChevronRight as Fe } from "lucide-react";
const T = () => {
const e = document;
return (e.fullscreenElement ?? e.webkitFullscreenElement ?? e.mozFullScreenElement ?? e.msFullscreenElement) != null;
}, $ = () => {
const e = document;
return e.exitFullscreen ? e.exitFullscreen() : e.webkitExitFullscreen ? e.webkitExitFullscreen() : e.mozCancelFullScreen ? e.mozCancelFullScreen() : e.msExitFullscreen ? e.msExitFullscreen() : Promise.resolve();
}, Ee = (e) => {
const t = e;
return t.requestFullscreen ? t.requestFullscreen() : t.webkitRequestFullscreen ? t.webkitRequestFullscreen() : t.webkitEnterFullscreen ? t.webkitEnterFullscreen() : t.mozRequestFullScreen ? t.mozRequestFullScreen() : t.msRequestFullscreen ? t.msRequestFullscreen() : Promise.resolve();
}, ye = (e) => {
const [t, o] = y(!1), l = L(!0);
w(() => (l.current = !0, () => {
l.current = !1;
}), []);
const i = p(() => {
e.ref.current !== null && (T() && $(), Ee(e.ref.current).then(() => {
l.current && o(!0);
}).catch((r) => {
console.log(
`Error attempting to enable full-screen mode: ${r.message} (${r.name})`
);
}));
}, []), u = p(() => {
$().then(() => {
l.current && o(!1);
}).catch((r) => {
console.log(
`Error attempting to exit full-screen mode: ${r.message} (${r.name})`
);
});
}, []), a = p(() => {
T() ? u() : i();
}, []);
return w(() => {
const r = () => {
l.current && o(T());
};
return document.addEventListener("fullscreenchange", r), document.addEventListener("webkitfullscreenchange", r), document.addEventListener("mozfullscreenchange", r), document.addEventListener("MSFullscreenChange", r), () => {
document.removeEventListener("fullscreenchange", r), document.removeEventListener(
"webkitfullscreenchange",
r
), document.removeEventListener(
"mozfullscreenchange",
r
), document.removeEventListener(
"MSFullscreenChange",
r
);
};
}, []), w(() => () => {
T() && $();
}, []), [t, i, u, a];
}, Le = ({
enabled: e,
onClose: t,
children: o,
className: l
}) => {
const i = L(null), u = L(null), [a, r, c] = y(
() => [S(0), S(1), S(!1)]
)[0], h = L(!1);
return w(() => {
if (!e || !i.current) return;
const s = window.innerHeight, f = s * 0.2, x = () => {
c.value = !0, a.value = 0, r.value = 1, setTimeout(() => {
c.value = !1;
}, 300);
}, g = ue(
{ useTouchEventsOnly: !0 },
{
onVerticalDragStart: () => {
h.current = !1;
},
onVerticalDragUpdate: (F) => {
const { primaryDistance: v } = F;
if (v < 0) {
a.value = v;
const C = Math.min(
Math.abs(v) / (s * 0.3),
1
);
r.value = 1 - C * 0.8;
}
},
onVerticalDragEnd: (F) => {
h.current = !0;
const { primaryDistance: v } = F;
v < 0 && Math.abs(v) > f ? (c.value = !0, a.value = -s, r.value = 0, setTimeout(t, 300)) : x();
},
// Fallback: reset if vertical drag started but onVerticalDragEnd wasn't called
// (happens when last delta direction differs from overall drag direction)
onDragEnd: () => {
!h.current && a.value !== 0 && x();
}
}
);
return g.attach(i.current), g.observe(), u.current = g, () => {
g.unobserve(), g.detach(), u.current = null;
};
}, [e, t, a, r, c]), /* @__PURE__ */ n(Q, { signals: [a, r, c], children: () => /* @__PURE__ */ n(
"div",
{
ref: i,
className: l,
style: {
transform: `translateY(${a.value}px)`,
opacity: r.value,
transition: c.value ? "transform 300ms ease-out, opacity 300ms ease-out" : "none"
},
children: o
}
) });
}, Z = ({
onClick: e,
className: t = "rk-lightbox-close",
style: o
}) => /* @__PURE__ */ n(
import { jsx as e, jsxs as f, Fragment as p } from "react/jsx-runtime";
import { useRef as M, useState as Q, useEffect as O, useLayoutEffect as ue, useMemo as ge, useCallback as G } from "react";
import { createPortal as he } from "react-dom";
import { getSlideProgress as Y, abs as K, clamp as me, createSignal as z, createContentLoadingController as ve, useFullscreen as fe, useBodyLock as be, createDisposableList as ee, observeDomEvent as j, reaction as pe, Observe as x, SwipeToClose as xe, Reel as ke, flipTransition as Ce, slideTransition as we, createContentPreloader as ye, useSoundState as ne, observeMediaLoading as Ne, syncMutedToVideo as Se, captureFrame as Re, createSharedVideo as Ee, SoundProvider as Fe } from "@reelkit/react";
import { X as Le, Minimize as Ie, Maximize as Pe, VolumeX as Te, Volume2 as ze, ImageOff as Me, ChevronLeft as Ve, ChevronRight as We } from "lucide-react";
const Be = (n, i, t, o) => {
if (o === 0) return { opacity: 0 };
const r = Y(
n,
i,
t,
o
), g = K(r);
return {
transform: `translateX(${r * o * 0.08}px)`,
opacity: me(1 - g, 0, 1),
zIndex: i === t ? 2 : 1
};
}, $e = (n, i, t, o) => {
if (o === 0) return { opacity: 0 };
const r = Y(
n,
i,
t,
o
), g = K(r);
if (g >= 1) return { opacity: 0, zIndex: 0 };
const h = 0.7 + (1 - g) * 0.3;
return {
transform: `translateX(${r * o * 0.08}px) scale(${h})`,
opacity: 1 - g,
zIndex: g < 0.5 ? 2 : 1
};
}, te = ({
onClick: n,
className: i = "rk-lightbox-close",
style: t
}) => /* @__PURE__ */ e(
"button",
{
className: t,
onClick: e,
className: i,
onClick: n,
title: "Close (Esc)",
style: o,
children: /* @__PURE__ */ n(ve, { size: 24 })
style: t,
children: /* @__PURE__ */ e(Le, { size: 24 })
}
), K = ({
currentIndex: e,
count: t,
className: o = "rk-lightbox-counter",
style: l
}) => /* @__PURE__ */ k("span", { className: o, style: l, children: [
e + 1,
), re = ({
currentIndex: n,
count: i,
className: t = "rk-lightbox-counter",
style: o
}) => /* @__PURE__ */ f("span", { className: t, style: o, children: [
n + 1,
" / ",
t
] }), ee = ({
isFullscreen: e,
onToggle: t,
className: o = "rk-lightbox-btn",
style: l
}) => /* @__PURE__ */ n(
i
] }), oe = ({
isFullscreen: n,
onToggle: i,
className: t = "rk-lightbox-btn",
style: o
}) => /* @__PURE__ */ e(
"button",
{
className: o,
onClick: t,
title: e ? "Exit Fullscreen" : "Enter Fullscreen",
style: l,
children: e ? /* @__PURE__ */ n(be, { size: 20 }) : /* @__PURE__ */ n(pe, { size: 20 })
className: t,
onClick: i,
title: n ? "Exit Fullscreen" : "Enter Fullscreen",
style: o,
children: n ? /* @__PURE__ */ e(Ie, { size: 20 }) : /* @__PURE__ */ e(Pe, { size: 20 })
}
), Ce = ({
isMuted: e,
onToggle: t,
className: o = "rk-lightbox-btn",
style: l
}) => /* @__PURE__ */ n(
), De = ({
isMuted: n,
onToggle: i,
className: t = "rk-lightbox-btn",
style: o
}) => /* @__PURE__ */ e(
"button",
{
className: o,
onClick: t,
title: e ? "Unmute" : "Mute",
style: l,
children: e ? /* @__PURE__ */ n(we, { size: 20 }) : /* @__PURE__ */ n(xe, { size: 20 })
className: t,
onClick: i,
title: n ? "Unmute" : "Mute",
style: o,
children: n ? /* @__PURE__ */ e(Te, { size: 20 }) : /* @__PURE__ */ e(ze, { size: 20 })
}
), Re = ({
onClose: e,
currentIndex: t,
count: o,
isFullscreen: l,
onToggleFullscreen: i
}) => /* @__PURE__ */ k(q, { children: [
/* @__PURE__ */ k("div", { className: "rk-lightbox-controls-left", children: [
/* @__PURE__ */ n(K, { currentIndex: t, count: o }),
/* @__PURE__ */ n(
ee,
), je = ({
onClose: n,
currentIndex: i,
count: t,
isFullscreen: o,
onToggleFullscreen: r
}) => /* @__PURE__ */ f(p, { children: [
/* @__PURE__ */ f("div", { className: "rk-lightbox-controls-left", children: [
/* @__PURE__ */ e(re, { currentIndex: i, count: t }),
/* @__PURE__ */ e(
oe,
{
isFullscreen: l,
onToggle: i
isFullscreen: o,
onToggle: r
}
)
] }),
/* @__PURE__ */ n(Z, { onClick: e })
] }), U = 2, X = /* @__PURE__ */ new Set(), Y = (e) => {
if (X.has(e)) return;
X.add(e);
const t = new Image();
t.src = e;
}, Ne = ({
images: e,
initialIndex: t = 0,
onClose: o,
transition: l = "slide",
onSlideChange: i,
apiRef: u,
renderControls: a,
renderNavigation: r,
renderInfo: c,
renderSlide: h,
transitionDuration: s,
swipeDistanceFactor: f,
loop: x = !1,
useNavKeys: g = !0,
enableWheel: F = !0,
wheelDebounceMs: v
}) => {
const C = L(null), ne = L(null), z = u ?? ne, [m, re] = y(t), D = L(m);
D.current = m;
const [I] = y(
() => S(
/* @__PURE__ */ e(te, { onClick: n })
] }), Oe = {
slide: we,
fade: Be,
flip: Ce,
"zoom-in": $e
}, J = 2, S = ye({ maxCacheSize: 1e3 }), Xe = (n) => {
const {
images: i,
initialIndex: t = 0,
onClose: o,
transition: r = "slide",
transitionFn: g,
apiRef: h,
loop: k = !1,
enableNavKeys: C = !0,
enableWheel: R = !0,
wheelDebounceMs: b,
transitionDuration: w,
swipeDistanceFactor: c
} = n, u = M(n);
u.current = n;
const y = M(null), $ = M(null), E = h ?? $, [
{
sizeSignal: D,
loadingCtrl: F,
indexSignal: v,
isMobileSignal: N,
handleAfterChange: ie,
handlePrev: X,
handleNext: _,
itemBuilder: se
}
] = Q(() => {
const l = z(
typeof window < "u" ? [window.innerWidth, window.innerHeight] : [0, 0]
)
), [M, Pe, O, B] = ye({
ref: C
}), [V, oe] = y(
() => typeof window < "u" ? "ontouchstart" in window || navigator.maxTouchPoints > 0 : !1
);
de(!0), w(() => {
const d = () => {
I.value = [window.innerWidth, window.innerHeight], oe("ontouchstart" in window || navigator.maxTouchPoints > 0);
};
return window.addEventListener("resize", d), () => window.removeEventListener("resize", d);
}, []), w(() => {
const d = Math.max(0, m - U), P = Math.min(e.length - 1, m + U);
for (let b = d; b <= P; b++)
if (b !== m) {
const E = e[b];
(E.type ?? "image") === "video" ? E.poster && Y(E.poster) : Y(E.src);
), a = ve(!0, t), d = z(t), s = z(
typeof window < "u" ? "ontouchstart" in window || navigator.maxTouchPoints > 0 : !1
), W = /* @__PURE__ */ new Set(), ae = z(0);
return {
sizeSignal: l,
loadingCtrl: a,
indexSignal: d,
isMobileSignal: s,
imageErrors: W,
errorVersion: ae,
handleAfterChange: (m) => {
a.setActiveIndex(m);
const L = u.current.images[m]?.src;
L && S.isErrored(L) ? a.onError(m) : L && S.isLoaded(L) && a.onReady(m), d.value = m, u.current.onSlideChange?.(m);
},
handlePrev: () => {
E.current?.prev();
},
handleNext: () => {
E.current?.next();
},
itemBuilder: (m, L, I) => {
const { images: ce, renderSlide: H } = u.current, P = ce[m], U = m === d.value, Z = () => a.onReady(m), de = () => a.onWaiting(m), q = () => {
S.markErrored(P.src), a.onError(m);
};
if (H) {
const B = H({
item: P,
index: m,
size: I,
isActive: U,
onReady: Z,
onWaiting: de,
onError: q
});
if (B !== null)
return /* @__PURE__ */ e(
"div",
{
className: "rk-lightbox-slide",
style: { width: I[0], height: I[1] },
children: B
}
);
}
return /* @__PURE__ */ e(
"div",
{
className: "rk-lightbox-slide",
style: { width: I[0], height: I[1] },
children: /* @__PURE__ */ e(
"img",
{
src: P.src,
alt: P.title || `Image ${m + 1}`,
className: "rk-lightbox-img",
draggable: !1,
loading: U ? "eager" : "lazy",
onLoad: () => {
S.markLoaded(P.src), Z();
},
onError: (B) => {
B.target.style.display = "none", q();
}
}
)
}
);
}
}, [m, e]), w(() => {
const d = (P) => {
P.key === "Escape" && (M ? O() : o());
};
return window.addEventListener("keydown", d), () => window.removeEventListener("keydown", d);
}, [M, O, o]);
const se = p(
(d) => {
re(d), i?.(d);
},
[i]
), j = p(() => {
z.current?.prev();
}, [z]), H = p(() => {
z.current?.next();
}, [z]), R = e[m], le = p(
(d, P, b) => {
const E = e[d], W = d === D.current, _ = `rk-lightbox-slide ${l !== "slide" ? `rk-transition-${l}` : ""} ${W ? "rk-active" : ""}`.trim();
if (h) {
const G = h(E, d, b, W);
if (G !== null)
return /* @__PURE__ */ n(
"div",
{
className: _,
style: { width: b[0], height: b[1] },
children: G
}
}), [V, Ue, le, A] = fe({
ref: y
});
return be(!0), O(() => {
const l = ee();
l.push(
j(window, "resize", () => {
D.value = [window.innerWidth, window.innerHeight], N.value = "ontouchstart" in window || navigator.maxTouchPoints > 0;
}),
j(window, "keydown", (s) => {
s.key === "Escape" && (V.value ? le() : u.current.onClose());
}),
pe(
() => [v],
() => {
S.preloadRange(
u.current.images,
v.value,
J
);
}
return /* @__PURE__ */ n(
"div",
{
className: _,
style: { width: b[0], height: b[1] },
children: /* @__PURE__ */ n(
"img",
{
src: E.src,
alt: E.title || `Image ${d + 1}`,
className: "rk-lightbox-img",
draggable: !1,
loading: "lazy"
}
)
}
);
},
[e, l, h]
), A = /* @__PURE__ */ k("div", { ref: C, className: "rk-lightbox-container", children: [
a ? a({
onClose: o,
currentIndex: m,
count: e.length,
isFullscreen: M,
onToggleFullscreen: B
}) : /* @__PURE__ */ n(
Re,
{
onClose: o,
currentIndex: m,
count: e.length,
isFullscreen: M,
onToggleFullscreen: B
}
),
/* @__PURE__ */ n(Le, { enabled: V, onClose: o, children: /* @__PURE__ */ n(Q, { signals: [I], children: () => /* @__PURE__ */ n(
he,
{
count: e.length,
size: I.value,
direction: "horizontal",
initialIndex: t,
apiRef: z,
afterChange: se,
transitionDuration: s,
swipeDistanceFactor: f,
loop: x,
useNavKeys: g,
enableWheel: F,
wheelDebounceMs: v,
itemBuilder: le
}
) }) }),
r ? r({
onPrev: j,
onNext: H,
activeIndex: m,
count: e.length
}) : !V && e.length > 1 && /* @__PURE__ */ k(q, { children: [
m > 0 && /* @__PURE__ */ n(
"button",
)
), S.preloadRange(
u.current.images,
v.value,
J
);
const a = v.value, d = u.current.images[a]?.src;
return d && l.push(
S.onLoaded(d, () => F.onReady(a))
), l.dispose;
}, []), he(
/* @__PURE__ */ f("div", { ref: y, className: "rk-lightbox-container", children: [
/* @__PURE__ */ e(x, { signals: [V, v], children: () => {
const l = v.value, {
renderControls: a,
onClose: d,
images: s
} = u.current;
return /* @__PURE__ */ e(p, { children: a ? a({
item: s[l],
onClose: d,
activeIndex: l,
count: s.length,
isFullscreen: V.value,
onToggleFullscreen: A
}) : /* @__PURE__ */ e(
je,
{
onClose: d,
currentIndex: l,
count: s.length,
isFullscreen: V.value,
onToggleFullscreen: A
}
) });
} }),
/* @__PURE__ */ e(
x,
{
className: "rk-lightbox-nav rk-lightbox-nav-prev",
onClick: j,
title: "Previous",
children: /* @__PURE__ */ n(ke, { size: 32 })
signals: [F.isLoading, F.isError, v],
children: () => {
const l = v.value, { renderLoading: a, renderError: d, images: s } = u.current, W = s[l];
return F.isError.value ? d ? /* @__PURE__ */ e(p, { children: d({ item: W, activeIndex: l }) }) : /* @__PURE__ */ f(
"div",
{
className: "rk-lightbox-img-error",
role: "img",
"aria-label": "Content unavailable",
children: [
/* @__PURE__ */ e(Me, { size: 48, strokeWidth: 1.5, "aria-hidden": "true" }),
/* @__PURE__ */ e("span", { className: "rk-lightbox-img-error-text", children: "Content unavailable" })
]
}
) : F.isLoading.value ? a ? /* @__PURE__ */ e(p, { children: a({ item: W, activeIndex: l }) }) : /* @__PURE__ */ e("div", { className: "rk-lightbox-spinner" }) : null;
}
}
),
m < e.length - 1 && /* @__PURE__ */ n(
"button",
/* @__PURE__ */ e(x, { signals: [D, N], children: () => /* @__PURE__ */ e(
xe,
{
className: "rk-lightbox-nav rk-lightbox-nav-next",
onClick: H,
title: "Next",
children: /* @__PURE__ */ n(Fe, { size: 32 })
direction: "up",
enabled: N.value,
onClose: o,
children: /* @__PURE__ */ e(
ke,
{
count: i.length,
size: D.value,
direction: "horizontal",
initialIndex: t,
apiRef: E,
afterChange: ie,
transition: g ?? Oe[r],
transitionDuration: w,
swipeDistanceFactor: c,
loop: k,
enableNavKeys: C,
enableWheel: R,
wheelDebounceMs: b,
itemBuilder: se
}
)
}
)
) }),
/* @__PURE__ */ e(x, { signals: [v, N], children: () => {
const l = v.value, a = N.value, { renderNavigation: d, images: s } = u.current;
return d ? /* @__PURE__ */ e(p, { children: d({
item: s[l],
onPrev: X,
onNext: _,
activeIndex: l,
count: s.length
}) }) : a || s.length <= 1 ? null : /* @__PURE__ */ f(p, { children: [
l > 0 && /* @__PURE__ */ e(
"button",
{
className: "rk-lightbox-nav rk-lightbox-nav-prev",
onClick: X,
title: "Previous",
children: /* @__PURE__ */ e(Ve, { size: 32 })
}
),
l < s.length - 1 && /* @__PURE__ */ e(
"button",
{
className: "rk-lightbox-nav rk-lightbox-nav-next",
onClick: _,
title: "Next",
children: /* @__PURE__ */ e(We, { size: 32 })
}
)
] });
} }),
/* @__PURE__ */ e(x, { signals: [v], children: () => {
const l = v.value, { renderInfo: a, images: d } = u.current, s = d[l];
return a ? /* @__PURE__ */ e(p, { children: a({ item: s, index: l }) }) : !s?.title && !s?.description ? null : /* @__PURE__ */ f("div", { className: "rk-lightbox-info", children: [
s.title && /* @__PURE__ */ e("h3", { className: "rk-lightbox-title", children: s.title }),
s.description && /* @__PURE__ */ e("p", { className: "rk-lightbox-description", children: s.description })
] });
} }),
/* @__PURE__ */ e(x, { signals: [N], children: () => N.value ? /* @__PURE__ */ e("div", { className: "rk-lightbox-swipe-hint", children: "Swipe up to close" }) : null })
] }),
c ? c({ item: R, index: m }) : (R?.title || R?.description) && /* @__PURE__ */ k("div", { className: "rk-lightbox-info", children: [
R.title && /* @__PURE__ */ n("h3", { className: "rk-lightbox-title", children: R.title }),
R.description && /* @__PURE__ */ n("p", { className: "rk-lightbox-description", children: R.description })
] }),
V && /* @__PURE__ */ n("div", { className: "rk-lightbox-swipe-hint", children: "Swipe up to close" })
] });
return typeof document > "u" ? A : ae(A, document.body);
}, Oe = (e) => e.isOpen ? /* @__PURE__ */ n(Ne, { ...e }) : null, N = me({
document.body
);
}, Ye = (n) => n.isOpen ? /* @__PURE__ */ e(Xe, { ...n }) : null, T = Ee({
className: "rk-lightbox-video-element"
});
let te = !0;
const J = (e) => {
te = e;
const t = N.getVideo();
t.muted = e;
}, ze = typeof window < "u" ? ie : w, Me = ({
src: e,
poster: t,
isActive: o,
size: l,
slideKey: i
}), _e = typeof window < "u" ? ue : O, Ae = ({
src: n,
poster: i,
isActive: t,
size: o,
slideKey: r,
onPlaying: g,
onWaiting: h,
onError: k
}) => {
const u = L(null), [a, r] = y(!1), [c, h] = y(!0);
return ze(() => {
if (!o || !u.current) return;
const s = N.getVideo(), f = u.current;
r(!0), h(!0);
const x = () => r(!1), g = () => r(!0), F = () => {
r(!1), h(!1);
};
s.addEventListener("canplay", x), s.addEventListener("waiting", g), s.addEventListener("playing", F), s.src = e, s.muted = te, s.style.objectFit = "contain";
const v = N.playbackPositions.get(i);
return s.currentTime = v ?? 0, f.appendChild(s), s.play().catch(fe), () => {
s.removeEventListener("canplay", x), s.removeEventListener("waiting", g), s.removeEventListener("playing", F), N.playbackPositions.set(i, s.currentTime);
const C = ge(s);
C && N.capturedFrames.set(i, C), s.parentNode === f && f.removeChild(s), r(!1);
};
}, [o, e, i]), /* @__PURE__ */ k(
const C = M(null), R = ne(), [b] = Q(() => z(!0)), w = M({ onPlaying: g, onWaiting: h, onError: k });
return w.current = { onPlaying: g, onWaiting: h, onError: k }, _e(() => {
if (!t || !C.current) return;
const c = T.getVideo(), u = C.current, y = ee();
b.value = !0, y.push(
Ne(c, {
onReady: () => {
w.current.onPlaying?.();
},
onWaiting: () => {
w.current.onWaiting?.();
},
onPlaying: () => {
b.value = !1;
}
}),
j(c, "error", () => {
c.error && w.current.onError?.();
})
), c.src = n, c.muted = R.muted.value, y.push(Se(R, c)), c.style.objectFit = "contain";
const $ = T.playbackPositions.get(r);
return c.currentTime = $ ?? 0, u.appendChild(c), c.play().catch(() => {
w.current.onError?.();
}), y.push(() => {
T.playbackPositions.set(r, c.currentTime);
const E = Re(c);
E && T.capturedFrames.set(r, E), c.parentNode === u && u.removeChild(c);
}), y.dispose;
}, [t, n, r]), /* @__PURE__ */ e(
"div",
{
ref: u,
ref: C,
className: "rk-lightbox-video-container",
style: {
width: l[0],
height: l[1]
width: o[0],
height: o[1]
},
children: [
(N.capturedFrames.get(i) ?? t) && /* @__PURE__ */ n(
children: /* @__PURE__ */ e(x, { signals: [b], children: () => {
const c = T.capturedFrames.get(r) ?? i;
return c ? /* @__PURE__ */ e(
"img",
{
src: N.capturedFrames.get(i) ?? t,
src: c,
alt: "",
className: `rk-lightbox-video-poster ${!o || c ? "rk-visible" : ""}`,
className: `rk-lightbox-video-poster ${!t || b.value ? "rk-visible" : ""}`,
style: { objectFit: "contain" }
}
),
/* @__PURE__ */ n(
"div",
{
className: `rk-lightbox-video-loader ${a ? "rk-visible" : ""}`
}
)
]
) : null;
} })
}
);
}, He = ({
onClose: n,
activeIndex: i,
count: t,
isFullscreen: o,
onToggleFullscreen: r,
isVideoSlide: g
}) => {
const h = ne();
return O(() => () => {
h.muted.value = !0;
}, []), /* @__PURE__ */ f(p, { children: [
/* @__PURE__ */ f("div", { className: "rk-lightbox-controls-left", children: [
/* @__PURE__ */ e(re, { currentIndex: i, count: t }),
/* @__PURE__ */ e(
oe,
{
isFullscreen: o,
onToggle: r
}
),
g && /* @__PURE__ */ e(x, { signals: [h.muted], children: () => /* @__PURE__ */ e(
De,
{
isMuted: h.muted.value,
onToggle: h.toggle
}
) })
] }),
/* @__PURE__ */ e(te, { onClick: n })
] });
};
function Be(e, t) {
const [o, l] = y(!0), i = p(() => {
l((c) => !c);
}, []), u = ce(
() => e.some((c) => c.type === "video"),
[e]
);
w(() => {
J(o);
}, [o]), w(() => {
t === !1 && (l(!0), J(!0));
}, [t]);
const a = p(
(c, h, s, f) => (c.type ?? "image") !== "video" ? null : /* @__PURE__ */ n(
Me,
function Ke(n) {
const i = ge(
() => n.some((r) => r.type === "video"),
[n]
), t = G(
({
item: r,
index: g,
size: h,
isActive: k,
onReady: C,
onWaiting: R,
onError: b
}) => (r.type ?? "image") !== "video" ? null : /* @__PURE__ */ e(
Ae,
{
src: c.src,
poster: c.poster,
isActive: f,
size: s,
slideKey: `lightbox-${h}`
src: r.src,
poster: r.poster,
isActive: k,
size: h,
slideKey: `lightbox-${g}`,
onPlaying: C,
onWaiting: R,
onError: b
}
),
[]
), r = p(
({
onClose: c,
currentIndex: h,
count: s,
isFullscreen: f,
onToggleFullscreen: x
}) => /* @__PURE__ */ k(q, { children: [
/* @__PURE__ */ k("div", { className: "rk-lightbox-controls-left", children: [
/* @__PURE__ */ n(K, { currentIndex: h, count: s }),
/* @__PURE__ */ n(
ee,
{
isFullscreen: f,
onToggle: x
}
),
u && /* @__PURE__ */ n(Ce, { isMuted: o, onToggle: i })
] }),
/* @__PURE__ */ n(Z, { onClick: c })
] }),
[u, o, i]
), o = G(
(r) => /* @__PURE__ */ e(
He,
{
...r,
isVideoSlide: n[r.activeIndex]?.type === "video"
}
),
[n]
);
return { renderSlide: a, isMuted: o, onToggleMute: i, hasVideo: u, renderControls: r };
return { renderSlide: t, SoundProvider: Fe, hasVideo: i, renderControls: o };
}
export {
Z as CloseButton,
K as Counter,
ee as FullscreenButton,
Oe as LightboxOverlay,
Me as LightboxVideoSlide,
Ce as SoundButton,
ye as useFullscreen,
Be as useVideoSlideRenderer
te as CloseButton,
re as Counter,
oe as FullscreenButton,
Ye as LightboxOverlay,
Ae as LightboxVideoSlide,
De as SoundButton,
Be as lightboxFadeTransition,
$e as lightboxZoomTransition,
Ke as useVideoSlideRenderer
};

@@ -6,4 +6,2 @@ import { default as React } from 'react';

export interface CloseButtonProps {
/** Callback to close the lightbox. */
onClick: () => void;
/** Optional CSS class. Defaults to `"rk-lightbox-close"`. */

@@ -13,2 +11,4 @@ className?: string;

style?: React.CSSProperties;
/** Callback to close the lightbox. */
onClick: () => void;
}

@@ -55,4 +55,2 @@ /**

isFullscreen: boolean;
/** Toggle fullscreen mode. */
onToggle: () => void;
/** Optional CSS class. Defaults to `"rk-lightbox-btn"`. */

@@ -62,2 +60,4 @@ className?: string;

style?: React.CSSProperties;
/** Toggle fullscreen mode. */
onToggle: () => void;
}

@@ -76,4 +76,2 @@ /**

isMuted: boolean;
/** Callback to toggle the muted state. */
onToggle: () => void;
/** Optional CSS class. Defaults to `"rk-lightbox-btn"`. */

@@ -83,2 +81,4 @@ className?: string;

style?: React.CSSProperties;
/** Callback to toggle the muted state. */
onToggle: () => void;
}

@@ -101,6 +101,6 @@ /**

interface LightboxControlsProps {
onClose: () => void;
currentIndex: number;
count: number;
isFullscreen: boolean;
onClose: () => void;
onToggleFullscreen: () => void;

@@ -107,0 +107,0 @@ }

import { MutableRefObject, ReactNode, FC } from 'react';
import { ReelApi, ReelProps } from '@reelkit/react';
import { LightboxControlsRenderProps, NavigationRenderProps, InfoRenderProps } from './types';
import { ReelApi, ReelProps, TransitionTransformFn } from '@reelkit/react';
import { ControlsRenderProps, SlideRenderProps, NavigationRenderProps, InfoRenderProps } from './types';
/**
* Built-in transition aliases for the lightbox.
* Use `transitionFn` for custom {@link TransitionTransformFn} instead.
*/
export type TransitionType = 'slide' | 'fade' | 'flip' | 'zoom-in';
/**
* Data for a single lightbox item (image or video).

@@ -33,10 +38,2 @@ *

/**
* Available CSS transition effects applied when navigating between slides.
*
* - `'slide'` — standard horizontal slide (default)
* - `'fade'` — crossfade between images
* - `'zoom-in'` — zoom in from smaller to normal size
*/
export type TransitionType = 'slide' | 'fade' | 'zoom-in';
/**
* Subset of {@link ReelProps} forwarded to the underlying `Reel` component.

@@ -47,3 +44,3 @@ *

*/
export type ReelProxyProps = Pick<ReelProps, 'transitionDuration' | 'swipeDistanceFactor' | 'loop' | 'useNavKeys' | 'enableWheel' | 'wheelDebounceMs'>;
export type ReelProxyProps = Pick<ReelProps, 'transitionDuration' | 'swipeDistanceFactor' | 'loop' | 'enableNavKeys' | 'enableWheel' | 'wheelDebounceMs'>;
/**

@@ -70,5 +67,5 @@ * Props for the {@link LightboxOverlay} component.

* onClose={handleClose}
* renderControls={({ onClose, currentIndex, count }) => (
* renderControls={({ onClose, activeIndex, count }) => (
* <div>
* <Counter currentIndex={currentIndex} count={count} />
* <Counter currentIndex={activeIndex} count={count} />
* <CloseButton onClick={onClose} />

@@ -91,6 +88,8 @@ * </div>

initialIndex?: number;
/** Callback to close the lightbox. Triggered by close button or Escape key. */
onClose: () => void;
/**
* Transition animation type
* Ref to access the Reel API
*/
apiRef?: MutableRefObject<ReelApi | null>;
/**
* Built-in transition alias.
* @default 'slide'

@@ -100,2 +99,8 @@ */

/**
* Custom transition function. Takes priority over `transition` alias.
*/
transitionFn?: TransitionTransformFn;
/** Callback to close the lightbox. Triggered by close button or Escape key. */
onClose: () => void;
/**
* Callback fired after slide change

@@ -105,9 +110,5 @@ */

/**
* Ref to access the Reel API
*/
apiRef?: MutableRefObject<ReelApi | null>;
/**
* Custom controls. Replaces default close button, counter, and fullscreen toggle.
*/
renderControls?: (props: LightboxControlsRenderProps) => ReactNode;
renderControls?: (props: ControlsRenderProps) => ReactNode;
/**

@@ -124,4 +125,17 @@ * Custom navigation arrows. Replaces default prev/next buttons.

* Custom slide rendering. Return null to fall back to default image slide.
*
* The optional `onReady` / `onWaiting` callbacks let the custom slide
* report its loading state so the overlay can show/hide the spinner.
*/
renderSlide?: (item: LightboxItem, index: number, size: [number, number], isActive: boolean) => ReactNode | null;
renderSlide?: (props: SlideRenderProps) => ReactNode | null;
/** Custom loading indicator. Replaces default spinner. */
renderLoading?: (props: {
item: LightboxItem;
activeIndex: number;
}) => ReactNode;
/** Custom error indicator. Replaces default error icon. */
renderError?: (props: {
item: LightboxItem;
activeIndex: number;
}) => ReactNode;
}

@@ -128,0 +142,0 @@ /**

@@ -16,10 +16,10 @@ import { default as React } from 'react';

slideKey: string;
/** Called when the video starts playing (buffering complete). */
onPlaying?: () => void;
/** Called when the video stalls (buffering mid-playback). */
onWaiting?: () => void;
/** Called when the video fails to load or play. */
onError?: () => void;
}
/**
* Set the muted state on the shared video element directly.
* Called by `useVideoSlideRenderer` — no re-render needed.
* @internal
*/
export declare const setLightboxVideoMuted: (muted: boolean) => void;
/**
* Lightweight video slide component for the lightbox.

@@ -26,0 +26,0 @@ *

@@ -5,7 +5,7 @@ import { LightboxItem } from './LightboxOverlay';

*/
export interface LightboxControlsRenderProps {
/** Callback to close the lightbox. */
onClose: () => void;
export interface ControlsRenderProps {
/** The currently active lightbox item. */
item: LightboxItem;
/** Zero-based index of the currently active slide. */
currentIndex: number;
activeIndex: number;
/** Total number of images. */

@@ -15,2 +15,4 @@ count: number;

isFullscreen: boolean;
/** Callback to close the lightbox. */
onClose: () => void;
/** Toggle fullscreen mode. */

@@ -23,2 +25,8 @@ onToggleFullscreen: () => void;

export interface NavigationRenderProps {
/** The currently active lightbox item. */
item: LightboxItem;
/** Zero-based index of the currently active slide. */
activeIndex: number;
/** Total number of slides. */
count: number;
/** Navigate to the previous slide. */

@@ -28,12 +36,27 @@ onPrev: () => void;

onNext: () => void;
/** Zero-based index of the currently active slide. */
activeIndex: number;
/** Total number of slides. */
count: number;
}
/**
* Props passed to the {@link LightboxOverlay} `renderSlide` callback.
*/
export interface SlideRenderProps {
/** The lightbox item for this slide. */
item: LightboxItem;
/** Zero-based index of this slide. */
index: number;
/** `[width, height]` dimensions of the lightbox in pixels. */
size: [number, number];
/** Whether this is the currently active slide. */
isActive: boolean;
/** Notify that the slide content is ready (loaded). Clears the spinner. */
onReady: () => void;
/** Notify that the slide content is loading/waiting. Shows the spinner. */
onWaiting: () => void;
/** Notify that the slide content failed to load. Shows error icon. */
onError: () => void;
}
/**
* Props passed to the {@link LightboxOverlay} `renderInfo` callback.
*/
export interface InfoRenderProps {
/** The current lightbox item data. */
/** The currently active lightbox item. */
item: LightboxItem;

@@ -40,0 +63,0 @@ /** Zero-based index of the current item. */

@@ -1,4 +0,4 @@

import { ReactNode } from 'react';
import { ReactNode, FC } from 'react';
import { LightboxItem } from './LightboxOverlay';
import { LightboxControlsRenderProps } from './types';
import { ControlsRenderProps, SlideRenderProps } from './types';
/**

@@ -9,2 +9,18 @@ * Return value of {@link useVideoSlideRenderer}.

/**
* Wrap `LightboxOverlay` in this to provide sound context.
* Required for video mute/unmute to work.
*
* @example
* ```tsx
* <SoundProvider>
* <LightboxOverlay ... />
* </SoundProvider>
* ```
*/
SoundProvider: FC<{
children: ReactNode;
}>;
/** `true` when at least one item has `type: 'video'`. */
hasVideo: boolean;
/**
* Pass this to `LightboxOverlay`'s `renderSlide` prop.

@@ -14,9 +30,3 @@ * Returns a `LightboxVideoSlide` for video items, or `null` to fall

*/
renderSlide: (item: LightboxItem, index: number, size: [number, number], isActive: boolean) => ReactNode | null;
/** Whether audio is currently muted. Defaults to `true`. */
isMuted: boolean;
/** Toggle the muted state. */
onToggleMute: () => void;
/** `true` when at least one item has `type: 'video'`. */
hasVideo: boolean;
renderSlide: (props: SlideRenderProps) => ReactNode | null;
/**

@@ -27,25 +37,4 @@ * Ready-to-use `renderControls` callback with Counter, FullscreenButton,

*/
renderControls: (props: LightboxControlsRenderProps) => ReactNode;
renderControls: (props: ControlsRenderProps) => ReactNode;
}
/**
* Convenience hook that manages mute state and returns a ready-to-use
* `renderSlide` function for the lightbox.
*
* Video support is fully opt-in — import this hook and wire it into
* `LightboxOverlay` to enable video slides. Image-only usage pays zero
* extra bundle cost.
*
* @example
* ```tsx
* import { LightboxOverlay, useVideoSlideRenderer } from '@reelkit/react-lightbox';
*
* const { renderSlide, renderControls } = useVideoSlideRenderer(items, isOpen);
*
* <LightboxOverlay
* images={items}
* renderSlide={renderSlide}
* renderControls={renderControls}
* />
* ```
*/
export declare function useVideoSlideRenderer(items: LightboxItem[], isOpen?: boolean): UseVideoSlideRendererResult;
export declare function useVideoSlideRenderer(items: LightboxItem[]): UseVideoSlideRendererResult;
{
"name": "@reelkit/react-lightbox",
"version": "0.2.1",
"version": "0.3.0",
"type": "module",

@@ -56,7 +56,7 @@ "sideEffects": [

"peerDependencies": {
"@reelkit/react": "*",
"react": ">=18",
"react-dom": ">=18",
"@reelkit/react": "^0.3.0",
"react": ">=18.0.0",
"react-dom": ">=18.0.0",
"lucide-react": ">=0.400.0"
}
}

@@ -5,7 +5,8 @@ # @reelkit/react-lightbox

<a href="https://www.npmjs.com/package/@reelkit/react-lightbox"><img src="https://img.shields.io/npm/v/@reelkit/react-lightbox?color=6366f1&label=npm" alt="npm" /></a>
<img src="https://img.shields.io/badge/gzip-3.2%20kB-6366f1" alt="Bundle size" />
<img src="https://img.shields.io/badge/gzip-3.1%20kB-6366f1" alt="Bundle size" />
<img src="https://img.shields.io/badge/coverage-95%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>
Image gallery lightbox for React — opens full-screen with swipe navigation, keyboard controls, and transition effects. Everything is replaceable via render props if the defaults don't fit. ~3.2 kB gzip.
Image gallery lightbox for React — opens full-screen with swipe navigation, keyboard controls, and transition effects. Everything is replaceable via render props if the defaults don't fit. ~3.1 kB gzip.

@@ -97,3 +98,3 @@ ## Installation

| `loop` | `boolean` | `false` | Enable infinite loop |
| `useNavKeys` | `boolean` | `true` | Enable keyboard navigation |
| `enableNavKeys` | `boolean` | `true` | Enable keyboard navigation |
| `enableWheel` | `boolean` | `true` | Enable mouse wheel |

@@ -100,0 +101,0 @@ | `wheelDebounceMs` | `number` | `200` | Wheel debounce (ms) |

import { ReactNode, FC } from 'react';
/** Props for the {@link SwipeToClose} wrapper component. */
export interface SwipeToCloseProps {
/** When `true`, vertical swipe-to-close gesture handling is active. Typically `true` on touch devices. */
enabled: boolean;
/** Callback invoked when the user completes a swipe-up gesture that exceeds the dismiss threshold. */
onClose: () => void;
/** Content to wrap — usually the `Reel` slider element. */
children: ReactNode;
/** Optional CSS class forwarded to the outer `<div>`. */
className?: string;
}
/**
* Wraps its children in a touch-aware container that can be swiped
* upward to dismiss the lightbox.
*
* Uses a {@link GestureController} from `@reelkit/core` to track
* vertical drag gestures. While dragging, the container translates
* upward and fades out. If the drag distance exceeds 20 % of the
* viewport height the `onClose` callback fires; otherwise the
* container animates back to its original position.
*
* Rendering is optimised via `Signal`-backed `Observe`
* so that only the inline styles re-render — not the entire subtree.
*
* @internal Used by {@link LightboxContent}. Not exported from the package.
*/
export declare const SwipeToClose: FC<SwipeToCloseProps>;
import { MutableRefObject } from 'react';
/**
* 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]` — `boolean` indicating whether the element is currently fullscreen.
* - `[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: boolean,
requestFullscreen: () => void,
exitFullscreen: () => void,
toggleFullscreen: () => void
];
/**
* React hook for managing the Fullscreen API with cross-browser support.
*
* Wraps the standard `requestFullscreen` / `exitFullscreen` methods
* together with their WebKit, Mozilla, and MS vendor-prefixed
* variants. Automatically listens for `fullscreenchange` events
* (all prefixes) and exits fullscreen when the component unmounts.
*
* @typeParam E - The HTML element type (inferred from `props.ref`).
*
* @example
* ```tsx
* const ref = useRef<HTMLDivElement>(null);
* const [isFs, requestFs, exitFs, toggleFs] = useFullscreen({ ref });
* ```
*/
export declare const useFullscreen: <E extends HTMLElement>(props: UseFullscreenProps<E>) => UseFullscreenResult;