UNPKG

yet-another-react-lightbox

Version:
1,122 lines (1,093 loc) 73.4 kB
'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