yet-another-react-lightbox
Version:
Modern React lightbox component
1,122 lines (1,093 loc) • 73.4 kB
JavaScript
'use client';
import * as React from 'react';
import { IMAGE_FIT_COVER, IMAGE_FIT_CONTAIN, ACTION_CLOSE, MODULE_CONTROLLER, UNKNOWN_ACTION_TYPE, ELEMENT_BUTTON, ELEMENT_ICON, EVENT_ON_WHEEL, EVENT_ON_KEY_UP, EVENT_ON_KEY_DOWN, EVENT_ON_POINTER_CANCEL, EVENT_ON_POINTER_LEAVE, EVENT_ON_POINTER_UP, EVENT_ON_POINTER_MOVE, EVENT_ON_POINTER_DOWN, SLIDE_STATUS_LOADING, activeSlideStatus, SLIDE_STATUS_COMPLETE, SLIDE_STATUS_ERROR, SLIDE_STATUS_PLACEHOLDER, CLASS_SLIDE, CLASS_SLIDE_WRAPPER, ACTION_PREV, ACTION_NEXT, ACTION_SWIPE, MODULE_PORTAL, CLASS_FLEX_CENTER, MODULE_CAROUSEL, VK_ARROW_RIGHT, VK_ARROW_LEFT, VK_ESCAPE, MODULE_NAVIGATION, CLASS_NO_SCROLL, CLASS_NO_SCROLL_PADDING, MODULE_NO_SCROLL, MODULE_ROOT, MODULE_TOOLBAR } from './types.js';
import { createPortal } from 'react-dom';
export { ACTIVE_SLIDE_COMPLETE, ACTIVE_SLIDE_ERROR, ACTIVE_SLIDE_LOADING, ACTIVE_SLIDE_PLAYING, CLASS_FULLSIZE, CLASS_SLIDE_WRAPPER_INTERACTIVE, PLUGIN_CAPTIONS, PLUGIN_COUNTER, PLUGIN_DOWNLOAD, PLUGIN_FULLSCREEN, PLUGIN_INLINE, PLUGIN_SHARE, PLUGIN_SLIDESHOW, PLUGIN_THUMBNAILS, PLUGIN_ZOOM, SLIDE_STATUS_PLAYING } from './types.js';
const cssPrefix$3 = "yarl__";
function clsx(...classes) {
return [...classes].filter(Boolean).join(" ");
}
function cssClass(name) {
return `${cssPrefix$3}${name}`;
}
function cssVar(name) {
return `--${cssPrefix$3}${name}`;
}
function composePrefix(base, prefix) {
return `${base}${prefix ? `_${prefix}` : ""}`;
}
function makeComposePrefix(base) {
return (prefix) => composePrefix(base, prefix);
}
function label(labels, defaultLabel) {
var _a;
return (_a = labels === null || labels === void 0 ? void 0 : labels[defaultLabel]) !== null && _a !== void 0 ? _a : defaultLabel;
}
function cleanup(...cleaners) {
return () => {
cleaners.forEach((cleaner) => {
cleaner();
});
};
}
function makeUseContext(name, contextName, context) {
return () => {
const ctx = React.useContext(context);
if (!ctx) {
throw new Error(`${name} must be used within a ${contextName}.Provider`);
}
return ctx;
};
}
function hasWindow() {
return typeof window !== "undefined";
}
function round(value, decimals = 0) {
const factor = 10 ** decimals;
return Math.round((value + Number.EPSILON) * factor) / factor;
}
function isImageSlide(slide) {
return slide.type === undefined || slide.type === "image";
}
function isImageFitCover(image, imageFit) {
return image.imageFit === IMAGE_FIT_COVER || (image.imageFit !== IMAGE_FIT_CONTAIN && imageFit === IMAGE_FIT_COVER);
}
function parseInt(value) {
return typeof value === "string" ? Number.parseInt(value, 10) : value;
}
function parseLengthPercentage(input) {
if (typeof input === "number") {
return { pixel: input };
}
if (typeof input === "string") {
const value = parseInt(input);
return input.endsWith("%") ? { percent: value } : { pixel: value };
}
return { pixel: 0 };
}
function computeSlideRect(containerRect, padding) {
const paddingValue = parseLengthPercentage(padding);
const paddingPixels = paddingValue.percent !== undefined ? (containerRect.width / 100) * paddingValue.percent : paddingValue.pixel;
return {
width: Math.max(containerRect.width - 2 * paddingPixels, 0),
height: Math.max(containerRect.height - 2 * paddingPixels, 0),
};
}
function devicePixelRatio() {
return (hasWindow() ? window === null || window === void 0 ? void 0 : window.devicePixelRatio : undefined) || 1;
}
function getSlideIndex(index, slidesCount) {
return slidesCount > 0 ? ((index % slidesCount) + slidesCount) % slidesCount : 0;
}
function hasSlides(slides) {
return slides.length > 0;
}
function getSlide(slides, index) {
return slides[getSlideIndex(index, slides.length)];
}
function getSlideIfPresent(slides, index) {
return hasSlides(slides) ? getSlide(slides, index) : undefined;
}
function getSlideKey(slide) {
return isImageSlide(slide) ? slide.src : undefined;
}
function addToolbarButton(toolbar, key, button) {
if (!button)
return toolbar;
const { buttons, ...restToolbar } = toolbar;
const index = buttons.findIndex((item) => item === key);
const buttonWithKey = React.isValidElement(button) ? React.cloneElement(button, { key }, null) : button;
if (index >= 0) {
const result = [...buttons];
result.splice(index, 1, buttonWithKey);
return { buttons: result, ...restToolbar };
}
return { buttons: [buttonWithKey, ...buttons], ...restToolbar };
}
function stopNavigationEventsPropagation() {
const stopPropagation = (event) => {
event.stopPropagation();
};
return { onPointerDown: stopPropagation, onKeyDown: stopPropagation, onWheel: stopPropagation };
}
function calculatePreload(carousel, slides, minimum = 0) {
return Math.min(carousel.preload, Math.max(carousel.finite ? slides.length - 1 : Math.floor(slides.length / 2), minimum));
}
const isReact19 = Number(React.version.split(".")[0]) >= 19;
function makeInertWhen(condition) {
const legacyValue = condition ? "" : undefined;
return { inert: isReact19 ? condition : legacyValue };
}
function reflow(node) {
node.scrollTop;
}
const LightboxDefaultProps = {
open: false,
close: () => { },
index: 0,
slides: [],
render: {},
plugins: [],
toolbar: { buttons: [ACTION_CLOSE] },
labels: {},
animation: {
fade: 250,
swipe: 500,
easing: {
fade: "ease",
swipe: "ease-out",
navigation: "ease-in-out",
},
},
carousel: {
finite: false,
preload: 2,
padding: "16px",
spacing: "30%",
imageFit: IMAGE_FIT_CONTAIN,
imageProps: {},
},
controller: {
ref: null,
focus: true,
aria: false,
touchAction: "none",
closeOnPullUp: false,
closeOnPullDown: false,
closeOnBackdropClick: false,
preventDefaultWheelX: true,
preventDefaultWheelY: false,
disableSwipeNavigation: false,
},
portal: {},
noScroll: {
disabled: false,
},
on: {},
styles: {},
className: "",
};
function createModule(name, component) {
return { name, component };
}
function createNode(module, children) {
return { module, children };
}
function traverseNode(node, target, apply) {
if (node.module.name === target) {
return apply(node);
}
if (node.children) {
return [
createNode(node.module, node.children.flatMap((n) => { var _a; return (_a = traverseNode(n, target, apply)) !== null && _a !== void 0 ? _a : []; })),
];
}
return [node];
}
function traverse(nodes, target, apply) {
return nodes.flatMap((node) => { var _a; return (_a = traverseNode(node, target, apply)) !== null && _a !== void 0 ? _a : []; });
}
function withPlugins(root, plugins = [], augmentations = []) {
let config = root;
const contains = (target) => {
const nodes = [...config];
while (nodes.length > 0) {
const node = nodes.pop();
if ((node === null || node === void 0 ? void 0 : node.module.name) === target)
return true;
if (node === null || node === void 0 ? void 0 : node.children)
nodes.push(...node.children);
}
return false;
};
const addParent = (target, module) => {
if (target === "") {
config = [createNode(module, config)];
return;
}
config = traverse(config, target, (node) => [createNode(module, [node])]);
};
const append = (target, module) => {
config = traverse(config, target, (node) => [createNode(node.module, [createNode(module, node.children)])]);
};
const addChild = (target, module, precede) => {
config = traverse(config, target, (node) => {
var _a;
return [
createNode(node.module, [
...(precede ? [createNode(module)] : []),
...((_a = node.children) !== null && _a !== void 0 ? _a : []),
...(!precede ? [createNode(module)] : []),
]),
];
});
};
const addSibling = (target, module, precede) => {
config = traverse(config, target, (node) => [
...(precede ? [createNode(module)] : []),
node,
...(!precede ? [createNode(module)] : []),
]);
};
const addModule = (module) => {
append(MODULE_CONTROLLER, module);
};
const replace = (target, module) => {
config = traverse(config, target, (node) => [createNode(module, node.children)]);
};
const remove = (target) => {
config = traverse(config, target, (node) => node.children);
};
const augment = (augmentation) => {
augmentations.push(augmentation);
};
plugins.forEach((plugin) => {
plugin({
contains,
addParent,
append,
addChild,
addSibling,
addModule,
replace,
remove,
augment,
});
});
return {
config,
augmentation: (props) => augmentations.reduce((acc, augmentation) => augmentation(acc), props),
};
}
const DocumentContext = React.createContext(null);
const useDocumentContext = makeUseContext("useDocument", "DocumentContext", DocumentContext);
function DocumentContextProvider({ nodeRef, children }) {
const context = React.useMemo(() => {
const getOwnerDocument = (node) => { var _a; return ((_a = (node || nodeRef.current)) === null || _a === void 0 ? void 0 : _a.ownerDocument) || document; };
const getOwnerWindow = (node) => { var _a; return ((_a = getOwnerDocument(node)) === null || _a === void 0 ? void 0 : _a.defaultView) || window; };
return { getOwnerDocument, getOwnerWindow };
}, [nodeRef]);
return React.createElement(DocumentContext.Provider, { value: context }, children);
}
const EventsContext = React.createContext(null);
const useEvents = makeUseContext("useEvents", "EventsContext", EventsContext);
function EventsProvider({ children }) {
const [subscriptions] = React.useState({});
React.useEffect(() => () => {
Object.keys(subscriptions).forEach((topic) => delete subscriptions[topic]);
}, [subscriptions]);
const context = React.useMemo(() => {
const unsubscribe = (topic, callback) => {
var _a;
(_a = subscriptions[topic]) === null || _a === void 0 ? void 0 : _a.splice(0, subscriptions[topic].length, ...subscriptions[topic].filter((cb) => cb !== callback));
};
const subscribe = (topic, callback) => {
if (!subscriptions[topic]) {
subscriptions[topic] = [];
}
subscriptions[topic].push(callback);
return () => unsubscribe(topic, callback);
};
const publish = (...[topic, event]) => {
var _a;
(_a = subscriptions[topic]) === null || _a === void 0 ? void 0 : _a.forEach((callback) => callback(event));
};
return { publish, subscribe, unsubscribe };
}, [subscriptions]);
return React.createElement(EventsContext.Provider, { value: context }, children);
}
const LightboxPropsContext = React.createContext(null);
const useLightboxProps = makeUseContext("useLightboxProps", "LightboxPropsContext", LightboxPropsContext);
function LightboxPropsProvider({ children, ...props }) {
return React.createElement(LightboxPropsContext.Provider, { value: props }, children);
}
const LightboxStateContext = React.createContext(null);
const useLightboxState = makeUseContext("useLightboxState", "LightboxStateContext", LightboxStateContext);
const LightboxDispatchContext = React.createContext(null);
const useLightboxDispatch = makeUseContext("useLightboxDispatch", "LightboxDispatchContext", LightboxDispatchContext);
function reducer(state, action) {
switch (action.type) {
case "swipe": {
const { slides } = state;
const increment = (action === null || action === void 0 ? void 0 : action.increment) || 0;
const globalIndex = state.globalIndex + increment;
const currentIndex = getSlideIndex(globalIndex, slides.length);
const currentSlide = getSlideIfPresent(slides, currentIndex);
const animation = increment || action.duration !== undefined
? {
increment,
duration: action.duration,
easing: action.easing,
}
: undefined;
return { slides, currentIndex, globalIndex, currentSlide, animation };
}
case "update":
if (action.slides !== state.slides || action.index !== state.currentIndex) {
return {
slides: action.slides,
currentIndex: action.index,
globalIndex: action.index,
currentSlide: getSlideIfPresent(action.slides, action.index),
};
}
return state;
default:
throw new Error(UNKNOWN_ACTION_TYPE);
}
}
function LightboxStateProvider({ slides, index, children }) {
const [state, dispatch] = React.useReducer(reducer, {
slides,
currentIndex: index,
globalIndex: index,
currentSlide: getSlideIfPresent(slides, index),
});
React.useEffect(() => {
dispatch({ type: "update", slides, index });
}, [slides, index]);
const context = React.useMemo(() => ({ ...state, state, dispatch }), [state, dispatch]);
return (React.createElement(LightboxDispatchContext.Provider, { value: dispatch },
React.createElement(LightboxStateContext.Provider, { value: context }, children)));
}
const TimeoutsContext = React.createContext(null);
const useTimeouts = makeUseContext("useTimeouts", "TimeoutsContext", TimeoutsContext);
function TimeoutsProvider({ children }) {
const [timeouts] = React.useState([]);
React.useEffect(() => () => {
timeouts.forEach((tid) => window.clearTimeout(tid));
timeouts.splice(0, timeouts.length);
}, [timeouts]);
const context = React.useMemo(() => {
const removeTimeout = (id) => {
timeouts.splice(0, timeouts.length, ...timeouts.filter((tid) => tid !== id));
};
const setTimeout = (fn, delay) => {
const id = window.setTimeout(() => {
removeTimeout(id);
fn();
}, delay);
timeouts.push(id);
return id;
};
const clearTimeout = (id) => {
if (id !== undefined) {
removeTimeout(id);
window.clearTimeout(id);
}
};
return { setTimeout, clearTimeout };
}, [timeouts]);
return React.createElement(TimeoutsContext.Provider, { value: context }, children);
}
const IconButton = React.forwardRef(function IconButton({ label: label$1, className, icon: Icon, renderIcon, onClick, style, ...rest }, ref) {
const { styles, labels } = useLightboxProps();
const buttonLabel = label(labels, label$1);
return (React.createElement("button", { ref: ref, type: "button", title: buttonLabel, "aria-label": buttonLabel, className: clsx(cssClass(ELEMENT_BUTTON), className), onClick: onClick, style: { ...style, ...styles.button }, ...rest }, renderIcon ? renderIcon() : React.createElement(Icon, { className: cssClass(ELEMENT_ICON), style: styles.icon })));
});
function svgIcon(name, children) {
const icon = (props) => (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", width: "24", height: "24", "aria-hidden": "true", focusable: "false", ...props }, children));
icon.displayName = name;
return icon;
}
function createIcon(name, glyph) {
return svgIcon(name, React.createElement("g", { fill: "currentColor" },
React.createElement("path", { d: "M0 0h24v24H0z", fill: "none" }),
glyph));
}
function createIconDisabled(name, glyph) {
return svgIcon(name, React.createElement(React.Fragment, null,
React.createElement("defs", null,
React.createElement("mask", { id: "strike" },
React.createElement("path", { d: "M0 0h24v24H0z", fill: "white" }),
React.createElement("path", { d: "M0 0L24 24", stroke: "black", strokeWidth: 4 }))),
React.createElement("path", { d: "M0.70707 2.121320L21.878680 23.292883", stroke: "currentColor", strokeWidth: 2 }),
React.createElement("g", { fill: "currentColor", mask: "url(#strike)" },
React.createElement("path", { d: "M0 0h24v24H0z", fill: "none" }),
glyph)));
}
const CloseIcon = createIcon("Close", React.createElement("path", { d: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" }));
const PreviousIcon = createIcon("Previous", React.createElement("path", { d: "M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" }));
const NextIcon = createIcon("Next", React.createElement("path", { d: "M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" }));
const LoadingIcon = createIcon("Loading", React.createElement(React.Fragment, null, Array.from({ length: 8 }).map((_, index, array) => (React.createElement("line", { key: index, x1: "12", y1: "6.5", x2: "12", y2: "1.8", strokeLinecap: "round", strokeWidth: "2.6", stroke: "currentColor", strokeOpacity: (1 / array.length) * (index + 1), transform: `rotate(${(360 / array.length) * index}, 12, 12)` })))));
const ErrorIcon = createIcon("Error", React.createElement("path", { d: "M21.9,21.9l-8.49-8.49l0,0L3.59,3.59l0,0L2.1,2.1L0.69,3.51L3,5.83V19c0,1.1,0.9,2,2,2h13.17l2.31,2.31L21.9,21.9z M5,18 l3.5-4.5l2.5,3.01L12.17,15l3,3H5z M21,18.17L5.83,3H19c1.1,0,2,0.9,2,2V18.17z" }));
const useLayoutEffect = hasWindow() ? React.useLayoutEffect : React.useEffect;
function useMotionPreference() {
const [reduceMotion, setReduceMotion] = React.useState(false);
React.useEffect(() => {
var _a, _b;
const mediaQuery = (_a = window.matchMedia) === null || _a === void 0 ? void 0 : _a.call(window, "(prefers-reduced-motion: reduce)");
setReduceMotion(mediaQuery === null || mediaQuery === void 0 ? void 0 : mediaQuery.matches);
const listener = (event) => setReduceMotion(event.matches);
(_b = mediaQuery === null || mediaQuery === void 0 ? void 0 : mediaQuery.addEventListener) === null || _b === void 0 ? void 0 : _b.call(mediaQuery, "change", listener);
return () => { var _a; return (_a = mediaQuery === null || mediaQuery === void 0 ? void 0 : mediaQuery.removeEventListener) === null || _a === void 0 ? void 0 : _a.call(mediaQuery, "change", listener); };
}, []);
return reduceMotion;
}
function currentTransformation(node) {
let x = 0;
let y = 0;
let z = 0;
const matrix = window.getComputedStyle(node).transform;
const matcher = matrix.match(/matrix.*\((.+)\)/);
if (matcher) {
const values = matcher[1].split(",").map(parseInt);
if (values.length === 6) {
x = values[4];
y = values[5];
}
else if (values.length === 16) {
x = values[12];
y = values[13];
z = values[14];
}
}
return { x, y, z };
}
function useAnimation(nodeRef, computeAnimation) {
const snapshot = React.useRef(undefined);
const animation = React.useRef(undefined);
const reduceMotion = useMotionPreference();
useLayoutEffect(() => {
var _a, _b, _c;
if (nodeRef.current && snapshot.current !== undefined && !reduceMotion) {
const { keyframes, duration, easing, onfinish } = computeAnimation(snapshot.current, nodeRef.current.getBoundingClientRect(), currentTransformation(nodeRef.current)) || {};
if (keyframes && duration) {
(_a = animation.current) === null || _a === void 0 ? void 0 : _a.cancel();
animation.current = undefined;
try {
animation.current = (_c = (_b = nodeRef.current).animate) === null || _c === void 0 ? void 0 : _c.call(_b, keyframes, { duration, easing });
}
catch (err) {
console.error(err);
}
if (animation.current) {
animation.current.onfinish = () => {
animation.current = undefined;
onfinish === null || onfinish === void 0 ? void 0 : onfinish();
};
}
}
}
snapshot.current = undefined;
});
return {
prepareAnimation: (currentSnapshot) => {
snapshot.current = currentSnapshot;
},
isAnimationPlaying: () => { var _a; return ((_a = animation.current) === null || _a === void 0 ? void 0 : _a.playState) === "running"; },
};
}
function useContainerRect() {
const containerRef = React.useRef(null);
const observerRef = React.useRef(undefined);
const [containerRect, setContainerRect] = React.useState();
const setContainerRef = React.useCallback((node) => {
containerRef.current = node;
if (observerRef.current) {
observerRef.current.disconnect();
observerRef.current = undefined;
}
const updateContainerRect = () => {
if (node) {
const styles = window.getComputedStyle(node);
const parse = (value) => parseFloat(value) || 0;
setContainerRect({
width: Math.round(node.clientWidth - parse(styles.paddingLeft) - parse(styles.paddingRight)),
height: Math.round(node.clientHeight - parse(styles.paddingTop) - parse(styles.paddingBottom)),
});
}
else {
setContainerRect(undefined);
}
};
updateContainerRect();
if (node && typeof ResizeObserver !== "undefined") {
observerRef.current = new ResizeObserver(updateContainerRect);
observerRef.current.observe(node);
}
}, []);
return { setContainerRef, containerRef, containerRect };
}
function useDelay() {
const timeoutId = React.useRef(undefined);
const { setTimeout, clearTimeout } = useTimeouts();
return React.useCallback((callback, delay) => {
clearTimeout(timeoutId.current);
timeoutId.current = setTimeout(callback, delay > 0 ? delay : 0);
}, [setTimeout, clearTimeout]);
}
function useEventCallback(fn) {
const ref = React.useRef(fn);
useLayoutEffect(() => {
ref.current = fn;
});
return React.useCallback((...args) => { var _a; return (_a = ref.current) === null || _a === void 0 ? void 0 : _a.call(ref, ...args); }, []);
}
function setRef(ref, value) {
if (typeof ref === "function") {
ref(value);
}
else if (ref) {
ref.current = value;
}
}
function useForkRef(refA, refB) {
return React.useMemo(() => refA == null && refB == null
? null
: (refValue) => {
setRef(refA, refValue);
setRef(refB, refValue);
}, [refA, refB]);
}
function useLoseFocus(focus, disabled = false) {
const focused = React.useRef(false);
useLayoutEffect(() => {
if (disabled && focused.current) {
focused.current = false;
focus();
}
}, [disabled, focus]);
const onFocus = React.useCallback(() => {
focused.current = true;
}, []);
const onBlur = React.useCallback(() => {
focused.current = false;
}, []);
return { onFocus, onBlur };
}
function useRTL() {
const [isRTL, setIsRTL] = React.useState(false);
useLayoutEffect(() => {
setIsRTL(window.getComputedStyle(window.document.documentElement).direction === "rtl");
}, []);
return isRTL;
}
function useSensors() {
const [subscribers] = React.useState({});
const notifySubscribers = React.useCallback((type, event) => {
var _a;
(_a = subscribers[type]) === null || _a === void 0 ? void 0 : _a.forEach((listener) => {
if (!event.isPropagationStopped())
listener(event);
});
}, [subscribers]);
const registerSensors = React.useMemo(() => ({
onPointerDown: (event) => notifySubscribers(EVENT_ON_POINTER_DOWN, event),
onPointerMove: (event) => notifySubscribers(EVENT_ON_POINTER_MOVE, event),
onPointerUp: (event) => notifySubscribers(EVENT_ON_POINTER_UP, event),
onPointerLeave: (event) => notifySubscribers(EVENT_ON_POINTER_LEAVE, event),
onPointerCancel: (event) => notifySubscribers(EVENT_ON_POINTER_CANCEL, event),
onKeyDown: (event) => notifySubscribers(EVENT_ON_KEY_DOWN, event),
onKeyUp: (event) => notifySubscribers(EVENT_ON_KEY_UP, event),
onWheel: (event) => notifySubscribers(EVENT_ON_WHEEL, event),
}), [notifySubscribers]);
const subscribeSensors = React.useCallback((type, callback) => {
if (!subscribers[type]) {
subscribers[type] = [];
}
subscribers[type].unshift(callback);
return () => {
const listeners = subscribers[type];
if (listeners) {
listeners.splice(0, listeners.length, ...listeners.filter((el) => el !== callback));
}
};
}, [subscribers]);
return { registerSensors, subscribeSensors };
}
function useThrottle(callback, delay) {
const lastCallbackTime = React.useRef(0);
const delayCallback = useDelay();
const executeCallback = useEventCallback((...args) => {
lastCallbackTime.current = Date.now();
callback(args);
});
return React.useCallback((...args) => {
delayCallback(() => {
executeCallback(args);
}, delay - (Date.now() - lastCallbackTime.current));
}, [delay, executeCallback, delayCallback]);
}
const slidePrefix = makeComposePrefix("slide");
const slideImagePrefix = makeComposePrefix("slide_image");
function ImageSlide({ slide: image, offset, render, rect, imageFit, imageProps, onClick, onLoad, onError, style, }) {
var _a, _b, _c, _d, _e, _f, _g;
const [status, setStatus] = React.useState(SLIDE_STATUS_LOADING);
const { publish } = useEvents();
const { setTimeout } = useTimeouts();
const imageRef = React.useRef(null);
React.useEffect(() => {
if (offset === 0) {
publish(activeSlideStatus(status));
}
}, [offset, status, publish]);
const handleLoading = useEventCallback((img) => {
("decode" in img ? img.decode() : Promise.resolve())
.catch(() => { })
.then(() => {
if (!img.parentNode) {
return;
}
setStatus(SLIDE_STATUS_COMPLETE);
setTimeout(() => {
onLoad === null || onLoad === void 0 ? void 0 : onLoad(img);
}, 0);
});
});
const setImageRef = React.useCallback((img) => {
imageRef.current = img;
if (img === null || img === void 0 ? void 0 : img.complete) {
handleLoading(img);
}
}, [handleLoading]);
const handleOnLoad = React.useCallback((event) => {
handleLoading(event.currentTarget);
}, [handleLoading]);
const handleOnError = useEventCallback(() => {
setStatus(SLIDE_STATUS_ERROR);
onError === null || onError === void 0 ? void 0 : onError();
});
const cover = isImageFitCover(image, imageFit);
const nonInfinite = (value, fallback) => (Number.isFinite(value) ? value : fallback);
const maxWidth = nonInfinite(Math.max(...((_b = (_a = image.srcSet) === null || _a === void 0 ? void 0 : _a.map((x) => x.width)) !== null && _b !== void 0 ? _b : []).concat(image.width ? [image.width] : []).filter(Boolean)), ((_c = imageRef.current) === null || _c === void 0 ? void 0 : _c.naturalWidth) || 0);
const maxHeight = nonInfinite(Math.max(...((_e = (_d = image.srcSet) === null || _d === void 0 ? void 0 : _d.map((x) => x.height)) !== null && _e !== void 0 ? _e : []).concat(image.height ? [image.height] : []).filter(Boolean)), ((_f = imageRef.current) === null || _f === void 0 ? void 0 : _f.naturalHeight) || 0);
const defaultStyle = maxWidth && maxHeight
? {
maxWidth: `min(${maxWidth}px, 100%)`,
maxHeight: `min(${maxHeight}px, 100%)`,
}
: {
maxWidth: "100%",
maxHeight: "100%",
};
const srcSet = (_g = image.srcSet) === null || _g === void 0 ? void 0 : _g.sort((a, b) => a.width - b.width).map((item) => `${item.src} ${item.width}w`).join(", ");
const estimateActualWidth = () => rect && !cover && image.width && image.height ? (rect.height / image.height) * image.width : Number.MAX_VALUE;
const sizes = srcSet && rect && hasWindow() ? `${Math.round(Math.min(estimateActualWidth(), rect.width))}px` : undefined;
const { style: imagePropsStyle, className: imagePropsClassName, ...restImageProps } = imageProps || {};
return (React.createElement(React.Fragment, null,
React.createElement("img", { ref: setImageRef, onLoad: handleOnLoad, onError: handleOnError, onClick: onClick, draggable: false, className: clsx(cssClass(slideImagePrefix()), cover && cssClass(slideImagePrefix("cover")), status !== SLIDE_STATUS_COMPLETE && cssClass(slideImagePrefix("loading")), imagePropsClassName), style: { ...defaultStyle, ...style, ...imagePropsStyle }, ...restImageProps, alt: image.alt, sizes: sizes, srcSet: srcSet, src: image.src }),
status !== SLIDE_STATUS_COMPLETE && (React.createElement("div", { className: cssClass(slidePrefix(SLIDE_STATUS_PLACEHOLDER)) },
status === SLIDE_STATUS_LOADING &&
((render === null || render === void 0 ? void 0 : render.iconLoading) ? (render.iconLoading()) : (React.createElement(LoadingIcon, { className: clsx(cssClass(ELEMENT_ICON), cssClass(slidePrefix(SLIDE_STATUS_LOADING))) }))),
status === SLIDE_STATUS_ERROR &&
((render === null || render === void 0 ? void 0 : render.iconError) ? (render.iconError()) : (React.createElement(ErrorIcon, { className: clsx(cssClass(ELEMENT_ICON), cssClass(slidePrefix(SLIDE_STATUS_ERROR))) })))))));
}
const LightboxRoot = React.forwardRef(function LightboxRoot({ className, children, ...rest }, ref) {
const nodeRef = React.useRef(null);
return (React.createElement(DocumentContextProvider, { nodeRef: nodeRef },
React.createElement("div", { ref: useForkRef(ref, nodeRef), className: clsx(cssClass("root"), className), ...rest }, children)));
});
var SwipeState;
(function (SwipeState) {
SwipeState[SwipeState["NONE"] = 0] = "NONE";
SwipeState[SwipeState["SWIPE"] = 1] = "SWIPE";
SwipeState[SwipeState["PULL"] = 2] = "PULL";
SwipeState[SwipeState["ANIMATION"] = 3] = "ANIMATION";
})(SwipeState || (SwipeState = {}));
function usePointerEvents(subscribeSensors, onPointerDown, onPointerMove, onPointerUp, disabled) {
React.useEffect(() => !disabled
? cleanup(subscribeSensors(EVENT_ON_POINTER_DOWN, onPointerDown), subscribeSensors(EVENT_ON_POINTER_MOVE, onPointerMove), subscribeSensors(EVENT_ON_POINTER_UP, onPointerUp), subscribeSensors(EVENT_ON_POINTER_LEAVE, onPointerUp), subscribeSensors(EVENT_ON_POINTER_CANCEL, onPointerUp))
: () => { }, [subscribeSensors, onPointerDown, onPointerMove, onPointerUp, disabled]);
}
var Gesture;
(function (Gesture) {
Gesture[Gesture["NONE"] = 0] = "NONE";
Gesture[Gesture["SWIPE"] = 1] = "SWIPE";
Gesture[Gesture["PULL"] = 2] = "PULL";
})(Gesture || (Gesture = {}));
const SWIPE_THRESHOLD = 30;
function usePointerSwipe({ disableSwipeNavigation, closeOnBackdropClick }, subscribeSensors, isSwipeValid, containerWidth, swipeAnimationDuration, onSwipeStart, onSwipeProgress, onSwipeFinish, onSwipeCancel, pullUpEnabled, pullDownEnabled, onPullStart, onPullProgress, onPullFinish, onPullCancel, onClose) {
const offset = React.useRef(0);
const pointers = React.useRef([]);
const activePointer = React.useRef(undefined);
const startTime = React.useRef(0);
const gesture = React.useRef(Gesture.NONE);
const clearPointer = React.useCallback((event) => {
if (activePointer.current === event.pointerId) {
activePointer.current = undefined;
gesture.current = Gesture.NONE;
}
const currentPointers = pointers.current;
currentPointers.splice(0, currentPointers.length, ...currentPointers.filter((p) => p.pointerId !== event.pointerId));
}, []);
const addPointer = React.useCallback((event) => {
clearPointer(event);
event.persist();
pointers.current.push(event);
}, [clearPointer]);
const lookupPointer = React.useCallback((event) => pointers.current.find(({ pointerId }) => event.pointerId === pointerId), []);
const onPointerDown = useEventCallback((event) => {
addPointer(event);
});
const exceedsPullThreshold = (value, threshold) => (pullDownEnabled && value > threshold) || (pullUpEnabled && value < -threshold);
const onPointerUp = useEventCallback((event) => {
const pointer = lookupPointer(event);
if (pointer) {
if (activePointer.current === event.pointerId) {
const duration = Date.now() - startTime.current;
const currentOffset = offset.current;
if (gesture.current === Gesture.SWIPE) {
if (Math.abs(currentOffset) > 0.3 * containerWidth ||
(Math.abs(currentOffset) > 5 && duration < swipeAnimationDuration)) {
onSwipeFinish(currentOffset, duration);
}
else {
onSwipeCancel(currentOffset);
}
}
else if (gesture.current === Gesture.PULL) {
if (exceedsPullThreshold(currentOffset, 2 * SWIPE_THRESHOLD)) {
onPullFinish(currentOffset, duration);
}
else {
onPullCancel(currentOffset);
}
}
offset.current = 0;
gesture.current = Gesture.NONE;
}
else {
const { target } = event;
if (closeOnBackdropClick &&
target instanceof HTMLElement &&
target === pointer.target &&
(target.classList.contains(cssClass(CLASS_SLIDE)) || target.classList.contains(cssClass(CLASS_SLIDE_WRAPPER)))) {
onClose();
}
}
}
clearPointer(event);
});
const onPointerMove = useEventCallback((event) => {
const pointer = lookupPointer(event);
if (pointer) {
const isCurrentPointer = activePointer.current === event.pointerId;
if (event.buttons === 0) {
if (isCurrentPointer && offset.current !== 0) {
onPointerUp(event);
}
else {
clearPointer(pointer);
}
return;
}
const deltaX = event.clientX - pointer.clientX;
const deltaY = event.clientY - pointer.clientY;
if (activePointer.current === undefined) {
const startGesture = (newGesture) => {
addPointer(event);
activePointer.current = event.pointerId;
startTime.current = Date.now();
gesture.current = newGesture;
};
if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > SWIPE_THRESHOLD && isSwipeValid(deltaX)) {
if (!disableSwipeNavigation) {
startGesture(Gesture.SWIPE);
onSwipeStart();
}
}
else if (Math.abs(deltaY) > Math.abs(deltaX) && exceedsPullThreshold(deltaY, SWIPE_THRESHOLD)) {
startGesture(Gesture.PULL);
onPullStart();
}
}
else if (isCurrentPointer) {
if (gesture.current === Gesture.SWIPE) {
offset.current = deltaX;
onSwipeProgress(deltaX);
}
else if (gesture.current === Gesture.PULL) {
offset.current = deltaY;
onPullProgress(deltaY);
}
}
}
});
usePointerEvents(subscribeSensors, onPointerDown, onPointerMove, onPointerUp);
}
function usePreventWheelDefaults({ preventDefaultWheelX, preventDefaultWheelY, }) {
const ref = React.useRef(null);
const listener = useEventCallback((event) => {
const horizontal = Math.abs(event.deltaX) > Math.abs(event.deltaY);
if ((horizontal && preventDefaultWheelX) || (!horizontal && preventDefaultWheelY) || event.ctrlKey) {
event.preventDefault();
}
});
return React.useCallback((node) => {
var _a;
if (node) {
node.addEventListener("wheel", listener, { passive: false });
}
else {
(_a = ref.current) === null || _a === void 0 ? void 0 : _a.removeEventListener("wheel", listener);
}
ref.current = node;
}, [listener]);
}
function useWheelSwipe(swipeState, subscribeSensors, isSwipeValid, containerWidth, swipeAnimationDuration, onSwipeStart, onSwipeProgress, onSwipeFinish, onSwipeCancel) {
const offset = React.useRef(0);
const intent = React.useRef(0);
const intentCleanup = React.useRef(undefined);
const resetCleanup = React.useRef(undefined);
const wheelInertia = React.useRef(0);
const wheelInertiaCleanup = React.useRef(undefined);
const startTime = React.useRef(0);
const { setTimeout, clearTimeout } = useTimeouts();
const cancelSwipeIntentCleanup = React.useCallback(() => {
if (intentCleanup.current) {
clearTimeout(intentCleanup.current);
intentCleanup.current = undefined;
}
}, [clearTimeout]);
const cancelSwipeResetCleanup = React.useCallback(() => {
if (resetCleanup.current) {
clearTimeout(resetCleanup.current);
resetCleanup.current = undefined;
}
}, [clearTimeout]);
const handleCleanup = useEventCallback(() => {
if (swipeState !== SwipeState.SWIPE) {
offset.current = 0;
startTime.current = 0;
cancelSwipeIntentCleanup();
cancelSwipeResetCleanup();
}
});
React.useEffect(handleCleanup, [swipeState, handleCleanup]);
const handleCancelSwipe = useEventCallback((currentSwipeOffset) => {
resetCleanup.current = undefined;
if (offset.current === currentSwipeOffset) {
onSwipeCancel(offset.current);
}
});
const onWheel = useEventCallback((event) => {
if (event.ctrlKey) {
return;
}
if (Math.abs(event.deltaY) > Math.abs(event.deltaX)) {
return;
}
const setWheelInertia = (inertia) => {
wheelInertia.current = inertia;
clearTimeout(wheelInertiaCleanup.current);
wheelInertiaCleanup.current =
inertia > 0
? setTimeout(() => {
wheelInertia.current = 0;
wheelInertiaCleanup.current = undefined;
}, 300)
: undefined;
};
if (swipeState === SwipeState.NONE) {
if (Math.abs(event.deltaX) <= 1.2 * Math.abs(wheelInertia.current)) {
setWheelInertia(event.deltaX);
return;
}
if (!isSwipeValid(-event.deltaX)) {
return;
}
intent.current += event.deltaX;
cancelSwipeIntentCleanup();
if (Math.abs(intent.current) > 30) {
intent.current = 0;
setWheelInertia(0);
startTime.current = Date.now();
onSwipeStart();
}
else {
const currentSwipeIntent = intent.current;
intentCleanup.current = setTimeout(() => {
intentCleanup.current = undefined;
if (currentSwipeIntent === intent.current) {
intent.current = 0;
}
}, swipeAnimationDuration);
}
}
else if (swipeState === SwipeState.SWIPE) {
let newSwipeOffset = offset.current - event.deltaX;
newSwipeOffset = Math.min(Math.abs(newSwipeOffset), containerWidth) * Math.sign(newSwipeOffset);
offset.current = newSwipeOffset;
onSwipeProgress(newSwipeOffset);
cancelSwipeResetCleanup();
if (Math.abs(newSwipeOffset) > 0.2 * containerWidth) {
setWheelInertia(event.deltaX);
onSwipeFinish(newSwipeOffset, Date.now() - startTime.current);
return;
}
resetCleanup.current = setTimeout(() => handleCancelSwipe(newSwipeOffset), 2 * swipeAnimationDuration);
}
else {
setWheelInertia(event.deltaX);
}
});
React.useEffect(() => subscribeSensors(EVENT_ON_WHEEL, onWheel), [subscribeSensors, onWheel]);
}
const cssContainerPrefix = makeComposePrefix("container");
const ControllerContext = React.createContext(null);
const useController = makeUseContext("useController", "ControllerContext", ControllerContext);
function Controller({ children, ...props }) {
var _a;
const { carousel, animation, controller, on, styles, render } = props;
const { closeOnPullUp, closeOnPullDown, preventDefaultWheelX, preventDefaultWheelY } = controller;
const [toolbarWidth, setToolbarWidth] = React.useState();
const state = useLightboxState();
const dispatch = useLightboxDispatch();
const [swipeState, setSwipeState] = React.useState(SwipeState.NONE);
const swipeOffset = React.useRef(0);
const pullOffset = React.useRef(0);
const pullOpacity = React.useRef(1);
const { registerSensors, subscribeSensors } = useSensors();
const { subscribe, publish } = useEvents();
const cleanupAnimationIncrement = useDelay();
const cleanupSwipeOffset = useDelay();
const cleanupPullOffset = useDelay();
const { containerRef, setContainerRef, containerRect } = useContainerRect();
const handleContainerRef = useForkRef(usePreventWheelDefaults({ preventDefaultWheelX, preventDefaultWheelY }), setContainerRef);
const carouselRef = React.useRef(null);
const setCarouselRef = useForkRef(carouselRef, undefined);
const { getOwnerDocument } = useDocumentContext();
const isRTL = useRTL();
const rtl = (value) => (isRTL ? -1 : 1) * (typeof value === "number" ? value : 1);
const focus = useEventCallback(() => { var _a; return (_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.focus(); });
const getLightboxProps = useEventCallback(() => props);
const getLightboxState = useEventCallback(() => state);
const prev = React.useCallback((params) => publish(ACTION_PREV, params), [publish]);
const next = React.useCallback((params) => publish(ACTION_NEXT, params), [publish]);
const close = React.useCallback(() => publish(ACTION_CLOSE), [publish]);
const isSwipeValid = (offset) => !(carousel.finite &&
((rtl(offset) > 0 && state.currentIndex === 0) ||
(rtl(offset) < 0 && state.currentIndex === state.slides.length - 1)));
const setSwipeOffset = (offset) => {
var _a;
swipeOffset.current = offset;
(_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.style.setProperty(cssVar("swipe_offset"), `${Math.round(offset)}px`);
};
const setPullOffset = (offset) => {
var _a, _b;
pullOffset.current = offset;
pullOpacity.current = (() => {
const threshold = 60;
const minOpacity = 0.5;
const offsetValue = (() => {
if (closeOnPullDown && offset > 0)
return offset;
if (closeOnPullUp && offset < 0)
return -offset;
return 0;
})();
return Math.min(Math.max(round(1 - (offsetValue / threshold) * (1 - minOpacity), 2), minOpacity), 1);
})();
(_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.style.setProperty(cssVar("pull_offset"), `${Math.round(offset)}px`);
(_b = containerRef.current) === null || _b === void 0 ? void 0 : _b.style.setProperty(cssVar("pull_opacity"), `${pullOpacity.current}`);
};
const { prepareAnimation: preparePullAnimation } = useAnimation(carouselRef, (snapshot, rect, translate) => {
if (carouselRef.current && containerRect) {
return {
keyframes: [
{
transform: `translate(0, ${snapshot.rect.y - rect.y + translate.y}px)`,
opacity: snapshot.opacity,
},
{ transform: "translate(0, 0)", opacity: 1 },
],
duration: snapshot.duration,
easing: animation.easing.fade,
};
}
return undefined;
});
const pull = (offset, cancel) => {
if (closeOnPullUp || closeOnPullDown) {
setPullOffset(offset);
let duration = 0;
if (carouselRef.current) {
duration = animation.fade * (cancel ? 2 : 1);
preparePullAnimation({
rect: carouselRef.current.getBoundingClientRect(),
opacity: pullOpacity.current,
duration,
});
}
cleanupPullOffset(() => {
setPullOffset(0);
setSwipeState(SwipeState.NONE);
}, duration);
setSwipeState(SwipeState.ANIMATION);
if (!cancel) {
close();
}
}
};
const { prepareAnimation, isAnimationPlaying } = useAnimation(carouselRef, (snapshot, rect, translate) => {
var _a;
if (carouselRef.current && containerRect && ((_a = state.animation) === null || _a === void 0 ? void 0 : _a.duration)) {
const parsedSpacing = parseLengthPercentage(carousel.spacing);
const spacingValue = (parsedSpacing.percent ? (parsedSpacing.percent * containerRect.width) / 100 : parsedSpacing.pixel) || 0;
return {
keyframes: [
{
transform: `translate(${rtl(state.globalIndex - snapshot.index) * (containerRect.width + spacingValue) +
snapshot.rect.x -
rect.x +
translate.x}px, 0)`,
},
{ transform: "translate(0, 0)" },
],
duration: state.animation.duration,
easing: state.animation.easing,
};
}
return undefined;
});
const swipe = useEventCallback((action) => {
var _a, _b;
const currentSwipeOffset = action.offset || 0;
const swipeDuration = !currentSwipeOffset ? ((_a = animation.navigation) !== null && _a !== void 0 ? _a : animation.swipe) : animation.swipe;
const swipeEasing = !currentSwipeOffset && !isAnimationPlaying() ? animation.easing.navigation : animation.easing.swipe;
let { direction } = action;
const count = (_b = action.count) !== null && _b !== void 0 ? _b : 1;
let newSwipeState = SwipeState.ANIMATION;
let newSwipeAnimationDuration = swipeDuration * count;
if (!direction) {
const containerWidth = containerRect === null || containerRect === void 0 ? void 0 : containerRect.width;
const elapsedTime = action.duration || 0;
const expectedTime = containerWidth
? (swipeDuration / containerWidth) * Math.abs(currentSwipeOffset)
: swipeDuration;
if (count !== 0) {
if (elapsedTime < expectedTime) {
newSwipeAnimationDuration =
(newSwipeAnimationDuration / expectedTime) * Math.max(elapsedTime, expectedTime / 5);
}
else if (containerWidth) {
newSwipeAnimationDuration =
(swipeDuration / containerWidth) * (containerWidth - Math.abs(currentSwipeOffset));
}
direction = rtl(currentSwipeOffset) > 0 ? ACTION_PREV : ACTION_NEXT;
}
else {
newSwipeAnimationDuration = swipeDuration / 2;
}
}
let increment = 0;
if (direction === ACTION_PREV) {
if (isSwipeValid(rtl(1))) {
increment = -count;
}
else {
newSwipeState = SwipeState.NONE;
newSwipeAnimationDuration = swipeDuration;
}
}
else if (direction === ACTION_NEXT) {
if (isSwipeValid(rtl(-1))) {
increment = count;
}
else {
newSwipeState = SwipeState.NONE;
newSwipeAnimationDuration = swipeDuration;
}
}
newSwipeAnimat