@deftui/react 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +7 -2
- package/dist/index.mjs +7 -3
- package/dist/vendor/README.md +65 -0
- package/dist/vendor/coss/scroll-area.d.mts +2 -2
- package/dist/vendor/coss/scroll-area.mjs +2 -2
- package/dist/vendor/shadcn/alert-dialog.d.mts +10 -8
- package/dist/vendor/shadcn/alert-dialog.mjs +14 -11
- package/dist/vendor/shadcn/badge.d.mts +1 -1
- package/dist/vendor/shadcn/badge.mjs +3 -0
- package/dist/vendor/shadcn/breadcrumb.mjs +1 -1
- package/dist/vendor/shadcn/button.d.mts +4 -1
- package/dist/vendor/shadcn/button.mjs +17 -5
- package/dist/vendor/shadcn/calendar.mjs +1 -1
- package/dist/vendor/shadcn/carousel.mjs +1 -1
- package/dist/vendor/shadcn/combobox.d.mts +16 -14
- package/dist/vendor/shadcn/combobox.mjs +24 -21
- package/dist/vendor/shadcn/dialog.d.mts +10 -8
- package/dist/vendor/shadcn/dialog.mjs +16 -13
- package/dist/vendor/shadcn/drawer.d.mts +10 -8
- package/dist/vendor/shadcn/drawer.mjs +16 -13
- package/dist/vendor/shadcn/dropdown-menu.mjs +1 -1
- package/dist/vendor/shadcn/field.mjs +0 -1
- package/dist/vendor/shadcn/input.mjs +1 -1
- package/dist/vendor/shadcn/pagination.mjs +1 -1
- package/dist/vendor/shadcn/select.mjs +1 -1
- package/dist/vendor/shadcn/sheet.mjs +1 -1
- package/dist/vendor/shadcn/sidebar.mjs +1 -2
- package/dist/vendor/shadcn/skeleton.mjs +11 -3
- package/dist/vendor/shadcn/spinner.mjs +1 -1
- package/dist/vendor/shadcn/toast.mjs +1 -1
- package/dist/vendor/tailored/lightbox.d.mts +187 -0
- package/dist/vendor/tailored/lightbox.mjs +2389 -0
- package/dist/widgets/breadcrumb.mjs +1 -1
- package/dist/widgets/datetime-picker.mjs +1 -1
- package/dist/widgets/index.d.mts +10 -15
- package/dist/widgets/index.mjs +28 -36
- package/dist/widgets/multimedia.d.mts +22 -0
- package/dist/widgets/multimedia.mjs +69 -0
- package/dist/widgets/pagination.mjs +1 -1
- package/dist/widgets/sidebar.mjs +1 -1
- package/dist/widgets/utils.d.mts +5 -0
- package/package.json +5 -1
- package/dist/vendor.css +0 -1
- package/dist/widgets/image-display.d.mts +0 -12
- package/dist/widgets/image-display.mjs +0 -29
- package/dist/widgets/toast.mjs +0 -1
|
@@ -0,0 +1,2389 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Spinner } from "../shadcn/spinner.mjs";
|
|
3
|
+
import { Button } from "../shadcn/button.mjs";
|
|
4
|
+
import { DropdownMenu, DropdownMenuItem, DropdownMenuTrigger } from "../shadcn/dropdown-menu.mjs";
|
|
5
|
+
import { Skeleton } from "../shadcn/skeleton.mjs";
|
|
6
|
+
import { cn } from "cn";
|
|
7
|
+
import { mergeProps } from "@base-ui/react/merge-props";
|
|
8
|
+
import * as React$1 from "react";
|
|
9
|
+
import { ChevronLeftIcon, ChevronRightIcon, DownloadIcon, EllipsisIcon, ImageOffIcon, XIcon, ZoomInIcon, ZoomOutIcon } from "lucide-react";
|
|
10
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
import { Dialog } from "@base-ui/react/dialog";
|
|
12
|
+
import { Menu } from "@base-ui/react/menu";
|
|
13
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
14
|
+
import { animate, useReducedMotion } from "motion/react";
|
|
15
|
+
import { flushSync } from "react-dom";
|
|
16
|
+
import { ScrollArea } from "@base-ui/react/scroll-area";
|
|
17
|
+
// src/vendor/tailored/lightbox.tsx
|
|
18
|
+
const LIGHTBOX_STYLES = String.raw`
|
|
19
|
+
:where([data-slot="lightbox-viewport"]) {
|
|
20
|
+
overscroll-behavior: contain;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
:where([data-slot="lightbox-slides"]) {
|
|
24
|
+
touch-action: none;
|
|
25
|
+
-webkit-user-select: none;
|
|
26
|
+
user-select: none;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
:where([data-slot="lightbox-slide"]) {
|
|
30
|
+
transform-origin: center;
|
|
31
|
+
will-change: transform, opacity;
|
|
32
|
+
backface-visibility: hidden;
|
|
33
|
+
contain: layout paint;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
:where([data-slot="lightbox-zoom"]) {
|
|
37
|
+
transform-origin: center;
|
|
38
|
+
will-change: transform;
|
|
39
|
+
backface-visibility: hidden;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
:where([data-slot="lightbox-zoom"][data-zoomable="true"]:not([data-zoomed="true"])) {
|
|
43
|
+
cursor: zoom-in;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
:where([data-slot="lightbox-zoom"][data-zoomed="true"]) {
|
|
47
|
+
cursor: zoom-out;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
:where([data-slot="lightbox-zoom"][data-zoomed="true"][data-dragging="true"]) {
|
|
51
|
+
cursor: grabbing;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
:where([data-slot="lightbox-content"][data-dragging="true"]),
|
|
55
|
+
:where([data-slot="lightbox-content"][data-dragging="true"] *) {
|
|
56
|
+
cursor: grabbing;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
:where([data-slot="lightbox-slide"][aria-hidden="true"]) {
|
|
60
|
+
pointer-events: none;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
:where([data-slot="lightbox-thumbnails"]) {
|
|
64
|
+
overscroll-behavior-inline: contain;
|
|
65
|
+
scrollbar-width: thin;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
html.lightbox-morph-opening [data-slot="lightbox-content"] {
|
|
69
|
+
scale: 1 !important;
|
|
70
|
+
transition-property: opacity !important;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@media (prefers-reduced-motion: reduce) {
|
|
74
|
+
:where([data-slot="lightbox-slide"]) {
|
|
75
|
+
transition: none !important;
|
|
76
|
+
animation: none !important;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@media (forced-colors: active) {
|
|
82
|
+
:where([data-slot="lightbox-toolbar"] button:focus-visible),
|
|
83
|
+
:where([data-slot="lightbox-viewport"] button:focus-visible),
|
|
84
|
+
:where([data-slot="lightbox-thumbnails"] button:focus-visible) {
|
|
85
|
+
outline: 2px solid CanvasText;
|
|
86
|
+
outline-offset: 2px;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
`;
|
|
90
|
+
const EMPTY_CAPTIONS_TRACK = "data:text/vtt;charset=utf-8,WEBVTT%0A%0A";
|
|
91
|
+
const VIDEO_THUMBNAIL_MAX_SIZE = 112;
|
|
92
|
+
const interactiveSelector = "a,button,input,select,textarea,summary,video,audio,iframe,[contenteditable=true],[role=button],[role=link],[data-lightbox-gesture-ignore]";
|
|
93
|
+
const postGestureActivationSelector = "a[href],button,input:not([type=\"hidden\"]),select,textarea,summary,label,[contenteditable=true],[role=\"button\"],[role=\"link\"],[role=\"checkbox\"],[role=\"menuitem\"],[role=\"menuitemcheckbox\"],[role=\"menuitemradio\"],[role=\"option\"],[role=\"radio\"],[role=\"switch\"],[role=\"tab\"]";
|
|
94
|
+
const generatedVideoThumbnails = /* @__PURE__ */ new WeakMap();
|
|
95
|
+
function mergeLightboxBackdropStyle(baseStyle, style) {
|
|
96
|
+
if (typeof style === "function") return (state) => {
|
|
97
|
+
return {
|
|
98
|
+
...baseStyle,
|
|
99
|
+
...style(state)
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
return {
|
|
103
|
+
...baseStyle,
|
|
104
|
+
...style
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function mergeLightboxContentStyle(baseStyle, style) {
|
|
108
|
+
if (typeof style === "function") return (state) => {
|
|
109
|
+
return {
|
|
110
|
+
...baseStyle,
|
|
111
|
+
...style(state)
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
return {
|
|
115
|
+
...baseStyle,
|
|
116
|
+
...style
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
const initialTransform = {
|
|
120
|
+
scale: 1,
|
|
121
|
+
panX: 0,
|
|
122
|
+
panY: 0,
|
|
123
|
+
swipeX: 0,
|
|
124
|
+
dismissY: 0,
|
|
125
|
+
dragging: false
|
|
126
|
+
};
|
|
127
|
+
const LightboxContext = React$1.createContext(null);
|
|
128
|
+
function useLightbox() {
|
|
129
|
+
const context = React$1.useContext(LightboxContext);
|
|
130
|
+
if (!context) throw new Error("Lightbox parts must be rendered inside <Lightbox>.");
|
|
131
|
+
return context;
|
|
132
|
+
}
|
|
133
|
+
function getAssetLoadKey(type, source) {
|
|
134
|
+
return `${type}:${source}`;
|
|
135
|
+
}
|
|
136
|
+
function getMediaLoadKey(item) {
|
|
137
|
+
return getAssetLoadKey(item.type, item.src);
|
|
138
|
+
}
|
|
139
|
+
function getThumbnailLoadKey(item) {
|
|
140
|
+
const source = item.type === "image" ? item.thumbnailSrc ?? item.src : item.poster ?? item.src;
|
|
141
|
+
return getAssetLoadKey(item.type, source);
|
|
142
|
+
}
|
|
143
|
+
function clamp(value, min, max) {
|
|
144
|
+
return Math.min(Math.max(value, min), max);
|
|
145
|
+
}
|
|
146
|
+
function normalizeIndex(index, length, loop) {
|
|
147
|
+
if (length === 0) return 0;
|
|
148
|
+
if (loop) return (index % length + length) % length;
|
|
149
|
+
return clamp(index, 0, length - 1);
|
|
150
|
+
}
|
|
151
|
+
function getItemLabel(item, index) {
|
|
152
|
+
if (!item) return `Media ${index + 1}`;
|
|
153
|
+
return item.type === "image" ? item.alt || `Image ${index + 1}` : item.label;
|
|
154
|
+
}
|
|
155
|
+
function isElementVisible(element) {
|
|
156
|
+
const rect = element.getBoundingClientRect();
|
|
157
|
+
return rect.width > 0 && rect.height > 0 && rect.bottom > 0 && rect.right > 0 && rect.top < window.innerHeight && rect.left < window.innerWidth;
|
|
158
|
+
}
|
|
159
|
+
function getMorphTarget(element) {
|
|
160
|
+
if (!element) return null;
|
|
161
|
+
if (element.matches("[data-lightbox-morph-target]")) return element;
|
|
162
|
+
const explicitTarget = element.querySelector("[data-lightbox-morph-target]");
|
|
163
|
+
if (explicitTarget) return explicitTarget;
|
|
164
|
+
if (element.matches("img,video,canvas")) return element;
|
|
165
|
+
return element.querySelector("img,video,canvas") ?? element;
|
|
166
|
+
}
|
|
167
|
+
function isStandaloneMedia(media, gallery) {
|
|
168
|
+
const interactiveAncestor = media.parentElement?.closest(interactiveSelector);
|
|
169
|
+
return !interactiveAncestor || interactiveAncestor === gallery;
|
|
170
|
+
}
|
|
171
|
+
function getStandaloneGalleryMedia(target, gallery) {
|
|
172
|
+
if (!(target instanceof Element)) return null;
|
|
173
|
+
const media = target.closest("img,video");
|
|
174
|
+
if (!(media instanceof HTMLImageElement || media instanceof HTMLVideoElement) || !gallery.contains(media) || !isStandaloneMedia(media, gallery)) return null;
|
|
175
|
+
return media;
|
|
176
|
+
}
|
|
177
|
+
function getPostGestureActivationTarget(target) {
|
|
178
|
+
if (!(target instanceof Element)) return null;
|
|
179
|
+
const element = target.closest(postGestureActivationSelector);
|
|
180
|
+
if (!element || element.matches(":disabled,[aria-disabled=\"true\"]") || element.closest("[inert]")) return null;
|
|
181
|
+
return element;
|
|
182
|
+
}
|
|
183
|
+
function imageToItem(image) {
|
|
184
|
+
const figure = image.closest("figure");
|
|
185
|
+
const caption = image.dataset.lightboxCaption ?? figure?.querySelector(":scope > figcaption")?.textContent?.trim() ?? void 0;
|
|
186
|
+
const width = Number(image.getAttribute("width"));
|
|
187
|
+
const height = Number(image.getAttribute("height"));
|
|
188
|
+
const srcSet = image.srcset || void 0;
|
|
189
|
+
return {
|
|
190
|
+
id: image.id || void 0,
|
|
191
|
+
type: "image",
|
|
192
|
+
src: image.currentSrc || image.src,
|
|
193
|
+
alt: image.alt,
|
|
194
|
+
width: width > 0 ? width : image.naturalWidth || void 0,
|
|
195
|
+
height: height > 0 ? height : image.naturalHeight || void 0,
|
|
196
|
+
srcSet,
|
|
197
|
+
sizes: srcSet ? image.sizes || void 0 : void 0,
|
|
198
|
+
thumbnailSrc: image.currentSrc || image.src,
|
|
199
|
+
caption
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function getVideoThumbnail(video) {
|
|
203
|
+
const source = video.currentSrc || video.src;
|
|
204
|
+
if (!source || video.readyState < 2 || video.videoWidth <= 0 || video.videoHeight <= 0) return;
|
|
205
|
+
const cached = generatedVideoThumbnails.get(video);
|
|
206
|
+
if (cached?.source === source) return cached.thumbnail;
|
|
207
|
+
const scale = Math.min(1, VIDEO_THUMBNAIL_MAX_SIZE / Math.max(video.videoWidth, video.videoHeight));
|
|
208
|
+
const canvas = document.createElement("canvas");
|
|
209
|
+
canvas.width = Math.max(1, Math.round(video.videoWidth * scale));
|
|
210
|
+
canvas.height = Math.max(1, Math.round(video.videoHeight * scale));
|
|
211
|
+
try {
|
|
212
|
+
const context = canvas.getContext("2d");
|
|
213
|
+
if (!context) return void 0;
|
|
214
|
+
context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
215
|
+
const thumbnail = canvas.toDataURL("image/webp", .72);
|
|
216
|
+
generatedVideoThumbnails.set(video, {
|
|
217
|
+
source,
|
|
218
|
+
thumbnail
|
|
219
|
+
});
|
|
220
|
+
return thumbnail;
|
|
221
|
+
} catch {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function videoToItem(video) {
|
|
226
|
+
const figure = video.closest("figure");
|
|
227
|
+
const caption = video.dataset.lightboxCaption ?? figure?.querySelector(":scope > figcaption")?.textContent?.trim() ?? void 0;
|
|
228
|
+
const width = Number(video.getAttribute("width"));
|
|
229
|
+
const height = Number(video.getAttribute("height"));
|
|
230
|
+
const sources = Array.from(video.querySelectorAll("source"), (source) => {
|
|
231
|
+
return {
|
|
232
|
+
media: source.media || void 0,
|
|
233
|
+
src: source.src,
|
|
234
|
+
type: source.type || void 0
|
|
235
|
+
};
|
|
236
|
+
});
|
|
237
|
+
const src = [
|
|
238
|
+
video.currentSrc,
|
|
239
|
+
video.dataset.lightboxSrc,
|
|
240
|
+
video.src,
|
|
241
|
+
sources.at(0)?.src
|
|
242
|
+
].find((source) => source) ?? "";
|
|
243
|
+
const label = [
|
|
244
|
+
video.getAttribute("aria-label"),
|
|
245
|
+
video.title,
|
|
246
|
+
caption
|
|
247
|
+
].find((value) => value);
|
|
248
|
+
return {
|
|
249
|
+
id: video.id || void 0,
|
|
250
|
+
type: "video",
|
|
251
|
+
src,
|
|
252
|
+
label: label ?? "Video",
|
|
253
|
+
poster: video.poster || getVideoThumbnail(video),
|
|
254
|
+
width: width > 0 ? width : video.videoWidth || void 0,
|
|
255
|
+
height: height > 0 ? height : video.videoHeight || void 0,
|
|
256
|
+
sources: sources.length > 0 ? sources : void 0,
|
|
257
|
+
caption,
|
|
258
|
+
videoProps: {
|
|
259
|
+
autoPlay: video.autoplay,
|
|
260
|
+
controls: true,
|
|
261
|
+
loop: video.loop,
|
|
262
|
+
muted: video.muted,
|
|
263
|
+
playsInline: video.playsInline,
|
|
264
|
+
preload: "auto"
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function getLoadedAssetKeys(media, item) {
|
|
269
|
+
if (!(media instanceof HTMLImageElement ? media.complete && media.naturalWidth > 0 : media.readyState >= 2)) return [];
|
|
270
|
+
const mediaKey = getMediaLoadKey(item);
|
|
271
|
+
const thumbnailKey = getThumbnailLoadKey(item);
|
|
272
|
+
if (mediaKey === thumbnailKey) return [mediaKey];
|
|
273
|
+
return item.type === "video" && item.poster?.startsWith("data:") ? [mediaKey, thumbnailKey] : [mediaKey];
|
|
274
|
+
}
|
|
275
|
+
function mediaToItem(media) {
|
|
276
|
+
return media instanceof HTMLImageElement ? imageToItem(media) : videoToItem(media);
|
|
277
|
+
}
|
|
278
|
+
function hasCustomContent(children) {
|
|
279
|
+
return React$1.Children.toArray(children).some((child) => React$1.isValidElement(child) && child.type.__lightboxContent === true);
|
|
280
|
+
}
|
|
281
|
+
function getEventDetails(reason, event, trigger) {
|
|
282
|
+
return {
|
|
283
|
+
reason,
|
|
284
|
+
event,
|
|
285
|
+
trigger
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
function Lightbox({ children, items: itemsProp, open: openProp, defaultOpen = false, onOpenChange, onOpenChangeComplete, onOpen, onClose, index: indexProp, defaultIndex = 0, onChangeImage, overlay = "blur", loop = true, noCounter = false, noCarousel = false, noControls = false, loading = "eager", preload = 1, maxZoom = 4, controls = [], dir, className, actionsRef, ...rootProps }) {
|
|
289
|
+
const galleryRef = React$1.useRef(null);
|
|
290
|
+
const mediaRef = React$1.useRef(null);
|
|
291
|
+
const sourceMapRef = React$1.useRef(/* @__PURE__ */ new Map());
|
|
292
|
+
const galleryTouchPressRef = React$1.useRef(null);
|
|
293
|
+
const suppressedGalleryClickRef = React$1.useRef(null);
|
|
294
|
+
const annotatedMediaRef = React$1.useRef(/* @__PURE__ */ new Map());
|
|
295
|
+
const openingCleanupRef = React$1.useRef(null);
|
|
296
|
+
const postGestureActivationCleanupRef = React$1.useRef(null);
|
|
297
|
+
const zoomAnimationRef = React$1.useRef(null);
|
|
298
|
+
const transformRef = React$1.useRef(initialTransform);
|
|
299
|
+
const [autoItems, setAutoItems] = React$1.useState([]);
|
|
300
|
+
const [loadedAssets, setLoadedAssets] = React$1.useState(() => /* @__PURE__ */ new Set());
|
|
301
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React$1.useState(defaultOpen);
|
|
302
|
+
const [uncontrolledIndex, setUncontrolledIndex] = React$1.useState(defaultIndex);
|
|
303
|
+
const [transform, setTransform] = React$1.useState(initialTransform);
|
|
304
|
+
React$1.useLayoutEffect(() => {
|
|
305
|
+
transformRef.current = transform;
|
|
306
|
+
}, [transform]);
|
|
307
|
+
const [inheritedDirection, setInheritedDirection] = React$1.useState(() => typeof document !== "undefined" && document.documentElement.dir === "rtl" ? "rtl" : "ltr");
|
|
308
|
+
const shouldReduceMotion = Boolean(useReducedMotion());
|
|
309
|
+
const isOpenControlled = openProp !== void 0;
|
|
310
|
+
const isIndexControlled = indexProp !== void 0;
|
|
311
|
+
const open = openProp ?? uncontrolledOpen;
|
|
312
|
+
const items = itemsProp ?? autoItems;
|
|
313
|
+
const activeIndex = normalizeIndex(indexProp ?? uncontrolledIndex, items.length, loop);
|
|
314
|
+
const activeItem = items[activeIndex];
|
|
315
|
+
const direction = dir ?? inheritedDirection;
|
|
316
|
+
const armPostGestureActivationRepair = React$1.useCallback((releaseEvent) => {
|
|
317
|
+
if (!(releaseEvent instanceof PointerEvent) || releaseEvent.pointerType !== "touch" && releaseEvent.pointerType !== "pen") return;
|
|
318
|
+
const releaseTarget = releaseEvent.target;
|
|
319
|
+
const doc = releaseTarget instanceof Node ? releaseTarget.ownerDocument ?? document : document;
|
|
320
|
+
const view = doc.defaultView ?? window;
|
|
321
|
+
postGestureActivationCleanupRef.current?.();
|
|
322
|
+
let active = true;
|
|
323
|
+
let fallbackTimer = 0;
|
|
324
|
+
let suppressionTimer = 0;
|
|
325
|
+
let press = null;
|
|
326
|
+
let pendingTarget = null;
|
|
327
|
+
let syntheticTarget = null;
|
|
328
|
+
function cleanup() {
|
|
329
|
+
if (!active) return;
|
|
330
|
+
active = false;
|
|
331
|
+
view.clearTimeout(fallbackTimer);
|
|
332
|
+
view.clearTimeout(suppressionTimer);
|
|
333
|
+
doc.removeEventListener("click", handleClick, true);
|
|
334
|
+
doc.removeEventListener("pointercancel", handlePointerCancel, true);
|
|
335
|
+
doc.removeEventListener("pointerdown", handlePointerDown, true);
|
|
336
|
+
doc.removeEventListener("pointermove", handlePointerMove, true);
|
|
337
|
+
doc.removeEventListener("pointerup", handlePointerUp, true);
|
|
338
|
+
if (postGestureActivationCleanupRef.current === cleanup) postGestureActivationCleanupRef.current = null;
|
|
339
|
+
}
|
|
340
|
+
function handlePointerDown(event) {
|
|
341
|
+
if (!event.isPrimary) {
|
|
342
|
+
if (press || pendingTarget) cleanup();
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
if (event.button !== 0) return;
|
|
346
|
+
if (press || pendingTarget || syntheticTarget) {
|
|
347
|
+
cleanup();
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
const target = getPostGestureActivationTarget(event.target);
|
|
351
|
+
if (!target || target.closest("[data-slot=\"lightbox-portal\"]")) {
|
|
352
|
+
cleanup();
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
press = {
|
|
356
|
+
pointerId: event.pointerId,
|
|
357
|
+
target,
|
|
358
|
+
startX: event.clientX,
|
|
359
|
+
startY: event.clientY
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
function handlePointerMove(event) {
|
|
363
|
+
if (!press || press.pointerId !== event.pointerId) return;
|
|
364
|
+
if (Math.hypot(event.clientX - press.startX, event.clientY - press.startY) > 8) cleanup();
|
|
365
|
+
}
|
|
366
|
+
function handlePointerCancel(event) {
|
|
367
|
+
if (press?.pointerId === event.pointerId) cleanup();
|
|
368
|
+
}
|
|
369
|
+
function handlePointerUp(event) {
|
|
370
|
+
if (!press || press.pointerId !== event.pointerId) return;
|
|
371
|
+
const currentPress = press;
|
|
372
|
+
press = null;
|
|
373
|
+
if (getPostGestureActivationTarget(event.target) !== currentPress.target || Math.hypot(event.clientX - currentPress.startX, event.clientY - currentPress.startY) > 8) {
|
|
374
|
+
cleanup();
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
pendingTarget = currentPress.target;
|
|
378
|
+
fallbackTimer = view.setTimeout(() => {
|
|
379
|
+
const activationTarget = pendingTarget;
|
|
380
|
+
if (!active || !activationTarget?.isConnected || getPostGestureActivationTarget(activationTarget) !== activationTarget) {
|
|
381
|
+
cleanup();
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
pendingTarget = null;
|
|
385
|
+
syntheticTarget = activationTarget;
|
|
386
|
+
activationTarget.click();
|
|
387
|
+
suppressionTimer = view.setTimeout(cleanup, 500);
|
|
388
|
+
}, 80);
|
|
389
|
+
}
|
|
390
|
+
function handleClick(event) {
|
|
391
|
+
if (!event.isTrusted) return;
|
|
392
|
+
const target = event.target instanceof Element ? event.target.closest(postGestureActivationSelector) : null;
|
|
393
|
+
if (syntheticTarget && target === syntheticTarget) {
|
|
394
|
+
event.preventDefault();
|
|
395
|
+
event.stopImmediatePropagation();
|
|
396
|
+
cleanup();
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
if (pendingTarget) cleanup();
|
|
400
|
+
}
|
|
401
|
+
doc.addEventListener("click", handleClick, true);
|
|
402
|
+
doc.addEventListener("pointercancel", handlePointerCancel, true);
|
|
403
|
+
doc.addEventListener("pointerdown", handlePointerDown, true);
|
|
404
|
+
doc.addEventListener("pointermove", handlePointerMove, true);
|
|
405
|
+
doc.addEventListener("pointerup", handlePointerUp, true);
|
|
406
|
+
postGestureActivationCleanupRef.current = cleanup;
|
|
407
|
+
}, []);
|
|
408
|
+
const markAssetsLoaded = React$1.useCallback((keys) => {
|
|
409
|
+
if (keys.length === 0) return;
|
|
410
|
+
setLoadedAssets((current) => {
|
|
411
|
+
const missingKeys = keys.filter((key) => !current.has(key));
|
|
412
|
+
if (missingKeys.length === 0) return current;
|
|
413
|
+
const next = new Set(current);
|
|
414
|
+
for (const key of missingKeys) next.add(key);
|
|
415
|
+
return next;
|
|
416
|
+
});
|
|
417
|
+
}, []);
|
|
418
|
+
const markAssetLoaded = React$1.useCallback((key) => markAssetsLoaded([key]), [markAssetsLoaded]);
|
|
419
|
+
React$1.useLayoutEffect(() => {
|
|
420
|
+
transformRef.current = transform;
|
|
421
|
+
}, [transform]);
|
|
422
|
+
const stopZoomAnimation = React$1.useCallback(() => {
|
|
423
|
+
zoomAnimationRef.current?.stop();
|
|
424
|
+
zoomAnimationRef.current = null;
|
|
425
|
+
}, []);
|
|
426
|
+
const animateZoom = React$1.useCallback((target) => {
|
|
427
|
+
stopZoomAnimation();
|
|
428
|
+
const start = transformRef.current;
|
|
429
|
+
const end = {
|
|
430
|
+
scale: clamp(target.scale, 1, maxZoom),
|
|
431
|
+
panX: target.panX,
|
|
432
|
+
panY: target.panY
|
|
433
|
+
};
|
|
434
|
+
const update = (progress) => {
|
|
435
|
+
const next = {
|
|
436
|
+
...transformRef.current,
|
|
437
|
+
scale: start.scale + (end.scale - start.scale) * progress,
|
|
438
|
+
panX: start.panX + (end.panX - start.panX) * progress,
|
|
439
|
+
panY: start.panY + (end.panY - start.panY) * progress,
|
|
440
|
+
swipeX: 0,
|
|
441
|
+
dismissY: 0
|
|
442
|
+
};
|
|
443
|
+
transformRef.current = next;
|
|
444
|
+
setTransform(next);
|
|
445
|
+
};
|
|
446
|
+
if (shouldReduceMotion) {
|
|
447
|
+
update(1);
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
zoomAnimationRef.current = animate(0, 1, {
|
|
451
|
+
duration: .22,
|
|
452
|
+
ease: [
|
|
453
|
+
.22,
|
|
454
|
+
1,
|
|
455
|
+
.36,
|
|
456
|
+
1
|
|
457
|
+
],
|
|
458
|
+
onUpdate: update,
|
|
459
|
+
onComplete: () => {
|
|
460
|
+
zoomAnimationRef.current = null;
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
}, [
|
|
464
|
+
maxZoom,
|
|
465
|
+
shouldReduceMotion,
|
|
466
|
+
stopZoomAnimation
|
|
467
|
+
]);
|
|
468
|
+
React$1.useEffect(() => () => {
|
|
469
|
+
openingCleanupRef.current?.();
|
|
470
|
+
postGestureActivationCleanupRef.current?.();
|
|
471
|
+
stopZoomAnimation();
|
|
472
|
+
}, [stopZoomAnimation]);
|
|
473
|
+
const handleGalleryRef = React$1.useCallback((element) => {
|
|
474
|
+
galleryRef.current = element;
|
|
475
|
+
if (!dir && element) setInheritedDirection(window.getComputedStyle(element).direction);
|
|
476
|
+
}, [dir]);
|
|
477
|
+
const restoreAnnotations = React$1.useCallback(() => {
|
|
478
|
+
for (const [media, previous] of annotatedMediaRef.current) for (const [name, value] of Object.entries(previous)) if (value == null) media.removeAttribute(name);
|
|
479
|
+
else media.setAttribute(name, value);
|
|
480
|
+
annotatedMediaRef.current.clear();
|
|
481
|
+
}, []);
|
|
482
|
+
const scanMedia = React$1.useCallback(() => {
|
|
483
|
+
const gallery = galleryRef.current;
|
|
484
|
+
if (!gallery || itemsProp) return {
|
|
485
|
+
foundItems: [],
|
|
486
|
+
sources: []
|
|
487
|
+
};
|
|
488
|
+
restoreAnnotations();
|
|
489
|
+
const mediaItems = Array.from(gallery.querySelectorAll("img,video")).filter((media) => isStandaloneMedia(media, gallery));
|
|
490
|
+
mediaItems.forEach((media, index) => {
|
|
491
|
+
annotatedMediaRef.current.set(media, {
|
|
492
|
+
role: media.getAttribute("role"),
|
|
493
|
+
tabindex: media.getAttribute("tabindex"),
|
|
494
|
+
"aria-haspopup": media.getAttribute("aria-haspopup"),
|
|
495
|
+
"aria-label": media.getAttribute("aria-label"),
|
|
496
|
+
"data-lightbox-source": media.getAttribute("data-lightbox-source")
|
|
497
|
+
});
|
|
498
|
+
media.setAttribute("role", "button");
|
|
499
|
+
media.setAttribute("tabindex", "0");
|
|
500
|
+
media.setAttribute("aria-haspopup", "dialog");
|
|
501
|
+
const label = media instanceof HTMLImageElement ? media.alt || `image ${index + 1}` : [media.getAttribute("aria-label"), media.title].find((value) => value) ?? `video ${index + 1}`;
|
|
502
|
+
media.setAttribute("aria-label", media.getAttribute("aria-label") ?? `Open ${label} in lightbox`);
|
|
503
|
+
media.setAttribute("data-lightbox-source", "");
|
|
504
|
+
if (media instanceof HTMLImageElement && !media.hasAttribute("alt")) console.debug("Lightbox auto-discovered an <img> without an alt attribute.");
|
|
505
|
+
});
|
|
506
|
+
const foundItems = mediaItems.map(mediaToItem);
|
|
507
|
+
markAssetsLoaded(mediaItems.flatMap((media, index) => {
|
|
508
|
+
const item = foundItems[index];
|
|
509
|
+
return item ? getLoadedAssetKeys(media, item) : [];
|
|
510
|
+
}));
|
|
511
|
+
return {
|
|
512
|
+
foundItems,
|
|
513
|
+
sources: mediaItems
|
|
514
|
+
};
|
|
515
|
+
}, [
|
|
516
|
+
itemsProp,
|
|
517
|
+
markAssetsLoaded,
|
|
518
|
+
restoreAnnotations
|
|
519
|
+
]);
|
|
520
|
+
React$1.useEffect(() => {
|
|
521
|
+
if (itemsProp) {
|
|
522
|
+
restoreAnnotations();
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
const gallery = galleryRef.current;
|
|
526
|
+
if (!gallery) return;
|
|
527
|
+
const refresh = () => {
|
|
528
|
+
if (open && autoItems.length > 0) return;
|
|
529
|
+
const { foundItems, sources } = scanMedia();
|
|
530
|
+
sourceMapRef.current.clear();
|
|
531
|
+
sources.forEach((element, index) => {
|
|
532
|
+
sourceMapRef.current.set(index, {
|
|
533
|
+
element,
|
|
534
|
+
morph: true
|
|
535
|
+
});
|
|
536
|
+
});
|
|
537
|
+
setAutoItems(foundItems);
|
|
538
|
+
};
|
|
539
|
+
refresh();
|
|
540
|
+
const observer = new MutationObserver(refresh);
|
|
541
|
+
observer.observe(gallery, {
|
|
542
|
+
childList: true,
|
|
543
|
+
subtree: true,
|
|
544
|
+
attributes: true,
|
|
545
|
+
attributeFilter: [
|
|
546
|
+
"src",
|
|
547
|
+
"srcset",
|
|
548
|
+
"sizes",
|
|
549
|
+
"alt",
|
|
550
|
+
"poster",
|
|
551
|
+
"width",
|
|
552
|
+
"height",
|
|
553
|
+
"data-lightbox-caption",
|
|
554
|
+
"data-lightbox-src",
|
|
555
|
+
"class"
|
|
556
|
+
]
|
|
557
|
+
});
|
|
558
|
+
return () => {
|
|
559
|
+
observer.disconnect();
|
|
560
|
+
restoreAnnotations();
|
|
561
|
+
};
|
|
562
|
+
}, [
|
|
563
|
+
autoItems.length,
|
|
564
|
+
itemsProp,
|
|
565
|
+
open,
|
|
566
|
+
restoreAnnotations,
|
|
567
|
+
scanMedia
|
|
568
|
+
]);
|
|
569
|
+
const changeIndex = React$1.useCallback((nextIndex, reason, event) => {
|
|
570
|
+
if (noCarousel || items.length === 0) return;
|
|
571
|
+
const normalized = normalizeIndex(nextIndex, items.length, loop);
|
|
572
|
+
if (normalized === activeIndex) return;
|
|
573
|
+
stopZoomAnimation();
|
|
574
|
+
if (!isIndexControlled) setUncontrolledIndex(normalized);
|
|
575
|
+
setTransform(initialTransform);
|
|
576
|
+
onChangeImage?.(normalized, items[normalized], {
|
|
577
|
+
reason,
|
|
578
|
+
event,
|
|
579
|
+
previousIndex: activeIndex
|
|
580
|
+
});
|
|
581
|
+
}, [
|
|
582
|
+
activeIndex,
|
|
583
|
+
isIndexControlled,
|
|
584
|
+
items,
|
|
585
|
+
loop,
|
|
586
|
+
noCarousel,
|
|
587
|
+
onChangeImage,
|
|
588
|
+
stopZoomAnimation
|
|
589
|
+
]);
|
|
590
|
+
const commitOpenChange = React$1.useCallback((nextOpen, details, lifecycleIndex = activeIndex, lifecycleItem = activeItem) => {
|
|
591
|
+
if (!isOpenControlled) setUncontrolledOpen(nextOpen);
|
|
592
|
+
onOpenChange?.(nextOpen, details);
|
|
593
|
+
const lifecycleDetails = {
|
|
594
|
+
reason: details.reason,
|
|
595
|
+
event: "event" in details ? details.event : void 0,
|
|
596
|
+
index: lifecycleIndex,
|
|
597
|
+
item: lifecycleItem
|
|
598
|
+
};
|
|
599
|
+
if (nextOpen) onOpen?.(lifecycleDetails);
|
|
600
|
+
else onClose?.(lifecycleDetails);
|
|
601
|
+
}, [
|
|
602
|
+
activeIndex,
|
|
603
|
+
activeItem,
|
|
604
|
+
isOpenControlled,
|
|
605
|
+
onClose,
|
|
606
|
+
onOpen,
|
|
607
|
+
onOpenChange
|
|
608
|
+
]);
|
|
609
|
+
const runOpenChange = React$1.useCallback((nextOpen, details, source, lifecycleIndex = activeIndex, lifecycleItem = activeItem) => {
|
|
610
|
+
openingCleanupRef.current?.();
|
|
611
|
+
openingCleanupRef.current = null;
|
|
612
|
+
stopZoomAnimation();
|
|
613
|
+
if (!nextOpen) {
|
|
614
|
+
commitOpenChange(false, details, lifecycleIndex, lifecycleItem);
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
const from = source?.element;
|
|
618
|
+
const canMorph = Boolean(source?.morph && from?.isConnected && isElementVisible(from) && !shouldReduceMotion);
|
|
619
|
+
const root = document.documentElement;
|
|
620
|
+
if (canMorph) root.classList.add("lightbox-morph-opening");
|
|
621
|
+
const fromRect = canMorph ? from?.getBoundingClientRect() : void 0;
|
|
622
|
+
const fromRadius = canMorph ? getComputedStyle(from).borderRadius : "";
|
|
623
|
+
flushSync(() => {
|
|
624
|
+
setTransform(initialTransform);
|
|
625
|
+
commitOpenChange(true, details, lifecycleIndex, lifecycleItem);
|
|
626
|
+
});
|
|
627
|
+
if (!canMorph || !fromRect) return;
|
|
628
|
+
const destination = getMorphTarget(mediaRef.current);
|
|
629
|
+
if (!destination || !isElementVisible(destination)) {
|
|
630
|
+
root.classList.remove("lightbox-morph-opening");
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
const toRect = destination.getBoundingClientRect();
|
|
634
|
+
if (toRect.width <= 0 || toRect.height <= 0) {
|
|
635
|
+
root.classList.remove("lightbox-morph-opening");
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
const scale = Math.max(fromRect.width / toRect.width, fromRect.height / toRect.height);
|
|
639
|
+
const translateX = fromRect.left + fromRect.width / 2 - (toRect.left + toRect.width / 2);
|
|
640
|
+
const translateY = fromRect.top + fromRect.height / 2 - (toRect.top + toRect.height / 2);
|
|
641
|
+
const previous = {
|
|
642
|
+
borderRadius: destination.style.borderRadius,
|
|
643
|
+
opacity: destination.style.opacity,
|
|
644
|
+
transform: destination.style.transform,
|
|
645
|
+
transition: destination.style.transition,
|
|
646
|
+
transformOrigin: destination.style.transformOrigin,
|
|
647
|
+
willChange: destination.style.willChange
|
|
648
|
+
};
|
|
649
|
+
let firstFrame = 0;
|
|
650
|
+
let secondFrame = 0;
|
|
651
|
+
let finishTimer = 0;
|
|
652
|
+
let finished = false;
|
|
653
|
+
const cleanup = () => {
|
|
654
|
+
if (finished) return;
|
|
655
|
+
finished = true;
|
|
656
|
+
window.cancelAnimationFrame(firstFrame);
|
|
657
|
+
window.cancelAnimationFrame(secondFrame);
|
|
658
|
+
window.clearTimeout(finishTimer);
|
|
659
|
+
destination.removeEventListener("transitionend", handleTransitionEnd);
|
|
660
|
+
Object.assign(destination.style, previous);
|
|
661
|
+
root.classList.remove("lightbox-morph-opening");
|
|
662
|
+
if (openingCleanupRef.current === cleanup) openingCleanupRef.current = null;
|
|
663
|
+
};
|
|
664
|
+
const handleTransitionEnd = (event) => {
|
|
665
|
+
if (event.target === destination && event.propertyName === "transform") cleanup();
|
|
666
|
+
};
|
|
667
|
+
Object.assign(destination.style, {
|
|
668
|
+
borderRadius: fromRadius,
|
|
669
|
+
opacity: "0.2",
|
|
670
|
+
transform: `translate3d(${translateX}px, ${translateY}px, 0) scale(${scale}) ${previous.transform}`.trim(),
|
|
671
|
+
transformOrigin: "center",
|
|
672
|
+
transition: "none",
|
|
673
|
+
willChange: "transform, opacity"
|
|
674
|
+
});
|
|
675
|
+
destination.addEventListener("transitionend", handleTransitionEnd);
|
|
676
|
+
openingCleanupRef.current = cleanup;
|
|
677
|
+
firstFrame = window.requestAnimationFrame(() => {
|
|
678
|
+
secondFrame = window.requestAnimationFrame(() => {
|
|
679
|
+
destination.style.transition = "transform 260ms cubic-bezier(0.22, 1, 0.36, 1), opacity 220ms ease-out, border-radius 260ms cubic-bezier(0.22, 1, 0.36, 1)";
|
|
680
|
+
destination.style.transform = previous.transform;
|
|
681
|
+
destination.style.opacity = previous.opacity;
|
|
682
|
+
destination.style.borderRadius = previous.borderRadius;
|
|
683
|
+
finishTimer = window.setTimeout(cleanup, 320);
|
|
684
|
+
});
|
|
685
|
+
});
|
|
686
|
+
}, [
|
|
687
|
+
activeIndex,
|
|
688
|
+
activeItem,
|
|
689
|
+
commitOpenChange,
|
|
690
|
+
shouldReduceMotion,
|
|
691
|
+
stopZoomAnimation
|
|
692
|
+
]);
|
|
693
|
+
const closeWithDetails = React$1.useCallback((details) => {
|
|
694
|
+
runOpenChange(false, details, sourceMapRef.current.get(activeIndex));
|
|
695
|
+
}, [activeIndex, runOpenChange]);
|
|
696
|
+
const requestOpen = React$1.useCallback((nextIndex, source, event) => {
|
|
697
|
+
let nextItems = items;
|
|
698
|
+
let nextSources = [];
|
|
699
|
+
if (!itemsProp) {
|
|
700
|
+
const scan = scanMedia();
|
|
701
|
+
nextItems = scan.foundItems;
|
|
702
|
+
nextSources = scan.sources;
|
|
703
|
+
sourceMapRef.current.clear();
|
|
704
|
+
nextSources.forEach((element, sourceIndex) => {
|
|
705
|
+
sourceMapRef.current.set(sourceIndex, {
|
|
706
|
+
element,
|
|
707
|
+
morph: true
|
|
708
|
+
});
|
|
709
|
+
});
|
|
710
|
+
flushSync(() => setAutoItems(nextItems));
|
|
711
|
+
}
|
|
712
|
+
if (nextItems.length === 0) return;
|
|
713
|
+
const normalized = normalizeIndex(nextIndex, nextItems.length, loop);
|
|
714
|
+
if (!isIndexControlled) flushSync(() => setUncontrolledIndex(normalized));
|
|
715
|
+
if (normalized !== activeIndex) onChangeImage?.(normalized, nextItems[normalized], {
|
|
716
|
+
reason: "trigger-press",
|
|
717
|
+
event,
|
|
718
|
+
previousIndex: activeIndex
|
|
719
|
+
});
|
|
720
|
+
if (source) sourceMapRef.current.set(normalized, source);
|
|
721
|
+
const details = getEventDetails("trigger-press", event, source?.element ?? nextSources[normalized]);
|
|
722
|
+
runOpenChange(true, details, source ?? sourceMapRef.current.get(normalized), normalized, nextItems[normalized]);
|
|
723
|
+
}, [
|
|
724
|
+
activeIndex,
|
|
725
|
+
isIndexControlled,
|
|
726
|
+
items,
|
|
727
|
+
itemsProp,
|
|
728
|
+
loop,
|
|
729
|
+
onChangeImage,
|
|
730
|
+
runOpenChange,
|
|
731
|
+
scanMedia
|
|
732
|
+
]);
|
|
733
|
+
const handleOpenChange = React$1.useCallback((nextOpen, details) => {
|
|
734
|
+
if (nextOpen) runOpenChange(true, details, sourceMapRef.current.get(activeIndex));
|
|
735
|
+
else closeWithDetails(details);
|
|
736
|
+
}, [
|
|
737
|
+
activeIndex,
|
|
738
|
+
closeWithDetails,
|
|
739
|
+
runOpenChange
|
|
740
|
+
]);
|
|
741
|
+
const handleOpenChangeComplete = React$1.useCallback((nextOpen) => {
|
|
742
|
+
if (!nextOpen && transformRef.current.dismissY === 0) {
|
|
743
|
+
stopZoomAnimation();
|
|
744
|
+
setTransform(initialTransform);
|
|
745
|
+
}
|
|
746
|
+
onOpenChangeComplete?.(nextOpen);
|
|
747
|
+
}, [onOpenChangeComplete, stopZoomAnimation]);
|
|
748
|
+
const registerSource = React$1.useCallback((sourceIndex, source) => {
|
|
749
|
+
if (source) sourceMapRef.current.set(sourceIndex, source);
|
|
750
|
+
else sourceMapRef.current.delete(sourceIndex);
|
|
751
|
+
}, []);
|
|
752
|
+
const registerMedia = React$1.useCallback((element) => {
|
|
753
|
+
mediaRef.current = element;
|
|
754
|
+
}, []);
|
|
755
|
+
const activeZoomable = activeItem?.type === "image" ? activeItem.zoomable !== false : Boolean(activeItem?.zoomable);
|
|
756
|
+
const actions = React$1.useMemo(() => {
|
|
757
|
+
return {
|
|
758
|
+
open: (nextIndex = activeIndex) => requestOpen(nextIndex),
|
|
759
|
+
close: () => closeWithDetails(getEventDetails("imperative-action", void 0, null)),
|
|
760
|
+
goTo: (nextIndex) => changeIndex(nextIndex, "programmatic"),
|
|
761
|
+
next: () => changeIndex(activeIndex + 1, "next-press"),
|
|
762
|
+
previous: () => changeIndex(activeIndex - 1, "previous-press"),
|
|
763
|
+
zoomIn: () => {
|
|
764
|
+
if (!activeZoomable) return;
|
|
765
|
+
const current = transformRef.current;
|
|
766
|
+
animateZoom({
|
|
767
|
+
scale: Math.min(maxZoom, current.scale < 2 ? 2 : current.scale + 1),
|
|
768
|
+
panX: current.panX,
|
|
769
|
+
panY: current.panY
|
|
770
|
+
});
|
|
771
|
+
},
|
|
772
|
+
zoomOut: () => {
|
|
773
|
+
const current = transformRef.current;
|
|
774
|
+
const scale = Math.max(1, current.scale - 1);
|
|
775
|
+
const panRatio = current.scale > 1 ? Math.max(0, (scale - 1) / (current.scale - 1)) : 0;
|
|
776
|
+
animateZoom({
|
|
777
|
+
scale,
|
|
778
|
+
panX: current.panX * panRatio,
|
|
779
|
+
panY: current.panY * panRatio
|
|
780
|
+
});
|
|
781
|
+
},
|
|
782
|
+
resetZoom: () => animateZoom({
|
|
783
|
+
scale: 1,
|
|
784
|
+
panX: 0,
|
|
785
|
+
panY: 0
|
|
786
|
+
})
|
|
787
|
+
};
|
|
788
|
+
}, [
|
|
789
|
+
activeIndex,
|
|
790
|
+
activeZoomable,
|
|
791
|
+
animateZoom,
|
|
792
|
+
changeIndex,
|
|
793
|
+
closeWithDetails,
|
|
794
|
+
maxZoom,
|
|
795
|
+
requestOpen
|
|
796
|
+
]);
|
|
797
|
+
React$1.useImperativeHandle(actionsRef, () => actions, [actions]);
|
|
798
|
+
const context = React$1.useMemo(() => {
|
|
799
|
+
return {
|
|
800
|
+
activeIndex,
|
|
801
|
+
activeItem,
|
|
802
|
+
actions,
|
|
803
|
+
armPostGestureActivationRepair,
|
|
804
|
+
animateZoom,
|
|
805
|
+
changeIndex,
|
|
806
|
+
closeWithDetails,
|
|
807
|
+
controls,
|
|
808
|
+
direction,
|
|
809
|
+
getSource: (sourceIndex) => sourceMapRef.current.get(sourceIndex),
|
|
810
|
+
items,
|
|
811
|
+
loadedAssets,
|
|
812
|
+
loading,
|
|
813
|
+
loop,
|
|
814
|
+
markAssetLoaded,
|
|
815
|
+
maxZoom,
|
|
816
|
+
noCarousel,
|
|
817
|
+
noControls,
|
|
818
|
+
noCounter,
|
|
819
|
+
open,
|
|
820
|
+
overlay,
|
|
821
|
+
preload: noCarousel ? 0 : Math.max(0, Math.floor(preload)),
|
|
822
|
+
registerMedia,
|
|
823
|
+
registerSource,
|
|
824
|
+
requestOpen,
|
|
825
|
+
setTransform,
|
|
826
|
+
shouldReduceMotion,
|
|
827
|
+
stopZoomAnimation,
|
|
828
|
+
transform
|
|
829
|
+
};
|
|
830
|
+
}, [
|
|
831
|
+
activeIndex,
|
|
832
|
+
activeItem,
|
|
833
|
+
actions,
|
|
834
|
+
armPostGestureActivationRepair,
|
|
835
|
+
animateZoom,
|
|
836
|
+
changeIndex,
|
|
837
|
+
closeWithDetails,
|
|
838
|
+
controls,
|
|
839
|
+
direction,
|
|
840
|
+
items,
|
|
841
|
+
loadedAssets,
|
|
842
|
+
loading,
|
|
843
|
+
loop,
|
|
844
|
+
markAssetLoaded,
|
|
845
|
+
maxZoom,
|
|
846
|
+
noCarousel,
|
|
847
|
+
noControls,
|
|
848
|
+
noCounter,
|
|
849
|
+
open,
|
|
850
|
+
overlay,
|
|
851
|
+
preload,
|
|
852
|
+
registerMedia,
|
|
853
|
+
registerSource,
|
|
854
|
+
requestOpen,
|
|
855
|
+
shouldReduceMotion,
|
|
856
|
+
stopZoomAnimation,
|
|
857
|
+
transform
|
|
858
|
+
]);
|
|
859
|
+
const handleGalleryClick = (event) => {
|
|
860
|
+
if (event.defaultPrevented || itemsProp) return;
|
|
861
|
+
const gallery = galleryRef.current;
|
|
862
|
+
if (!gallery) return;
|
|
863
|
+
const media = getStandaloneGalleryMedia(event.target, gallery);
|
|
864
|
+
if (!media) return;
|
|
865
|
+
const suppressedClick = suppressedGalleryClickRef.current;
|
|
866
|
+
suppressedGalleryClickRef.current = null;
|
|
867
|
+
if (event.detail !== 0 && suppressedClick?.media === media && performance.now() - suppressedClick.time < 700) return;
|
|
868
|
+
openGalleryMedia(media, event.nativeEvent);
|
|
869
|
+
};
|
|
870
|
+
const handleGalleryPointerDown = (event) => {
|
|
871
|
+
suppressedGalleryClickRef.current = null;
|
|
872
|
+
if (itemsProp || event.pointerType !== "touch" || !event.isPrimary || event.button !== 0) {
|
|
873
|
+
galleryTouchPressRef.current = null;
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
const gallery = galleryRef.current;
|
|
877
|
+
if (!gallery) return;
|
|
878
|
+
const media = getStandaloneGalleryMedia(event.target, gallery);
|
|
879
|
+
galleryTouchPressRef.current = media ? {
|
|
880
|
+
pointerId: event.pointerId,
|
|
881
|
+
media,
|
|
882
|
+
startX: event.clientX,
|
|
883
|
+
startY: event.clientY
|
|
884
|
+
} : null;
|
|
885
|
+
};
|
|
886
|
+
const handleGalleryPointerMove = (event) => {
|
|
887
|
+
const press = galleryTouchPressRef.current;
|
|
888
|
+
if (!press || press.pointerId !== event.pointerId || Math.hypot(event.clientX - press.startX, event.clientY - press.startY) <= 8) return;
|
|
889
|
+
galleryTouchPressRef.current = null;
|
|
890
|
+
};
|
|
891
|
+
const handleGalleryPointerUp = (event) => {
|
|
892
|
+
const press = galleryTouchPressRef.current;
|
|
893
|
+
galleryTouchPressRef.current = null;
|
|
894
|
+
if (!press || press.pointerId !== event.pointerId || Math.hypot(event.clientX - press.startX, event.clientY - press.startY) > 8) return;
|
|
895
|
+
const gallery = galleryRef.current;
|
|
896
|
+
if (!gallery) return;
|
|
897
|
+
const media = getStandaloneGalleryMedia(event.target, gallery);
|
|
898
|
+
if (media !== press.media) return;
|
|
899
|
+
suppressedGalleryClickRef.current = {
|
|
900
|
+
media,
|
|
901
|
+
time: performance.now()
|
|
902
|
+
};
|
|
903
|
+
openGalleryMedia(media, event.nativeEvent);
|
|
904
|
+
};
|
|
905
|
+
function openGalleryMedia(media, event) {
|
|
906
|
+
const gallery = galleryRef.current;
|
|
907
|
+
if (!gallery) return;
|
|
908
|
+
const sourceIndex = Array.from(gallery.querySelectorAll("img,video")).filter((item) => isStandaloneMedia(item, gallery)).indexOf(media);
|
|
909
|
+
if (sourceIndex < 0) return;
|
|
910
|
+
requestOpen(sourceIndex, {
|
|
911
|
+
element: media,
|
|
912
|
+
morph: true
|
|
913
|
+
}, event);
|
|
914
|
+
}
|
|
915
|
+
const handleGalleryKeyDown = (event) => {
|
|
916
|
+
if (itemsProp || event.key !== "Enter" && event.key !== " ") return;
|
|
917
|
+
const gallery = galleryRef.current;
|
|
918
|
+
const target = event.target;
|
|
919
|
+
if (!gallery || !(target instanceof HTMLImageElement || target instanceof HTMLVideoElement)) return;
|
|
920
|
+
if (!isStandaloneMedia(target, gallery)) return;
|
|
921
|
+
event.preventDefault();
|
|
922
|
+
openGalleryMedia(target, event.nativeEvent);
|
|
923
|
+
};
|
|
924
|
+
const handleGalleryMediaLoad = () => {
|
|
925
|
+
if (itemsProp) return;
|
|
926
|
+
const { foundItems } = scanMedia();
|
|
927
|
+
setAutoItems(foundItems);
|
|
928
|
+
};
|
|
929
|
+
const customContent = hasCustomContent(children);
|
|
930
|
+
return /* @__PURE__ */ jsxs(LightboxContext.Provider, {
|
|
931
|
+
value: context,
|
|
932
|
+
children: [/* @__PURE__ */ jsx("style", {
|
|
933
|
+
"data-slot": "lightbox-styles",
|
|
934
|
+
children: LIGHTBOX_STYLES
|
|
935
|
+
}), /* @__PURE__ */ jsxs(Dialog.Root, {
|
|
936
|
+
modal: true,
|
|
937
|
+
open,
|
|
938
|
+
onOpenChange: handleOpenChange,
|
|
939
|
+
onOpenChangeComplete: handleOpenChangeComplete,
|
|
940
|
+
...rootProps,
|
|
941
|
+
children: [itemsProp ? children : /* @__PURE__ */ jsx("div", {
|
|
942
|
+
ref: handleGalleryRef,
|
|
943
|
+
className,
|
|
944
|
+
"data-slot": "lightbox-gallery",
|
|
945
|
+
onClick: handleGalleryClick,
|
|
946
|
+
onKeyDown: handleGalleryKeyDown,
|
|
947
|
+
onLoadCapture: handleGalleryMediaLoad,
|
|
948
|
+
onLoadedDataCapture: handleGalleryMediaLoad,
|
|
949
|
+
onPointerCancel: () => {
|
|
950
|
+
galleryTouchPressRef.current = null;
|
|
951
|
+
},
|
|
952
|
+
onPointerDown: handleGalleryPointerDown,
|
|
953
|
+
onPointerMove: handleGalleryPointerMove,
|
|
954
|
+
onPointerUp: handleGalleryPointerUp,
|
|
955
|
+
children
|
|
956
|
+
}), !customContent && /* @__PURE__ */ jsx(LightboxContent, {})]
|
|
957
|
+
})]
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
function LightboxTrigger({ index = 0, morph, onClick, ref, ...props }) {
|
|
961
|
+
const { registerSource, requestOpen } = useLightbox();
|
|
962
|
+
const sourceRef = React$1.useRef(null);
|
|
963
|
+
const mergedRef = React$1.useCallback((element) => {
|
|
964
|
+
sourceRef.current = element;
|
|
965
|
+
if (element) {
|
|
966
|
+
const inferredMorph = morph ?? element.matches("img,video,canvas,picture,[data-lightbox-morph]");
|
|
967
|
+
registerSource(index, {
|
|
968
|
+
element,
|
|
969
|
+
morph: inferredMorph
|
|
970
|
+
});
|
|
971
|
+
} else registerSource(index, null);
|
|
972
|
+
if (typeof ref === "function") return ref(element);
|
|
973
|
+
if (ref) ref.current = element;
|
|
974
|
+
}, [
|
|
975
|
+
index,
|
|
976
|
+
morph,
|
|
977
|
+
ref,
|
|
978
|
+
registerSource
|
|
979
|
+
]);
|
|
980
|
+
return /* @__PURE__ */ jsx(Dialog.Trigger, {
|
|
981
|
+
ref: mergedRef,
|
|
982
|
+
"data-slot": "lightbox-trigger",
|
|
983
|
+
onClick: (event) => {
|
|
984
|
+
onClick?.(event);
|
|
985
|
+
if (event.defaultPrevented) return;
|
|
986
|
+
const element = sourceRef.current;
|
|
987
|
+
if (!element) return;
|
|
988
|
+
const inferredMorph = morph ?? element.matches("img,video,canvas,picture,[data-lightbox-morph]");
|
|
989
|
+
registerSource(index, {
|
|
990
|
+
element,
|
|
991
|
+
morph: inferredMorph
|
|
992
|
+
});
|
|
993
|
+
event.preventBaseUIHandler();
|
|
994
|
+
requestOpen(index, {
|
|
995
|
+
element,
|
|
996
|
+
morph: inferredMorph
|
|
997
|
+
}, event.nativeEvent);
|
|
998
|
+
},
|
|
999
|
+
...props
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
function LightboxBackdrop({ className, style, ...props }) {
|
|
1003
|
+
const { overlay, transform } = useLightbox();
|
|
1004
|
+
const backdropStyle = mergeLightboxBackdropStyle({ "--lightbox-backdrop-opacity": clamp(1 - Math.abs(transform.dismissY) / Math.max(typeof window === "undefined" ? 1 : window.innerHeight || 1, 1), 0, 1) }, style);
|
|
1005
|
+
return /* @__PURE__ */ jsx(Dialog.Backdrop, {
|
|
1006
|
+
"data-slot": "lightbox-backdrop",
|
|
1007
|
+
"data-overlay": overlay,
|
|
1008
|
+
className: cn("fixed inset-0 z-60 min-h-dvh opacity-[var(--lightbox-backdrop-opacity)] transition-[opacity,backdrop-filter] duration-200", overlay === "blur" && "bg-black/80 backdrop-blur-md", overlay === "brightness" && "bg-black/90", "data-starting-style:opacity-0 data-ending-style:opacity-0 data-ending-style:pointer-events-none motion-reduce:transition-opacity", className),
|
|
1009
|
+
style: backdropStyle,
|
|
1010
|
+
...props
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
function LightboxViewport({ className, ...props }) {
|
|
1014
|
+
return /* @__PURE__ */ jsx(Dialog.Viewport, {
|
|
1015
|
+
"data-slot": "lightbox-viewport",
|
|
1016
|
+
className: cn("fixed inset-0 z-70 flex min-h-dvh items-stretch overflow-hidden overscroll-contain data-ending-style:pointer-events-none", className),
|
|
1017
|
+
...props
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
function LightboxContent({ className, children, initialFocus, onKeyDown, style, ...props }) {
|
|
1021
|
+
const { activeIndex, activeItem, actions, changeIndex, direction, items, noCarousel, noControls, noCounter, setTransform, shouldReduceMotion, transform } = useLightbox();
|
|
1022
|
+
const popupRef = React$1.useRef(null);
|
|
1023
|
+
const handleKeyDown = (event) => {
|
|
1024
|
+
onKeyDown?.(event);
|
|
1025
|
+
if (event.defaultPrevented) return;
|
|
1026
|
+
if (event.target.matches("input,textarea,select,[contenteditable=true]")) return;
|
|
1027
|
+
const rtlMultiplier = direction === "rtl" ? -1 : 1;
|
|
1028
|
+
if (!noCarousel && (event.key === "ArrowLeft" || event.key === "ArrowRight") && event.shiftKey) {
|
|
1029
|
+
event.preventDefault();
|
|
1030
|
+
const physical = event.key === "ArrowRight" ? 1 : -1;
|
|
1031
|
+
changeIndex(activeIndex + physical * rtlMultiplier, "keyboard", event.nativeEvent);
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
if (transform.scale > 1 && event.key.startsWith("Arrow")) {
|
|
1035
|
+
event.preventDefault();
|
|
1036
|
+
const amount = 40;
|
|
1037
|
+
const delta = {
|
|
1038
|
+
ArrowLeft: [amount, 0],
|
|
1039
|
+
ArrowRight: [-40, 0],
|
|
1040
|
+
ArrowUp: [0, amount],
|
|
1041
|
+
ArrowDown: [0, -40]
|
|
1042
|
+
}[event.key];
|
|
1043
|
+
if (delta) panBy(delta[0], delta[1]);
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
if (!noCarousel && transform.scale === 1 && (event.key === "ArrowLeft" || event.key === "ArrowRight")) {
|
|
1047
|
+
event.preventDefault();
|
|
1048
|
+
const physical = event.key === "ArrowRight" ? 1 : -1;
|
|
1049
|
+
changeIndex(activeIndex + physical * rtlMultiplier, "keyboard", event.nativeEvent);
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
if (event.key === "+" || event.key === "=") {
|
|
1053
|
+
event.preventDefault();
|
|
1054
|
+
actions.zoomIn();
|
|
1055
|
+
} else if (event.key === "-") {
|
|
1056
|
+
event.preventDefault();
|
|
1057
|
+
actions.zoomOut();
|
|
1058
|
+
} else if (event.key === "0") {
|
|
1059
|
+
event.preventDefault();
|
|
1060
|
+
actions.resetZoom();
|
|
1061
|
+
}
|
|
1062
|
+
};
|
|
1063
|
+
function panBy(deltaX, deltaY) {
|
|
1064
|
+
const media = popupRef.current?.querySelector("[data-slot=\"lightbox-zoom\"][data-active=\"true\"]");
|
|
1065
|
+
if (!media) return;
|
|
1066
|
+
const rect = media.getBoundingClientRect();
|
|
1067
|
+
const baseWidth = rect.width / transform.scale;
|
|
1068
|
+
const baseHeight = rect.height / transform.scale;
|
|
1069
|
+
const maxX = Math.max(0, (baseWidth * transform.scale - window.innerWidth) / 2);
|
|
1070
|
+
const maxY = Math.max(0, (baseHeight * transform.scale - window.innerHeight) / 2);
|
|
1071
|
+
setTransform((current) => {
|
|
1072
|
+
return {
|
|
1073
|
+
...current,
|
|
1074
|
+
panX: clamp(current.panX + deltaX, -maxX, maxX),
|
|
1075
|
+
panY: clamp(current.panY + deltaY, -maxY, maxY)
|
|
1076
|
+
};
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
return /* @__PURE__ */ jsxs(Dialog.Portal, {
|
|
1080
|
+
"data-slot": "lightbox-portal",
|
|
1081
|
+
container: typeof document === "undefined" ? void 0 : document.body,
|
|
1082
|
+
children: [/* @__PURE__ */ jsx(LightboxBackdrop, { forceRender: true }), /* @__PURE__ */ jsx(LightboxViewport, {
|
|
1083
|
+
dir: direction,
|
|
1084
|
+
"data-lightbox-motion": shouldReduceMotion ? "reduced" : "full",
|
|
1085
|
+
children: /* @__PURE__ */ jsxs(Dialog.Popup, {
|
|
1086
|
+
ref: popupRef,
|
|
1087
|
+
"data-slot": "lightbox-content",
|
|
1088
|
+
"data-dragging": transform.dragging ? "true" : "false",
|
|
1089
|
+
"aria-describedby": activeItem?.caption ? `lightbox-caption-${activeIndex}` : void 0,
|
|
1090
|
+
initialFocus: initialFocus ?? (() => popupRef.current?.querySelector("[data-slot=\"lightbox-close\"]") ?? popupRef.current),
|
|
1091
|
+
className: cn("relative flex size-full min-h-0 min-w-0 flex-col overflow-hidden bg-transparent text-white outline-none", "transition-[opacity,scale] duration-200 ease-out data-starting-style:scale-[0.98] data-starting-style:opacity-0 data-ending-style:scale-[0.98] data-ending-style:opacity-0 data-ending-style:pointer-events-none", "motion-reduce:transition-opacity motion-reduce:transform-none", className),
|
|
1092
|
+
style: mergeLightboxContentStyle({ "--lightbox-header-inset": "max(5rem, calc(env(safe-area-inset-top) + 4rem))" }, style),
|
|
1093
|
+
onKeyDown: handleKeyDown,
|
|
1094
|
+
...props,
|
|
1095
|
+
children: [
|
|
1096
|
+
/* @__PURE__ */ jsx(Dialog.Title, {
|
|
1097
|
+
className: "sr-only",
|
|
1098
|
+
children: "Media viewer"
|
|
1099
|
+
}),
|
|
1100
|
+
children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1101
|
+
(!noCounter || !noControls) && /* @__PURE__ */ jsxs("div", {
|
|
1102
|
+
className: "pointer-events-none absolute inset-x-0 top-0 z-30 flex items-start justify-between gap-4 p-3 pt-[max(0.75rem,env(safe-area-inset-top))] sm:p-4",
|
|
1103
|
+
children: [!noCounter && /* @__PURE__ */ jsx(LightboxCounter, {}), !noControls && /* @__PURE__ */ jsx(LightboxToolbar, { className: cn(noCounter && "ms-auto") })]
|
|
1104
|
+
}),
|
|
1105
|
+
/* @__PURE__ */ jsx(LightboxSlides, {}),
|
|
1106
|
+
!noCarousel && items.length > 1 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(LightboxPrevious, {}), /* @__PURE__ */ jsx(LightboxNext, {})] }),
|
|
1107
|
+
(Boolean(activeItem?.caption) || !noCarousel && items.length > 1) && /* @__PURE__ */ jsxs("div", {
|
|
1108
|
+
className: "pointer-events-none absolute inset-x-0 bottom-0 z-30 flex flex-col items-center gap-3 px-3 pb-[max(0.75rem,env(safe-area-inset-bottom))] sm:px-4",
|
|
1109
|
+
children: [/* @__PURE__ */ jsx(LightboxCaptions, {}), !noCarousel && items.length > 1 && /* @__PURE__ */ jsx(LightboxThumbnails, {})]
|
|
1110
|
+
})
|
|
1111
|
+
] }),
|
|
1112
|
+
/* @__PURE__ */ jsx("span", {
|
|
1113
|
+
className: "sr-only",
|
|
1114
|
+
role: "status",
|
|
1115
|
+
"aria-live": "polite",
|
|
1116
|
+
"aria-atomic": "true",
|
|
1117
|
+
children: items.length > 0 ? `${getItemLabel(activeItem, activeIndex)}, ${activeIndex + 1} of ${items.length}` : "No media"
|
|
1118
|
+
})
|
|
1119
|
+
]
|
|
1120
|
+
})
|
|
1121
|
+
})]
|
|
1122
|
+
});
|
|
1123
|
+
}
|
|
1124
|
+
LightboxContent.__lightboxContent = true;
|
|
1125
|
+
function LightboxZoom({ active = true, className, render, style, ref, ...props }) {
|
|
1126
|
+
const { activeItem, registerMedia, transform } = useLightbox();
|
|
1127
|
+
const zoomable = activeItem?.type === "image" ? activeItem.zoomable !== false : Boolean(activeItem?.zoomable);
|
|
1128
|
+
const mediaRegistrationRef = React$1.useCallback((element) => {
|
|
1129
|
+
if (active) registerMedia(element);
|
|
1130
|
+
}, [active, registerMedia]);
|
|
1131
|
+
const defaultProps = {
|
|
1132
|
+
className: cn("flex max-h-full max-w-full origin-center items-center justify-center will-change-transform", className),
|
|
1133
|
+
"data-active": active ? "true" : "false",
|
|
1134
|
+
"data-dragging": transform.dragging ? "true" : "false",
|
|
1135
|
+
"data-slot": "lightbox-zoom",
|
|
1136
|
+
"data-zoomable": zoomable ? "true" : "false",
|
|
1137
|
+
"data-zoomed": transform.scale > 1 ? "true" : "false",
|
|
1138
|
+
style: {
|
|
1139
|
+
transform: active ? `translate3d(${transform.panX}px, ${transform.panY}px, 0) scale(${transform.scale})` : void 0,
|
|
1140
|
+
...style
|
|
1141
|
+
}
|
|
1142
|
+
};
|
|
1143
|
+
return useRender({
|
|
1144
|
+
defaultTagName: "div",
|
|
1145
|
+
ref: ref ? [ref, mediaRegistrationRef] : mediaRegistrationRef,
|
|
1146
|
+
props: mergeProps(defaultProps, props),
|
|
1147
|
+
render
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
function LightboxSlide({ index, active = false, className, style, ...props }) {
|
|
1151
|
+
const { activeItem, items, loadedAssets, loading, markAssetLoaded, noCarousel, noControls, noCounter } = useLightbox();
|
|
1152
|
+
const item = items[index];
|
|
1153
|
+
const [failedLoadKey, setFailedLoadKey] = React$1.useState(null);
|
|
1154
|
+
if (!item) return null;
|
|
1155
|
+
const loadKey = getMediaLoadKey(item);
|
|
1156
|
+
const mediaFailed = failedLoadKey === loadKey;
|
|
1157
|
+
const mediaLoaded = loadedAssets.has(loadKey);
|
|
1158
|
+
const shouldMountMedia = loading === "eager" || active || mediaLoaded;
|
|
1159
|
+
const hasTopChrome = !noCounter || !noControls;
|
|
1160
|
+
const hasBottomChrome = Boolean(activeItem?.caption);
|
|
1161
|
+
const captionTrack = item.type === "video" ? item.tracks?.find((track) => track.kind === "captions") : void 0;
|
|
1162
|
+
const media = item.type === "image" ? /* @__PURE__ */ jsx("img", {
|
|
1163
|
+
src: item.src,
|
|
1164
|
+
srcSet: item.srcSet,
|
|
1165
|
+
sizes: item.sizes,
|
|
1166
|
+
alt: item.alt,
|
|
1167
|
+
width: item.width,
|
|
1168
|
+
height: item.height,
|
|
1169
|
+
draggable: false,
|
|
1170
|
+
decoding: "async",
|
|
1171
|
+
loading: active ? "eager" : "lazy",
|
|
1172
|
+
onLoad: () => {
|
|
1173
|
+
setFailedLoadKey(null);
|
|
1174
|
+
markAssetLoaded(loadKey);
|
|
1175
|
+
},
|
|
1176
|
+
onError: () => setFailedLoadKey(loadKey),
|
|
1177
|
+
className: cn("block h-auto w-[min(82vw,64rem)] max-w-[calc(100vw-2rem)] select-none rounded-lg object-contain", "max-h-[calc(100dvh-11rem)] sm:max-h-[calc(100dvh-12rem)]", "transition-opacity duration-200", !mediaLoaded && "opacity-0", item.className)
|
|
1178
|
+
}) : /* @__PURE__ */ jsxs("video", {
|
|
1179
|
+
...item.videoProps,
|
|
1180
|
+
src: item.src,
|
|
1181
|
+
poster: item.poster,
|
|
1182
|
+
width: item.width,
|
|
1183
|
+
height: item.height,
|
|
1184
|
+
"aria-label": item.label,
|
|
1185
|
+
controls: item.videoProps?.controls ?? true,
|
|
1186
|
+
muted: item.videoProps?.muted ?? false,
|
|
1187
|
+
onError: (event) => {
|
|
1188
|
+
item.videoProps?.onError?.(event);
|
|
1189
|
+
setFailedLoadKey(loadKey);
|
|
1190
|
+
},
|
|
1191
|
+
onCanPlay: (event) => {
|
|
1192
|
+
item.videoProps?.onCanPlay?.(event);
|
|
1193
|
+
setFailedLoadKey(null);
|
|
1194
|
+
markAssetLoaded(loadKey);
|
|
1195
|
+
},
|
|
1196
|
+
onLoadedData: (event) => {
|
|
1197
|
+
item.videoProps?.onLoadedData?.(event);
|
|
1198
|
+
setFailedLoadKey(null);
|
|
1199
|
+
markAssetLoaded(loadKey);
|
|
1200
|
+
},
|
|
1201
|
+
onLoadedMetadata: (event) => {
|
|
1202
|
+
item.videoProps?.onLoadedMetadata?.(event);
|
|
1203
|
+
markAssetLoaded(loadKey);
|
|
1204
|
+
},
|
|
1205
|
+
playsInline: item.videoProps?.playsInline ?? true,
|
|
1206
|
+
preload: item.videoProps?.preload ?? (active ? "auto" : "metadata"),
|
|
1207
|
+
className: cn("block h-auto w-[min(82vw,64rem)] max-w-[calc(100vw-2rem)] rounded-lg bg-black object-contain", "max-h-[calc(100dvh-11rem)] sm:max-h-[calc(100dvh-12rem)]", "transition-opacity duration-200", !mediaLoaded && "opacity-0", item.className),
|
|
1208
|
+
children: [
|
|
1209
|
+
item.sources?.map((source) => /* @__PURE__ */ jsx("source", { ...source }, `${source.src}-${source.type}`)),
|
|
1210
|
+
/* @__PURE__ */ jsx("track", {
|
|
1211
|
+
...captionTrack,
|
|
1212
|
+
kind: "captions",
|
|
1213
|
+
label: captionTrack?.label ?? "Captions",
|
|
1214
|
+
src: captionTrack?.src ?? EMPTY_CAPTIONS_TRACK,
|
|
1215
|
+
srcLang: captionTrack?.srcLang ?? "en"
|
|
1216
|
+
}),
|
|
1217
|
+
item.tracks?.filter((track) => track !== captionTrack).map((track) => /* @__PURE__ */ jsx("track", { ...track }, `${track.src}-${track.kind}-${track.srcLang}`))
|
|
1218
|
+
]
|
|
1219
|
+
});
|
|
1220
|
+
const content = /* @__PURE__ */ jsxs("div", {
|
|
1221
|
+
className: "relative flex min-h-10 min-w-10 max-h-full max-w-full items-center justify-center",
|
|
1222
|
+
children: [mediaFailed ? /* @__PURE__ */ jsx("span", {
|
|
1223
|
+
role: "status",
|
|
1224
|
+
className: "px-4 py-8 text-sm text-white",
|
|
1225
|
+
children: item.type === "image" ? "Unable to load image" : "Unable to load video"
|
|
1226
|
+
}) : !mediaLoaded && /* @__PURE__ */ jsx("span", {
|
|
1227
|
+
className: "absolute inset-0 flex items-center justify-center",
|
|
1228
|
+
children: /* @__PURE__ */ jsx(Spinner, { className: "size-10 text-white" })
|
|
1229
|
+
}), shouldMountMedia && /* @__PURE__ */ jsx("div", {
|
|
1230
|
+
className: cn("flex max-h-full max-w-full items-center justify-center", mediaFailed && "hidden"),
|
|
1231
|
+
children: media
|
|
1232
|
+
})]
|
|
1233
|
+
});
|
|
1234
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1235
|
+
"data-slot": "lightbox-slide",
|
|
1236
|
+
"data-active": active ? "true" : "false",
|
|
1237
|
+
"data-type": item.type,
|
|
1238
|
+
role: "group",
|
|
1239
|
+
"aria-roledescription": "slide",
|
|
1240
|
+
"aria-label": `${index + 1} of ${items.length}: ${getItemLabel(item, index)}`,
|
|
1241
|
+
"aria-hidden": active ? void 0 : true,
|
|
1242
|
+
inert: active ? void 0 : true,
|
|
1243
|
+
className: cn("pointer-events-none absolute inset-0 flex items-center justify-center px-4", noCarousel ? hasTopChrome || hasBottomChrome ? "pt-[var(--lightbox-header-inset,5rem)] pb-20" : "py-4" : "pt-[var(--lightbox-header-inset,5rem)] pb-36", active && "pointer-events-auto", className),
|
|
1244
|
+
style,
|
|
1245
|
+
...props,
|
|
1246
|
+
children: /* @__PURE__ */ jsx(LightboxZoom, {
|
|
1247
|
+
active,
|
|
1248
|
+
children: content
|
|
1249
|
+
})
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
function relativeSlideOffset(index, activeIndex, length, loop, preferredOffset = 0) {
|
|
1253
|
+
let offset = index - activeIndex;
|
|
1254
|
+
if (!loop || length < 2) return offset;
|
|
1255
|
+
if (offset > length / 2) offset -= length;
|
|
1256
|
+
if (offset < -length / 2) offset += length;
|
|
1257
|
+
if (length === 2 && Math.abs(offset) === 1 && preferredOffset !== 0) return preferredOffset;
|
|
1258
|
+
return offset;
|
|
1259
|
+
}
|
|
1260
|
+
function isIndexMounted(index, activeIndex, length, preload, loop) {
|
|
1261
|
+
return Math.abs(relativeSlideOffset(index, activeIndex, length, loop)) <= preload;
|
|
1262
|
+
}
|
|
1263
|
+
function LightboxSlides({ className, onClick, onKeyDown, onPointerDown, onPointerMove, onPointerUp, onPointerCancel, onWheel, onDoubleClick, ...props }) {
|
|
1264
|
+
const { activeIndex, activeItem, armPostGestureActivationRepair, animateZoom, changeIndex, closeWithDetails, direction, items, loading, loop, maxZoom, noCarousel, preload, setTransform, shouldReduceMotion, stopZoomAnimation, transform } = useLightbox();
|
|
1265
|
+
const viewportRef = React$1.useRef(null);
|
|
1266
|
+
const pointersRef = React$1.useRef(/* @__PURE__ */ new Map());
|
|
1267
|
+
const sessionRef = React$1.useRef(null);
|
|
1268
|
+
const animationRef = React$1.useRef(null);
|
|
1269
|
+
const wheelRef = React$1.useRef({
|
|
1270
|
+
x: 0,
|
|
1271
|
+
y: 0,
|
|
1272
|
+
timer: 0
|
|
1273
|
+
});
|
|
1274
|
+
const lastTapRef = React$1.useRef({
|
|
1275
|
+
time: 0,
|
|
1276
|
+
x: 0,
|
|
1277
|
+
y: 0
|
|
1278
|
+
});
|
|
1279
|
+
const suppressClickRef = React$1.useRef(false);
|
|
1280
|
+
const suppressClickTimerRef = React$1.useRef(0);
|
|
1281
|
+
const transformRef = React$1.useRef(transform);
|
|
1282
|
+
React$1.useLayoutEffect(() => {
|
|
1283
|
+
suppressClickRef.current = true;
|
|
1284
|
+
window.clearTimeout(suppressClickTimerRef.current);
|
|
1285
|
+
suppressClickTimerRef.current = window.setTimeout(() => {
|
|
1286
|
+
suppressClickRef.current = false;
|
|
1287
|
+
}, 0);
|
|
1288
|
+
}, []);
|
|
1289
|
+
React$1.useLayoutEffect(() => {
|
|
1290
|
+
transformRef.current = transform;
|
|
1291
|
+
}, [transform]);
|
|
1292
|
+
const zoomable = activeItem?.type === "image" ? activeItem.zoomable !== false : Boolean(activeItem?.zoomable);
|
|
1293
|
+
const clickZoomable = activeItem?.type === "image" && zoomable;
|
|
1294
|
+
React$1.useEffect(() => {
|
|
1295
|
+
const animation = animationRef;
|
|
1296
|
+
const wheel = wheelRef;
|
|
1297
|
+
const pointers = pointersRef;
|
|
1298
|
+
const session = sessionRef;
|
|
1299
|
+
return () => {
|
|
1300
|
+
animation.current?.stop();
|
|
1301
|
+
window.clearTimeout(wheel.current.timer);
|
|
1302
|
+
window.clearTimeout(suppressClickTimerRef.current);
|
|
1303
|
+
wheel.current = {
|
|
1304
|
+
x: 0,
|
|
1305
|
+
y: 0,
|
|
1306
|
+
timer: 0
|
|
1307
|
+
};
|
|
1308
|
+
pointers.current.clear();
|
|
1309
|
+
session.current = null;
|
|
1310
|
+
};
|
|
1311
|
+
}, []);
|
|
1312
|
+
React$1.useEffect(() => {
|
|
1313
|
+
if (loading === "lazy") return;
|
|
1314
|
+
for (let distance = 1; distance <= preload; distance += 1) for (const candidate of [activeIndex - distance, activeIndex + distance]) {
|
|
1315
|
+
const normalized = normalizeIndex(candidate, items.length, loop);
|
|
1316
|
+
const item = items[normalized];
|
|
1317
|
+
if (item?.type !== "image") continue;
|
|
1318
|
+
const image = new Image();
|
|
1319
|
+
image.srcset = item.srcSet ?? "";
|
|
1320
|
+
image.sizes = item.sizes ?? "";
|
|
1321
|
+
image.src = item.src;
|
|
1322
|
+
image.decode?.().catch(() => void 0);
|
|
1323
|
+
}
|
|
1324
|
+
}, [
|
|
1325
|
+
activeIndex,
|
|
1326
|
+
items,
|
|
1327
|
+
loading,
|
|
1328
|
+
loop,
|
|
1329
|
+
preload
|
|
1330
|
+
]);
|
|
1331
|
+
const getPanBounds = React$1.useCallback((scale) => {
|
|
1332
|
+
const media = viewportRef.current?.querySelector("[data-slot=\"lightbox-zoom\"][data-active=\"true\"]");
|
|
1333
|
+
const viewport = viewportRef.current;
|
|
1334
|
+
if (!media || !viewport) return {
|
|
1335
|
+
x: 0,
|
|
1336
|
+
y: 0
|
|
1337
|
+
};
|
|
1338
|
+
const rect = media.getBoundingClientRect();
|
|
1339
|
+
const currentScale = transformRef.current.scale || 1;
|
|
1340
|
+
const width = rect.width / currentScale;
|
|
1341
|
+
const height = rect.height / currentScale;
|
|
1342
|
+
return {
|
|
1343
|
+
x: Math.max(0, (width * scale - viewport.clientWidth) / 2),
|
|
1344
|
+
y: Math.max(0, (height * scale - viewport.clientHeight) / 2)
|
|
1345
|
+
};
|
|
1346
|
+
}, []);
|
|
1347
|
+
const stopAnimation = React$1.useCallback(() => {
|
|
1348
|
+
animationRef.current?.stop();
|
|
1349
|
+
animationRef.current = null;
|
|
1350
|
+
}, []);
|
|
1351
|
+
const animateTransformValue = React$1.useCallback((key, to, onComplete) => {
|
|
1352
|
+
stopAnimation();
|
|
1353
|
+
if (shouldReduceMotion) {
|
|
1354
|
+
setTransform((current) => {
|
|
1355
|
+
return {
|
|
1356
|
+
...current,
|
|
1357
|
+
[key]: to
|
|
1358
|
+
};
|
|
1359
|
+
});
|
|
1360
|
+
onComplete?.();
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
const from = transformRef.current[key];
|
|
1364
|
+
animationRef.current = animate(from, to, {
|
|
1365
|
+
duration: .24,
|
|
1366
|
+
ease: "easeOut",
|
|
1367
|
+
onUpdate: (value) => setTransform((current) => {
|
|
1368
|
+
return {
|
|
1369
|
+
...current,
|
|
1370
|
+
[key]: value
|
|
1371
|
+
};
|
|
1372
|
+
}),
|
|
1373
|
+
onComplete: () => {
|
|
1374
|
+
animationRef.current = null;
|
|
1375
|
+
onComplete?.();
|
|
1376
|
+
}
|
|
1377
|
+
});
|
|
1378
|
+
}, [
|
|
1379
|
+
setTransform,
|
|
1380
|
+
shouldReduceMotion,
|
|
1381
|
+
stopAnimation
|
|
1382
|
+
]);
|
|
1383
|
+
const getZoomTarget = React$1.useCallback((nextScale, clientX, clientY) => {
|
|
1384
|
+
const viewport = viewportRef.current;
|
|
1385
|
+
if (!viewport) return null;
|
|
1386
|
+
const current = transformRef.current;
|
|
1387
|
+
const scale = clamp(nextScale, 1, maxZoom);
|
|
1388
|
+
if (scale === 1) return {
|
|
1389
|
+
...current,
|
|
1390
|
+
scale: 1,
|
|
1391
|
+
panX: 0,
|
|
1392
|
+
panY: 0
|
|
1393
|
+
};
|
|
1394
|
+
const rect = viewport.getBoundingClientRect();
|
|
1395
|
+
const pointX = clientX - (rect.left + rect.width / 2);
|
|
1396
|
+
const pointY = clientY - (rect.top + rect.height / 2);
|
|
1397
|
+
const ratio = scale / current.scale;
|
|
1398
|
+
const bounds = getPanBounds(scale);
|
|
1399
|
+
return {
|
|
1400
|
+
...current,
|
|
1401
|
+
scale,
|
|
1402
|
+
panX: clamp(pointX - (pointX - current.panX) * ratio, -bounds.x, bounds.x),
|
|
1403
|
+
panY: clamp(pointY - (pointY - current.panY) * ratio, -bounds.y, bounds.y),
|
|
1404
|
+
swipeX: 0,
|
|
1405
|
+
dismissY: 0
|
|
1406
|
+
};
|
|
1407
|
+
}, [getPanBounds, maxZoom]);
|
|
1408
|
+
const zoomAt = React$1.useCallback((nextScale, clientX, clientY) => {
|
|
1409
|
+
const target = getZoomTarget(nextScale, clientX, clientY);
|
|
1410
|
+
if (target) {
|
|
1411
|
+
transformRef.current = target;
|
|
1412
|
+
setTransform(target);
|
|
1413
|
+
}
|
|
1414
|
+
}, [getZoomTarget, setTransform]);
|
|
1415
|
+
const animateZoomAt = React$1.useCallback((nextScale, clientX, clientY) => {
|
|
1416
|
+
const target = getZoomTarget(nextScale, clientX, clientY);
|
|
1417
|
+
if (target) animateZoom(target);
|
|
1418
|
+
}, [animateZoom, getZoomTarget]);
|
|
1419
|
+
const toggleZoomAt = React$1.useCallback((clientX, clientY) => {
|
|
1420
|
+
if (activeItem?.zoomable === false || activeItem?.type === "video" && !activeItem.zoomable) return;
|
|
1421
|
+
const nextScale = transformRef.current.scale > 1 ? 1 : Math.min(2, maxZoom);
|
|
1422
|
+
animateZoomAt(nextScale, clientX, clientY);
|
|
1423
|
+
}, [
|
|
1424
|
+
activeItem,
|
|
1425
|
+
animateZoomAt,
|
|
1426
|
+
maxZoom
|
|
1427
|
+
]);
|
|
1428
|
+
const animateNavigation = React$1.useCallback((logicalDelta, reason, event) => {
|
|
1429
|
+
if (items.length < 2) return;
|
|
1430
|
+
const nextIndex = normalizeIndex(activeIndex + logicalDelta, items.length, loop);
|
|
1431
|
+
if (nextIndex === activeIndex) {
|
|
1432
|
+
animateTransformValue("swipeX", 0);
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1435
|
+
const viewportWidth = viewportRef.current?.clientWidth ?? window.innerWidth;
|
|
1436
|
+
const rtl = direction === "rtl" ? -1 : 1;
|
|
1437
|
+
const target = -logicalDelta * rtl * viewportWidth;
|
|
1438
|
+
animateTransformValue("swipeX", target, () => {
|
|
1439
|
+
changeIndex(nextIndex, reason, event);
|
|
1440
|
+
setTransform(initialTransform);
|
|
1441
|
+
});
|
|
1442
|
+
}, [
|
|
1443
|
+
activeIndex,
|
|
1444
|
+
animateTransformValue,
|
|
1445
|
+
changeIndex,
|
|
1446
|
+
direction,
|
|
1447
|
+
items.length,
|
|
1448
|
+
loop,
|
|
1449
|
+
setTransform
|
|
1450
|
+
]);
|
|
1451
|
+
const closeFromGesture = React$1.useCallback((event) => {
|
|
1452
|
+
if (event) armPostGestureActivationRepair(event);
|
|
1453
|
+
closeWithDetails(getEventDetails("imperative-action", event, null));
|
|
1454
|
+
}, [armPostGestureActivationRepair, closeWithDetails]);
|
|
1455
|
+
const releaseGesture = React$1.useCallback((event) => {
|
|
1456
|
+
const session = sessionRef.current;
|
|
1457
|
+
if (!session) return;
|
|
1458
|
+
const viewport = viewportRef.current;
|
|
1459
|
+
const width = viewport?.clientWidth ?? window.innerWidth;
|
|
1460
|
+
const height = viewport?.clientHeight ?? window.innerHeight;
|
|
1461
|
+
const current = transformRef.current;
|
|
1462
|
+
setTransform((value) => {
|
|
1463
|
+
return {
|
|
1464
|
+
...value,
|
|
1465
|
+
dragging: false
|
|
1466
|
+
};
|
|
1467
|
+
});
|
|
1468
|
+
if (session.mode === "swipe") {
|
|
1469
|
+
if (noCarousel) {
|
|
1470
|
+
animateTransformValue("swipeX", 0);
|
|
1471
|
+
sessionRef.current = null;
|
|
1472
|
+
return;
|
|
1473
|
+
}
|
|
1474
|
+
if (Math.abs(current.swipeX) >= width * .25 || Math.abs(session.velocityX) >= .5) {
|
|
1475
|
+
const rtl = direction === "rtl" ? -1 : 1;
|
|
1476
|
+
const logicalDelta = current.swipeX < 0 ? rtl : -rtl;
|
|
1477
|
+
animateNavigation(logicalDelta, "swipe", event);
|
|
1478
|
+
} else animateTransformValue("swipeX", 0);
|
|
1479
|
+
} else if (session.mode === "dismiss") {
|
|
1480
|
+
if (Math.abs(current.dismissY) >= height * .2 || Math.abs(session.velocityY) >= .5) {
|
|
1481
|
+
const target = Math.sign(current.dismissY || session.velocityY) * height;
|
|
1482
|
+
animateTransformValue("dismissY", target);
|
|
1483
|
+
closeFromGesture(event);
|
|
1484
|
+
} else animateTransformValue("dismissY", 0);
|
|
1485
|
+
} else if (session.mode === "pan" || session.mode === "pinch") {
|
|
1486
|
+
const scale = clamp(current.scale, 1, maxZoom);
|
|
1487
|
+
const bounds = getPanBounds(scale);
|
|
1488
|
+
setTransform((value) => {
|
|
1489
|
+
return {
|
|
1490
|
+
...value,
|
|
1491
|
+
scale,
|
|
1492
|
+
panX: clamp(value.panX, -bounds.x, bounds.x),
|
|
1493
|
+
panY: clamp(value.panY, -bounds.y, bounds.y),
|
|
1494
|
+
dragging: false
|
|
1495
|
+
};
|
|
1496
|
+
});
|
|
1497
|
+
} else {
|
|
1498
|
+
animateTransformValue("swipeX", 0);
|
|
1499
|
+
animateTransformValue("dismissY", 0);
|
|
1500
|
+
}
|
|
1501
|
+
sessionRef.current = null;
|
|
1502
|
+
}, [
|
|
1503
|
+
animateNavigation,
|
|
1504
|
+
animateTransformValue,
|
|
1505
|
+
closeFromGesture,
|
|
1506
|
+
direction,
|
|
1507
|
+
getPanBounds,
|
|
1508
|
+
maxZoom,
|
|
1509
|
+
noCarousel,
|
|
1510
|
+
setTransform
|
|
1511
|
+
]);
|
|
1512
|
+
const handlePointerDown = (event) => {
|
|
1513
|
+
onPointerDown?.(event);
|
|
1514
|
+
if (event.defaultPrevented) return;
|
|
1515
|
+
const interactiveTarget = event.target.closest(interactiveSelector);
|
|
1516
|
+
if (interactiveTarget && interactiveTarget !== event.currentTarget) return;
|
|
1517
|
+
stopAnimation();
|
|
1518
|
+
stopZoomAnimation();
|
|
1519
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
1520
|
+
pointersRef.current.set(event.pointerId, {
|
|
1521
|
+
x: event.clientX,
|
|
1522
|
+
y: event.clientY
|
|
1523
|
+
});
|
|
1524
|
+
const points = Array.from(pointersRef.current.values());
|
|
1525
|
+
const first = points[0] ?? {
|
|
1526
|
+
x: event.clientX,
|
|
1527
|
+
y: event.clientY
|
|
1528
|
+
};
|
|
1529
|
+
const second = points[1] ?? first;
|
|
1530
|
+
const isPinch = points.length > 1;
|
|
1531
|
+
const pinchDistance = isPinch ? Math.hypot(second.x - first.x, second.y - first.y) : 0;
|
|
1532
|
+
const pinchCenterX = isPinch ? (first.x + second.x) / 2 : event.clientX;
|
|
1533
|
+
const pinchCenterY = isPinch ? (first.y + second.y) / 2 : event.clientY;
|
|
1534
|
+
const current = transformRef.current;
|
|
1535
|
+
const now = performance.now();
|
|
1536
|
+
sessionRef.current = {
|
|
1537
|
+
mode: isPinch ? "pinch" : "pending",
|
|
1538
|
+
startX: event.clientX,
|
|
1539
|
+
startY: event.clientY,
|
|
1540
|
+
startPanX: current.panX,
|
|
1541
|
+
startPanY: current.panY,
|
|
1542
|
+
startSwipeX: current.swipeX,
|
|
1543
|
+
startDismissY: current.dismissY,
|
|
1544
|
+
startScale: current.scale,
|
|
1545
|
+
pinchDistance,
|
|
1546
|
+
pinchCenterX,
|
|
1547
|
+
pinchCenterY,
|
|
1548
|
+
lastX: event.clientX,
|
|
1549
|
+
lastY: event.clientY,
|
|
1550
|
+
lastTime: now,
|
|
1551
|
+
velocityX: 0,
|
|
1552
|
+
velocityY: 0
|
|
1553
|
+
};
|
|
1554
|
+
setTransform((value) => {
|
|
1555
|
+
return {
|
|
1556
|
+
...value,
|
|
1557
|
+
dragging: true
|
|
1558
|
+
};
|
|
1559
|
+
});
|
|
1560
|
+
};
|
|
1561
|
+
const handlePointerMove = (event) => {
|
|
1562
|
+
onPointerMove?.(event);
|
|
1563
|
+
if (event.defaultPrevented || !pointersRef.current.has(event.pointerId)) return;
|
|
1564
|
+
pointersRef.current.set(event.pointerId, {
|
|
1565
|
+
x: event.clientX,
|
|
1566
|
+
y: event.clientY
|
|
1567
|
+
});
|
|
1568
|
+
const session = sessionRef.current;
|
|
1569
|
+
if (!session) return;
|
|
1570
|
+
const points = Array.from(pointersRef.current.values());
|
|
1571
|
+
if (points.length >= 2 && zoomable) {
|
|
1572
|
+
const [first, second] = points;
|
|
1573
|
+
const distance = Math.hypot(second.x - first.x, second.y - first.y);
|
|
1574
|
+
const centerX = (first.x + second.x) / 2;
|
|
1575
|
+
const centerY = (first.y + second.y) / 2;
|
|
1576
|
+
if (!session.pinchDistance) {
|
|
1577
|
+
session.pinchDistance = distance;
|
|
1578
|
+
session.pinchCenterX = centerX;
|
|
1579
|
+
session.pinchCenterY = centerY;
|
|
1580
|
+
session.startScale = transformRef.current.scale;
|
|
1581
|
+
}
|
|
1582
|
+
session.mode = "pinch";
|
|
1583
|
+
const rawScale = session.startScale * (distance / Math.max(session.pinchDistance, 1));
|
|
1584
|
+
const elasticScale = rawScale < 1 ? 1 - (1 - rawScale) * .25 : rawScale > maxZoom ? maxZoom + (rawScale - maxZoom) * .25 : rawScale;
|
|
1585
|
+
zoomAt(elasticScale, centerX, centerY);
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1588
|
+
const dx = event.clientX - session.startX;
|
|
1589
|
+
const dy = event.clientY - session.startY;
|
|
1590
|
+
const now = performance.now();
|
|
1591
|
+
const elapsed = Math.max(1, now - session.lastTime);
|
|
1592
|
+
session.velocityX = (event.clientX - session.lastX) / elapsed;
|
|
1593
|
+
session.velocityY = (event.clientY - session.lastY) / elapsed;
|
|
1594
|
+
session.lastX = event.clientX;
|
|
1595
|
+
session.lastY = event.clientY;
|
|
1596
|
+
session.lastTime = now;
|
|
1597
|
+
if (transformRef.current.scale > 1) session.mode = "pan";
|
|
1598
|
+
else if (session.mode === "pending" && Math.hypot(dx, dy) > 8) session.mode = Math.abs(dx) > Math.abs(dy) ? noCarousel ? "pending" : "swipe" : "dismiss";
|
|
1599
|
+
if (session.mode === "pan") {
|
|
1600
|
+
const bounds = getPanBounds(transformRef.current.scale);
|
|
1601
|
+
const rawX = session.startPanX + dx;
|
|
1602
|
+
const rawY = session.startPanY + dy;
|
|
1603
|
+
const resist = (value, maximum) => Math.abs(value) <= maximum ? value : Math.sign(value) * (maximum + (Math.abs(value) - maximum) * .35);
|
|
1604
|
+
setTransform((value) => {
|
|
1605
|
+
return {
|
|
1606
|
+
...value,
|
|
1607
|
+
panX: resist(rawX, bounds.x),
|
|
1608
|
+
panY: resist(rawY, bounds.y)
|
|
1609
|
+
};
|
|
1610
|
+
});
|
|
1611
|
+
} else if (session.mode === "swipe") setTransform((value) => {
|
|
1612
|
+
return {
|
|
1613
|
+
...value,
|
|
1614
|
+
swipeX: session.startSwipeX + dx
|
|
1615
|
+
};
|
|
1616
|
+
});
|
|
1617
|
+
else if (session.mode === "dismiss") setTransform((value) => {
|
|
1618
|
+
return {
|
|
1619
|
+
...value,
|
|
1620
|
+
dismissY: session.startDismissY + dy
|
|
1621
|
+
};
|
|
1622
|
+
});
|
|
1623
|
+
};
|
|
1624
|
+
const handlePointerEnd = (event) => {
|
|
1625
|
+
if (event.type === "pointercancel") onPointerCancel?.(event);
|
|
1626
|
+
else onPointerUp?.(event);
|
|
1627
|
+
const session = sessionRef.current;
|
|
1628
|
+
pointersRef.current.delete(event.pointerId);
|
|
1629
|
+
if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
|
|
1630
|
+
if (!session) {
|
|
1631
|
+
suppressClickRef.current = false;
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
if (pointersRef.current.size > 0) {
|
|
1635
|
+
if (session?.mode === "pinch") {
|
|
1636
|
+
const [remaining] = pointersRef.current.values();
|
|
1637
|
+
if (remaining) {
|
|
1638
|
+
const current = transformRef.current;
|
|
1639
|
+
const now = performance.now();
|
|
1640
|
+
sessionRef.current = {
|
|
1641
|
+
...session,
|
|
1642
|
+
mode: "pan",
|
|
1643
|
+
startX: remaining.x,
|
|
1644
|
+
startY: remaining.y,
|
|
1645
|
+
startPanX: current.panX,
|
|
1646
|
+
startPanY: current.panY,
|
|
1647
|
+
startSwipeX: current.swipeX,
|
|
1648
|
+
startDismissY: current.dismissY,
|
|
1649
|
+
startScale: current.scale,
|
|
1650
|
+
pinchDistance: 0,
|
|
1651
|
+
pinchCenterX: remaining.x,
|
|
1652
|
+
pinchCenterY: remaining.y,
|
|
1653
|
+
lastX: remaining.x,
|
|
1654
|
+
lastY: remaining.y,
|
|
1655
|
+
lastTime: now,
|
|
1656
|
+
velocityX: 0,
|
|
1657
|
+
velocityY: 0
|
|
1658
|
+
};
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
let shouldToggleTouchZoom = false;
|
|
1664
|
+
if (event.type !== "pointercancel" && event.pointerType === "touch" && session?.mode === "pending") {
|
|
1665
|
+
const now = performance.now();
|
|
1666
|
+
const last = lastTapRef.current;
|
|
1667
|
+
if (now - last.time < 300 && Math.hypot(event.clientX - last.x, event.clientY - last.y) < 30) {
|
|
1668
|
+
shouldToggleTouchZoom = true;
|
|
1669
|
+
lastTapRef.current.time = 0;
|
|
1670
|
+
} else lastTapRef.current = {
|
|
1671
|
+
time: now,
|
|
1672
|
+
x: event.clientX,
|
|
1673
|
+
y: event.clientY
|
|
1674
|
+
};
|
|
1675
|
+
}
|
|
1676
|
+
const shouldToggleZoomOnDesktop = event.type !== "pointercancel" && event.pointerType !== "touch" && event.button === 0 && session?.mode === "pending" && clickZoomable;
|
|
1677
|
+
suppressClickRef.current = event.type === "pointercancel" || event.pointerType === "touch" || session?.mode !== "pending" || shouldToggleZoomOnDesktop;
|
|
1678
|
+
window.clearTimeout(suppressClickTimerRef.current);
|
|
1679
|
+
if (suppressClickRef.current) suppressClickTimerRef.current = window.setTimeout(() => {
|
|
1680
|
+
suppressClickRef.current = false;
|
|
1681
|
+
}, 0);
|
|
1682
|
+
releaseGesture(event.nativeEvent);
|
|
1683
|
+
if (shouldToggleTouchZoom) toggleZoomAt(event.clientX, event.clientY);
|
|
1684
|
+
else if (shouldToggleZoomOnDesktop) toggleZoomAt(event.clientX, event.clientY);
|
|
1685
|
+
};
|
|
1686
|
+
const handleClick = (event) => {
|
|
1687
|
+
onClick?.(event);
|
|
1688
|
+
if (event.defaultPrevented) return;
|
|
1689
|
+
if (suppressClickRef.current) {
|
|
1690
|
+
suppressClickRef.current = false;
|
|
1691
|
+
return;
|
|
1692
|
+
}
|
|
1693
|
+
const target = event.target;
|
|
1694
|
+
const activeZoom = target.closest("[data-slot=\"lightbox-zoom\"][data-active=\"true\"]");
|
|
1695
|
+
const interactiveTarget = target.closest(interactiveSelector);
|
|
1696
|
+
if (!activeZoom || interactiveTarget && interactiveTarget !== event.currentTarget || !clickZoomable) return;
|
|
1697
|
+
toggleZoomAt(event.clientX, event.clientY);
|
|
1698
|
+
};
|
|
1699
|
+
const handleKeyDown = (event) => {
|
|
1700
|
+
onKeyDown?.(event);
|
|
1701
|
+
if (event.defaultPrevented || event.target !== event.currentTarget || !clickZoomable || event.key !== "Enter" && event.key !== " ") return;
|
|
1702
|
+
event.preventDefault();
|
|
1703
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
1704
|
+
toggleZoomAt(rect.left + rect.width / 2, rect.top + rect.height / 2);
|
|
1705
|
+
};
|
|
1706
|
+
const releaseWheel = React$1.useCallback((event) => {
|
|
1707
|
+
const current = wheelRef.current;
|
|
1708
|
+
const width = viewportRef.current?.clientWidth ?? window.innerWidth;
|
|
1709
|
+
const height = viewportRef.current?.clientHeight ?? window.innerHeight;
|
|
1710
|
+
if (!noCarousel && (Math.abs(current.x) > 100 || Math.abs(transformRef.current.swipeX) > width * .25)) {
|
|
1711
|
+
const rtl = direction === "rtl" ? -1 : 1;
|
|
1712
|
+
animateNavigation(current.x > 0 ? rtl : -rtl, "swipe", event);
|
|
1713
|
+
} else if (Math.abs(current.y) > 100 || Math.abs(transformRef.current.dismissY) > height * .2) {
|
|
1714
|
+
const target = Math.sign(current.y || transformRef.current.dismissY) * height;
|
|
1715
|
+
animateTransformValue("dismissY", target);
|
|
1716
|
+
closeFromGesture(event);
|
|
1717
|
+
} else {
|
|
1718
|
+
animateTransformValue("swipeX", 0);
|
|
1719
|
+
animateTransformValue("dismissY", 0);
|
|
1720
|
+
}
|
|
1721
|
+
wheelRef.current.x = 0;
|
|
1722
|
+
wheelRef.current.y = 0;
|
|
1723
|
+
}, [
|
|
1724
|
+
animateNavigation,
|
|
1725
|
+
animateTransformValue,
|
|
1726
|
+
closeFromGesture,
|
|
1727
|
+
direction,
|
|
1728
|
+
noCarousel
|
|
1729
|
+
]);
|
|
1730
|
+
const handleWheel = (event) => {
|
|
1731
|
+
onWheel?.(event);
|
|
1732
|
+
event.stopPropagation();
|
|
1733
|
+
if (event.defaultPrevented) return;
|
|
1734
|
+
const interactiveTarget = event.target.closest(interactiveSelector);
|
|
1735
|
+
if (interactiveTarget && interactiveTarget !== event.currentTarget) return;
|
|
1736
|
+
const likelyMouseWheel = event.deltaMode !== WheelEvent.DOM_DELTA_PIXEL || Math.abs(event.deltaY) >= 50 && Math.abs(event.deltaX) < 8;
|
|
1737
|
+
if (event.altKey || likelyMouseWheel) {
|
|
1738
|
+
if (!zoomable) return;
|
|
1739
|
+
event.preventDefault();
|
|
1740
|
+
const sensitivity = event.altKey ? 8e-4 : .0016;
|
|
1741
|
+
const scale = transformRef.current.scale * Math.exp(-event.deltaY * sensitivity);
|
|
1742
|
+
animateZoomAt(scale, event.clientX, event.clientY);
|
|
1743
|
+
return;
|
|
1744
|
+
}
|
|
1745
|
+
event.preventDefault();
|
|
1746
|
+
if (transformRef.current.scale > 1) {
|
|
1747
|
+
const bounds = getPanBounds(transformRef.current.scale);
|
|
1748
|
+
setTransform((value) => {
|
|
1749
|
+
return {
|
|
1750
|
+
...value,
|
|
1751
|
+
panX: clamp(value.panX - event.deltaX, -bounds.x, bounds.x),
|
|
1752
|
+
panY: clamp(value.panY - event.deltaY, -bounds.y, bounds.y)
|
|
1753
|
+
};
|
|
1754
|
+
});
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
wheelRef.current.x += event.deltaX;
|
|
1758
|
+
wheelRef.current.y += event.deltaY;
|
|
1759
|
+
if (!noCarousel && Math.abs(wheelRef.current.x) > Math.abs(wheelRef.current.y)) setTransform((value) => {
|
|
1760
|
+
return {
|
|
1761
|
+
...value,
|
|
1762
|
+
swipeX: -wheelRef.current.x
|
|
1763
|
+
};
|
|
1764
|
+
});
|
|
1765
|
+
else setTransform((value) => {
|
|
1766
|
+
return {
|
|
1767
|
+
...value,
|
|
1768
|
+
dismissY: wheelRef.current.y
|
|
1769
|
+
};
|
|
1770
|
+
});
|
|
1771
|
+
window.clearTimeout(wheelRef.current.timer);
|
|
1772
|
+
const nativeEvent = event.nativeEvent;
|
|
1773
|
+
wheelRef.current.timer = window.setTimeout(() => releaseWheel(nativeEvent), 120);
|
|
1774
|
+
};
|
|
1775
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1776
|
+
ref: viewportRef,
|
|
1777
|
+
"data-slot": "lightbox-slides",
|
|
1778
|
+
"data-dragging": transform.dragging ? "true" : "false",
|
|
1779
|
+
"data-zoomed": transform.scale > 1 ? "true" : "false",
|
|
1780
|
+
role: clickZoomable ? "button" : void 0,
|
|
1781
|
+
"aria-label": clickZoomable ? transform.scale > 1 ? "Zoom out current image" : "Zoom in current image" : void 0,
|
|
1782
|
+
tabIndex: clickZoomable ? 0 : void 0,
|
|
1783
|
+
className: cn("relative min-h-0 flex-1 overflow-hidden touch-none outline-none select-none focus-visible:outline-none focus-visible:ring-0", className),
|
|
1784
|
+
onPointerDown: handlePointerDown,
|
|
1785
|
+
onPointerMove: handlePointerMove,
|
|
1786
|
+
onPointerUp: handlePointerEnd,
|
|
1787
|
+
onPointerCancel: handlePointerEnd,
|
|
1788
|
+
onClick: handleClick,
|
|
1789
|
+
onKeyDown: handleKeyDown,
|
|
1790
|
+
onWheel: handleWheel,
|
|
1791
|
+
onDoubleClick: (event) => {
|
|
1792
|
+
onDoubleClick?.(event);
|
|
1793
|
+
},
|
|
1794
|
+
...props,
|
|
1795
|
+
children: items.map((item, itemIndex) => {
|
|
1796
|
+
if (!isIndexMounted(itemIndex, activeIndex, items.length, preload, loop)) return null;
|
|
1797
|
+
const preferredOffset = (transform.swipeX === 0 ? 0 : -Math.sign(transform.swipeX)) * (direction === "rtl" ? -1 : 1);
|
|
1798
|
+
const offset = relativeSlideOffset(itemIndex, activeIndex, items.length, loop, preferredOffset);
|
|
1799
|
+
return /* @__PURE__ */ jsx(LightboxSlide, {
|
|
1800
|
+
index: itemIndex,
|
|
1801
|
+
active: itemIndex === activeIndex,
|
|
1802
|
+
style: { transform: `translate3d(calc(${offset * 100 * (direction === "rtl" ? -1 : 1)}% + ${transform.swipeX}px), ${transform.dismissY}px, 0)` }
|
|
1803
|
+
}, item.id ?? itemIndex);
|
|
1804
|
+
})
|
|
1805
|
+
});
|
|
1806
|
+
}
|
|
1807
|
+
function LightboxToolbar({ className, children, ...props }) {
|
|
1808
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1809
|
+
"data-slot": "lightbox-toolbar",
|
|
1810
|
+
className: cn("pointer-events-auto flex items-center gap-1", className),
|
|
1811
|
+
role: "toolbar",
|
|
1812
|
+
"aria-label": "Lightbox controls",
|
|
1813
|
+
...props,
|
|
1814
|
+
children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1815
|
+
/* @__PURE__ */ jsx(LightboxDownload, {}),
|
|
1816
|
+
/* @__PURE__ */ jsx(LightboxZoomButton, {}),
|
|
1817
|
+
/* @__PURE__ */ jsx(LightboxMore, {}),
|
|
1818
|
+
/* @__PURE__ */ jsx(LightboxClose, {})
|
|
1819
|
+
] })
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1822
|
+
function LightboxCounter({ className, ...props }) {
|
|
1823
|
+
const { activeIndex, items } = useLightbox();
|
|
1824
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1825
|
+
"data-slot": "lightbox-counter",
|
|
1826
|
+
className: cn("pointer-events-auto rounded-lg px-3 py-2 text-sm font-medium tabular-nums", className),
|
|
1827
|
+
...props,
|
|
1828
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
1829
|
+
"aria-hidden": "true",
|
|
1830
|
+
children: [
|
|
1831
|
+
items.length > 0 ? activeIndex + 1 : 0,
|
|
1832
|
+
" / ",
|
|
1833
|
+
items.length
|
|
1834
|
+
]
|
|
1835
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
1836
|
+
className: "sr-only",
|
|
1837
|
+
children: [
|
|
1838
|
+
"Item ",
|
|
1839
|
+
items.length > 0 ? activeIndex + 1 : 0,
|
|
1840
|
+
" of ",
|
|
1841
|
+
items.length
|
|
1842
|
+
]
|
|
1843
|
+
})]
|
|
1844
|
+
});
|
|
1845
|
+
}
|
|
1846
|
+
function LightboxCaptions({ className, ...props }) {
|
|
1847
|
+
const { activeIndex, activeItem } = useLightbox();
|
|
1848
|
+
if (!activeItem?.caption) return null;
|
|
1849
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1850
|
+
id: `lightbox-caption-${activeIndex}`,
|
|
1851
|
+
"data-slot": "lightbox-captions",
|
|
1852
|
+
className: cn("pointer-events-auto max-w-2xl px-3 py-2 text-center text-sm [text-shadow:0_1px_3px_rgb(0_0_0/0.8)]", className),
|
|
1853
|
+
...props,
|
|
1854
|
+
children: activeItem.caption
|
|
1855
|
+
});
|
|
1856
|
+
}
|
|
1857
|
+
function LightboxVideoThumbnail({ crossOrigin, onLoad, src }) {
|
|
1858
|
+
const videoRef = React$1.useRef(null);
|
|
1859
|
+
React$1.useEffect(() => {
|
|
1860
|
+
const video = videoRef.current;
|
|
1861
|
+
if (!video) return;
|
|
1862
|
+
const showFrame = () => {
|
|
1863
|
+
const duration = Number.isFinite(video.duration) ? video.duration : 0;
|
|
1864
|
+
video.currentTime = Math.min(.1, Math.max(0, duration / 2));
|
|
1865
|
+
};
|
|
1866
|
+
video.addEventListener("loadedmetadata", showFrame);
|
|
1867
|
+
if (video.readyState >= HTMLMediaElement.HAVE_METADATA) showFrame();
|
|
1868
|
+
return () => video.removeEventListener("loadedmetadata", showFrame);
|
|
1869
|
+
}, [src]);
|
|
1870
|
+
return /* @__PURE__ */ jsx("video", {
|
|
1871
|
+
ref: videoRef,
|
|
1872
|
+
"aria-hidden": "true",
|
|
1873
|
+
crossOrigin,
|
|
1874
|
+
muted: true,
|
|
1875
|
+
onLoadedData: onLoad,
|
|
1876
|
+
playsInline: true,
|
|
1877
|
+
preload: "metadata",
|
|
1878
|
+
src,
|
|
1879
|
+
tabIndex: -1,
|
|
1880
|
+
className: "size-full object-cover"
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1883
|
+
function LightboxThumbnail({ index, className, onClick, ...props }) {
|
|
1884
|
+
const { activeIndex, changeIndex, items, loadedAssets, loading, markAssetLoaded } = useLightbox();
|
|
1885
|
+
const item = items[index];
|
|
1886
|
+
const buttonRef = React$1.useRef(null);
|
|
1887
|
+
const active = index === activeIndex;
|
|
1888
|
+
const [intersected, setIntersected] = React$1.useState(false);
|
|
1889
|
+
const [failedLoadKey, setFailedLoadKey] = React$1.useState(null);
|
|
1890
|
+
const thumbnailLoadKey = item ? getThumbnailLoadKey(item) : "";
|
|
1891
|
+
const thumbnailFailed = failedLoadKey === thumbnailLoadKey;
|
|
1892
|
+
const thumbnailCached = item ? loadedAssets.has(thumbnailLoadKey) : false;
|
|
1893
|
+
React$1.useEffect(() => {
|
|
1894
|
+
if (loading === "eager" || active || intersected || thumbnailCached) return;
|
|
1895
|
+
const button = buttonRef.current;
|
|
1896
|
+
if (!button) return;
|
|
1897
|
+
if (typeof IntersectionObserver === "undefined") {
|
|
1898
|
+
const frame = requestAnimationFrame(() => setIntersected(true));
|
|
1899
|
+
return () => cancelAnimationFrame(frame);
|
|
1900
|
+
}
|
|
1901
|
+
const root = button.closest("[data-slot=\"scroll-area-viewport\"]");
|
|
1902
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
1903
|
+
if (!entry?.isIntersecting) return;
|
|
1904
|
+
setIntersected(true);
|
|
1905
|
+
observer.disconnect();
|
|
1906
|
+
}, {
|
|
1907
|
+
root,
|
|
1908
|
+
threshold: .01
|
|
1909
|
+
});
|
|
1910
|
+
observer.observe(button);
|
|
1911
|
+
return () => observer.disconnect();
|
|
1912
|
+
}, [
|
|
1913
|
+
active,
|
|
1914
|
+
intersected,
|
|
1915
|
+
loading,
|
|
1916
|
+
thumbnailCached
|
|
1917
|
+
]);
|
|
1918
|
+
React$1.useEffect(() => {
|
|
1919
|
+
if (active) buttonRef.current?.scrollIntoView({
|
|
1920
|
+
block: "nearest",
|
|
1921
|
+
inline: "center"
|
|
1922
|
+
});
|
|
1923
|
+
}, [active]);
|
|
1924
|
+
if (!item) return null;
|
|
1925
|
+
const shouldLoad = loading === "eager" || active || intersected || thumbnailCached;
|
|
1926
|
+
const thumbnailSource = item.type === "image" ? item.thumbnailSrc ?? item.src : item.poster;
|
|
1927
|
+
const thumbnail = shouldLoad ? item.thumbnail ?? (item.type === "image" ? /* @__PURE__ */ jsx("img", {
|
|
1928
|
+
src: thumbnailSource,
|
|
1929
|
+
alt: "",
|
|
1930
|
+
draggable: false,
|
|
1931
|
+
loading: "lazy",
|
|
1932
|
+
className: "size-full object-cover",
|
|
1933
|
+
onLoad: () => markAssetLoaded(thumbnailLoadKey)
|
|
1934
|
+
}) : item.poster ? /* @__PURE__ */ jsx("img", {
|
|
1935
|
+
src: item.poster,
|
|
1936
|
+
alt: "",
|
|
1937
|
+
draggable: false,
|
|
1938
|
+
loading: "lazy",
|
|
1939
|
+
className: "size-full object-cover",
|
|
1940
|
+
onLoad: () => markAssetLoaded(thumbnailLoadKey)
|
|
1941
|
+
}) : /* @__PURE__ */ jsx(LightboxVideoThumbnail, {
|
|
1942
|
+
crossOrigin: item.videoProps?.crossOrigin,
|
|
1943
|
+
onLoad: () => markAssetLoaded(thumbnailLoadKey),
|
|
1944
|
+
src: item.src
|
|
1945
|
+
})) : null;
|
|
1946
|
+
const mediaLoaded = item.thumbnail !== void 0 ? shouldLoad : thumbnailCached;
|
|
1947
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
1948
|
+
ref: buttonRef,
|
|
1949
|
+
"data-slot": "lightbox-thumbnail",
|
|
1950
|
+
"data-index": index,
|
|
1951
|
+
"data-active": active ? "true" : "false",
|
|
1952
|
+
variant: "ghost",
|
|
1953
|
+
size: "icon-lg",
|
|
1954
|
+
"aria-current": active ? "true" : void 0,
|
|
1955
|
+
"aria-label": `Show ${getItemLabel(item, index)}${thumbnailFailed ? " (thumbnail unavailable)" : ""}`,
|
|
1956
|
+
tabIndex: active ? 0 : -1,
|
|
1957
|
+
className: cn("size-12 shrink-0 overflow-hidden rounded-lg bg-transparent! p-0 text-muted-foreground text-xs font-medium opacity-65 hover:bg-transparent! hover:opacity-100 active:bg-transparent! focus-visible:bg-transparent! focus-visible:opacity-100 aria-expanded:bg-transparent! data-[active=true]:opacity-100 sm:size-14", className),
|
|
1958
|
+
onClick: (event) => {
|
|
1959
|
+
onClick?.(event);
|
|
1960
|
+
if (!event.defaultPrevented) changeIndex(index, "thumbnail-press", event.nativeEvent);
|
|
1961
|
+
},
|
|
1962
|
+
...props,
|
|
1963
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
1964
|
+
"aria-hidden": "true",
|
|
1965
|
+
"data-slot": "lightbox-thumbnail-content",
|
|
1966
|
+
className: "pointer-events-none relative flex size-full items-center justify-center",
|
|
1967
|
+
onErrorCapture: () => setFailedLoadKey(thumbnailLoadKey),
|
|
1968
|
+
onLoadCapture: () => setFailedLoadKey(null),
|
|
1969
|
+
onLoadedDataCapture: () => setFailedLoadKey(null),
|
|
1970
|
+
children: [thumbnailFailed ? /* @__PURE__ */ jsx(ImageOffIcon, { className: "size-5" }) : !mediaLoaded && /* @__PURE__ */ jsx(Skeleton, {
|
|
1971
|
+
"aria-hidden": "true",
|
|
1972
|
+
className: "absolute inset-0 size-full rounded-none"
|
|
1973
|
+
}), thumbnail && /* @__PURE__ */ jsx("span", {
|
|
1974
|
+
className: cn("relative size-full transition-opacity duration-200 [&>*]:size-full [&>img]:object-cover [&>video]:object-cover", !mediaLoaded && "opacity-0", thumbnailFailed && "hidden"),
|
|
1975
|
+
children: thumbnail
|
|
1976
|
+
})]
|
|
1977
|
+
})
|
|
1978
|
+
});
|
|
1979
|
+
}
|
|
1980
|
+
function LightboxThumbnails({ className, children, onKeyDown, ...props }) {
|
|
1981
|
+
const { activeIndex, changeIndex, direction, items, loop } = useLightbox();
|
|
1982
|
+
return /* @__PURE__ */ jsx(ScrollArea$1, {
|
|
1983
|
+
"data-slot": "lightbox-thumbnails",
|
|
1984
|
+
className: cn("pointer-events-auto max-w-[min(100%,42rem)] rounded-lg p-1.5", className),
|
|
1985
|
+
orientation: "horizontal",
|
|
1986
|
+
hideScrollbar: true,
|
|
1987
|
+
scrollShadow: "horizontal",
|
|
1988
|
+
fadeColor: "rgb(0 0 0 / 0.25)",
|
|
1989
|
+
...props,
|
|
1990
|
+
children: /* @__PURE__ */ jsx(ScrollAreaContent, {
|
|
1991
|
+
className: "flex w-max items-center gap-1.5",
|
|
1992
|
+
role: "toolbar",
|
|
1993
|
+
"aria-label": "Choose media",
|
|
1994
|
+
onKeyDown: (event) => {
|
|
1995
|
+
onKeyDown?.(event);
|
|
1996
|
+
if (event.defaultPrevented) return;
|
|
1997
|
+
if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
|
|
1998
|
+
event.preventDefault();
|
|
1999
|
+
const rtl = direction === "rtl" ? -1 : 1;
|
|
2000
|
+
const physical = event.key === "ArrowRight" ? 1 : -1;
|
|
2001
|
+
const next = normalizeIndex(activeIndex + physical * rtl, items.length, loop);
|
|
2002
|
+
const thumbnails = event.currentTarget;
|
|
2003
|
+
changeIndex(next, "keyboard", event.nativeEvent);
|
|
2004
|
+
requestAnimationFrame(() => {
|
|
2005
|
+
thumbnails.querySelector(`[data-slot="lightbox-thumbnail"][data-index="${next}"]`)?.focus();
|
|
2006
|
+
});
|
|
2007
|
+
},
|
|
2008
|
+
children: children ?? items.map((item, index) => /* @__PURE__ */ jsx(LightboxThumbnail, { index }, item.id ?? index))
|
|
2009
|
+
})
|
|
2010
|
+
});
|
|
2011
|
+
}
|
|
2012
|
+
function LightboxPrevious({ className, disabled: disabledProp, onClick, style, ...props }) {
|
|
2013
|
+
const { activeIndex, changeIndex, direction, items, loop } = useLightbox();
|
|
2014
|
+
if (disabledProp === true || items.length < 2 || !loop && activeIndex === 0) return null;
|
|
2015
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
2016
|
+
"data-slot": "lightbox-previous",
|
|
2017
|
+
variant: "ghost",
|
|
2018
|
+
size: "icon-lg",
|
|
2019
|
+
"aria-label": "Previous item",
|
|
2020
|
+
className: cn("absolute start-2 z-20 size-12 -translate-y-1/2 rounded-lg bg-transparent text-white hover:bg-white/15 hover:text-white focus-visible:bg-white/15 sm:start-4 [&_svg:not([class*='size-'])]:size-4.5", className),
|
|
2021
|
+
style: {
|
|
2022
|
+
top: "calc(50% + (var(--lightbox-header-inset, 5rem) - 9rem) / 2)",
|
|
2023
|
+
...style
|
|
2024
|
+
},
|
|
2025
|
+
onClick: (event) => {
|
|
2026
|
+
onClick?.(event);
|
|
2027
|
+
if (!event.defaultPrevented) changeIndex(activeIndex - 1, "previous-press", event.nativeEvent);
|
|
2028
|
+
},
|
|
2029
|
+
...props,
|
|
2030
|
+
children: direction === "rtl" ? /* @__PURE__ */ jsx(ChevronRightIcon, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(ChevronLeftIcon, { "aria-hidden": "true" })
|
|
2031
|
+
});
|
|
2032
|
+
}
|
|
2033
|
+
function LightboxNext({ className, disabled: disabledProp, onClick, style, ...props }) {
|
|
2034
|
+
const { activeIndex, changeIndex, direction, items, loop } = useLightbox();
|
|
2035
|
+
if (disabledProp === true || items.length < 2 || !loop && activeIndex === items.length - 1) return null;
|
|
2036
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
2037
|
+
"data-slot": "lightbox-next",
|
|
2038
|
+
variant: "ghost",
|
|
2039
|
+
size: "icon-lg",
|
|
2040
|
+
"aria-label": "Next item",
|
|
2041
|
+
className: cn("absolute end-2 z-20 size-12 -translate-y-1/2 rounded-lg bg-transparent text-white hover:bg-white/15 hover:text-white focus-visible:bg-white/15 sm:end-4 [&_svg:not([class*='size-'])]:size-4.5", className),
|
|
2042
|
+
style: {
|
|
2043
|
+
top: "calc(50% + (var(--lightbox-header-inset, 5rem) - 9rem) / 2)",
|
|
2044
|
+
...style
|
|
2045
|
+
},
|
|
2046
|
+
onClick: (event) => {
|
|
2047
|
+
onClick?.(event);
|
|
2048
|
+
if (!event.defaultPrevented) changeIndex(activeIndex + 1, "next-press", event.nativeEvent);
|
|
2049
|
+
},
|
|
2050
|
+
...props,
|
|
2051
|
+
children: direction === "rtl" ? /* @__PURE__ */ jsx(ChevronLeftIcon, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(ChevronRightIcon, { "aria-hidden": "true" })
|
|
2052
|
+
});
|
|
2053
|
+
}
|
|
2054
|
+
function LightboxZoomButton({ className, disabled, onClick, ...props }) {
|
|
2055
|
+
const { activeItem, actions, transform } = useLightbox();
|
|
2056
|
+
if (!(activeItem?.type === "image" ? activeItem.zoomable !== false : Boolean(activeItem?.zoomable)) || disabled) return null;
|
|
2057
|
+
const zoomed = transform.scale > 1;
|
|
2058
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
2059
|
+
"data-slot": "lightbox-zoom-button",
|
|
2060
|
+
variant: "ghost",
|
|
2061
|
+
size: "icon-lg",
|
|
2062
|
+
"aria-label": zoomed ? "Zoom out" : "Zoom in",
|
|
2063
|
+
className: cn("text-white hover:bg-white/15 hover:text-white [&_svg:not([class*='size-'])]:size-4.5", className),
|
|
2064
|
+
onClick: (event) => {
|
|
2065
|
+
onClick?.(event);
|
|
2066
|
+
if (!event.defaultPrevented) {
|
|
2067
|
+
if (zoomed) actions.resetZoom();
|
|
2068
|
+
else actions.zoomIn();
|
|
2069
|
+
}
|
|
2070
|
+
},
|
|
2071
|
+
...props,
|
|
2072
|
+
children: zoomed ? /* @__PURE__ */ jsx(ZoomOutIcon, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(ZoomInIcon, { "aria-hidden": "true" })
|
|
2073
|
+
});
|
|
2074
|
+
}
|
|
2075
|
+
function getDownload(item) {
|
|
2076
|
+
if (!item) return void 0;
|
|
2077
|
+
if (item.download === false) return void 0;
|
|
2078
|
+
if (typeof item.download === "object") return {
|
|
2079
|
+
src: item.download.src ?? item.src,
|
|
2080
|
+
filename: item.download.filename
|
|
2081
|
+
};
|
|
2082
|
+
return {
|
|
2083
|
+
src: item.src,
|
|
2084
|
+
filename: void 0
|
|
2085
|
+
};
|
|
2086
|
+
}
|
|
2087
|
+
function getDownloadFilename(response, configured) {
|
|
2088
|
+
if (configured) return configured;
|
|
2089
|
+
const disposition = response.headers.get("content-disposition");
|
|
2090
|
+
const encodedFilename = disposition?.match(/filename\*=UTF-8''([^;]+)/i)?.[1];
|
|
2091
|
+
const plainFilename = disposition?.match(/filename="?([^";]+)"?/i)?.[1];
|
|
2092
|
+
const candidate = encodedFilename ?? plainFilename;
|
|
2093
|
+
if (candidate) try {
|
|
2094
|
+
return decodeURIComponent(candidate).replace(/[\\/:*?"<>|]/g, "-");
|
|
2095
|
+
} catch {
|
|
2096
|
+
return candidate.replace(/[\\/:*?"<>|]/g, "-");
|
|
2097
|
+
}
|
|
2098
|
+
try {
|
|
2099
|
+
const segment = new URL(response.url).pathname.split("/").filter(Boolean).at(-1);
|
|
2100
|
+
return segment ? decodeURIComponent(segment) : "download";
|
|
2101
|
+
} catch {
|
|
2102
|
+
return "download";
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
async function downloadFile(source, filename, signal) {
|
|
2106
|
+
const response = await fetch(source, { signal });
|
|
2107
|
+
if (!response.ok) throw new Error(`Download failed with status ${response.status}`);
|
|
2108
|
+
const blobUrl = URL.createObjectURL(await response.blob());
|
|
2109
|
+
const anchor = document.createElement("a");
|
|
2110
|
+
anchor.href = blobUrl;
|
|
2111
|
+
anchor.download = getDownloadFilename(response, filename);
|
|
2112
|
+
anchor.hidden = true;
|
|
2113
|
+
document.body.append(anchor);
|
|
2114
|
+
anchor.click();
|
|
2115
|
+
anchor.remove();
|
|
2116
|
+
window.setTimeout(() => URL.revokeObjectURL(blobUrl), 1e3);
|
|
2117
|
+
}
|
|
2118
|
+
function LightboxDownload({ className, disabled, onClick, ...props }) {
|
|
2119
|
+
const { activeItem } = useLightbox();
|
|
2120
|
+
const download = getDownload(activeItem);
|
|
2121
|
+
const abortControllerRef = React$1.useRef(null);
|
|
2122
|
+
const resetTimerRef = React$1.useRef(0);
|
|
2123
|
+
const [status, setStatus] = React$1.useState("idle");
|
|
2124
|
+
React$1.useEffect(() => () => {
|
|
2125
|
+
const controller = abortControllerRef.current;
|
|
2126
|
+
abortControllerRef.current = null;
|
|
2127
|
+
controller?.abort();
|
|
2128
|
+
window.clearTimeout(resetTimerRef.current);
|
|
2129
|
+
}, [download?.src]);
|
|
2130
|
+
if (!download || disabled) return null;
|
|
2131
|
+
const statusMessage = {
|
|
2132
|
+
idle: "",
|
|
2133
|
+
loading: "Preparing download",
|
|
2134
|
+
success: "Download started",
|
|
2135
|
+
error: "Download failed"
|
|
2136
|
+
}[status];
|
|
2137
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Button, {
|
|
2138
|
+
"data-slot": "lightbox-download",
|
|
2139
|
+
type: "button",
|
|
2140
|
+
variant: "ghost",
|
|
2141
|
+
size: "icon-lg",
|
|
2142
|
+
"aria-label": status === "loading" ? "Preparing current item download" : "Download current item",
|
|
2143
|
+
"aria-busy": status === "loading" || void 0,
|
|
2144
|
+
className: cn("text-white hover:bg-white/15 hover:text-white [&_svg:not([class*='size-'])]:size-4.5", className),
|
|
2145
|
+
onClick: (event) => {
|
|
2146
|
+
onClick?.(event);
|
|
2147
|
+
if (event.defaultPrevented || status === "loading") return;
|
|
2148
|
+
abortControllerRef.current?.abort();
|
|
2149
|
+
window.clearTimeout(resetTimerRef.current);
|
|
2150
|
+
const controller = new AbortController();
|
|
2151
|
+
abortControllerRef.current = controller;
|
|
2152
|
+
setStatus("loading");
|
|
2153
|
+
downloadFile(download.src, download.filename, controller.signal).then(() => setStatus("success")).catch((error) => {
|
|
2154
|
+
if (error instanceof DOMException && error.name === "AbortError") return;
|
|
2155
|
+
setStatus("error");
|
|
2156
|
+
}).finally(() => {
|
|
2157
|
+
if (abortControllerRef.current === controller) {
|
|
2158
|
+
abortControllerRef.current = null;
|
|
2159
|
+
resetTimerRef.current = window.setTimeout(() => setStatus("idle"), 2e3);
|
|
2160
|
+
}
|
|
2161
|
+
});
|
|
2162
|
+
},
|
|
2163
|
+
...props,
|
|
2164
|
+
children: /* @__PURE__ */ jsx(DownloadIcon, { "aria-hidden": "true" })
|
|
2165
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
2166
|
+
className: "sr-only",
|
|
2167
|
+
role: "status",
|
|
2168
|
+
"aria-live": "polite",
|
|
2169
|
+
children: statusMessage
|
|
2170
|
+
})] });
|
|
2171
|
+
}
|
|
2172
|
+
function LightboxMore({ className, ...props }) {
|
|
2173
|
+
const { activeIndex, activeItem, actions, controls } = useLightbox();
|
|
2174
|
+
const availableControls = controls.filter((control) => !control.disabled);
|
|
2175
|
+
if (availableControls.length === 0 || props.disabled) return null;
|
|
2176
|
+
return /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
|
|
2177
|
+
render: /* @__PURE__ */ jsx(Button, {
|
|
2178
|
+
"data-slot": "lightbox-more",
|
|
2179
|
+
variant: "ghost",
|
|
2180
|
+
size: "icon-lg",
|
|
2181
|
+
"aria-label": "More actions",
|
|
2182
|
+
className: cn("text-white hover:bg-white/15 hover:text-white [&_svg:not([class*='size-'])]:size-4.5", className),
|
|
2183
|
+
...props
|
|
2184
|
+
}),
|
|
2185
|
+
children: /* @__PURE__ */ jsx(EllipsisIcon, { "aria-hidden": "true" })
|
|
2186
|
+
}), /* @__PURE__ */ jsx(Menu.Portal, { children: /* @__PURE__ */ jsx(Menu.Positioner, {
|
|
2187
|
+
align: "end",
|
|
2188
|
+
sideOffset: 4,
|
|
2189
|
+
className: "isolate z-80 outline-none",
|
|
2190
|
+
children: /* @__PURE__ */ jsx(Menu.Popup, {
|
|
2191
|
+
"data-slot": "dropdown-menu-content",
|
|
2192
|
+
className: "max-h-(--available-height) min-w-48 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-md bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:overflow-hidden data-closed:fade-out-0 data-closed:zoom-out-95",
|
|
2193
|
+
children: availableControls.map((control) => /* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
2194
|
+
onClick: (event) => control.onClick({
|
|
2195
|
+
item: activeItem,
|
|
2196
|
+
index: activeIndex,
|
|
2197
|
+
event,
|
|
2198
|
+
actions
|
|
2199
|
+
}),
|
|
2200
|
+
children: [control.icon, /* @__PURE__ */ jsx("span", { children: control.label })]
|
|
2201
|
+
}, control.id))
|
|
2202
|
+
})
|
|
2203
|
+
}) })] });
|
|
2204
|
+
}
|
|
2205
|
+
function LightboxClose({ className, children, ...props }) {
|
|
2206
|
+
return /* @__PURE__ */ jsx(Dialog.Close, {
|
|
2207
|
+
"data-slot": "lightbox-close",
|
|
2208
|
+
"aria-label": "Close lightbox",
|
|
2209
|
+
render: /* @__PURE__ */ jsx(Button, {
|
|
2210
|
+
variant: "ghost",
|
|
2211
|
+
size: "icon-lg",
|
|
2212
|
+
className: cn("text-white hover:bg-white/15 hover:text-white [&_svg:not([class*='size-'])]:size-4.5", className)
|
|
2213
|
+
}),
|
|
2214
|
+
...props,
|
|
2215
|
+
children: children ?? /* @__PURE__ */ jsx(XIcon, { "aria-hidden": "true" })
|
|
2216
|
+
});
|
|
2217
|
+
}
|
|
2218
|
+
function ScrollArea$1({ className, cornerStyle, fadeColor, horizontalScrollbarStyle, viewportClassName, viewportTabIndex, hideScrollbar = false, orientation = "vertical", scrollbarClassName, scrollShadow = "none", verticalScrollbarStyle, children, ...props }) {
|
|
2219
|
+
const fadeColorStyle = fadeColor === void 0 ? {} : { "--scroll-area-fade": fadeColor };
|
|
2220
|
+
return /* @__PURE__ */ jsxs(ScrollArea.Root, {
|
|
2221
|
+
className: cn("relative isolate min-h-0 flex flex-col", className),
|
|
2222
|
+
...props,
|
|
2223
|
+
children: [
|
|
2224
|
+
/* @__PURE__ */ jsxs(ScrollArea.Viewport, {
|
|
2225
|
+
"data-fade-mode": fadeColor === void 0 ? "auto" : "custom",
|
|
2226
|
+
"data-scroll-shadow": scrollShadow,
|
|
2227
|
+
"data-slot": "scroll-area-viewport",
|
|
2228
|
+
tabIndex: viewportTabIndex,
|
|
2229
|
+
className: cn("size-full min-h-0 flex-1 overscroll-contain rounded-[inherit] outline-none transition-shadow focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background", orientation === "vertical" && "overflow-x-hidden! [overflow-x:hidden!important]", orientation === "horizontal" && "overflow-y-hidden! [overflow-y:hidden!important]", viewportClassName),
|
|
2230
|
+
style: {
|
|
2231
|
+
...orientation === "vertical" ? { overflowX: "hidden" } : {},
|
|
2232
|
+
...orientation === "horizontal" ? { overflowY: "hidden" } : {}
|
|
2233
|
+
},
|
|
2234
|
+
children: [
|
|
2235
|
+
/* @__PURE__ */ jsx("div", {
|
|
2236
|
+
"data-slot": "scroll-area-vertical-shadow",
|
|
2237
|
+
className: cn(scrollShadow === "vertical" || scrollShadow === "both" ? "block" : "hidden", [
|
|
2238
|
+
"pointer-events-none absolute inset-0 z-10",
|
|
2239
|
+
"before:absolute before:top-0 before:block before:w-full before:rounded-md before:rounded-b-none before:content-[''] after:absolute after:bottom-0 after:block after:w-full after:rounded-md after:rounded-t-none after:content-['']",
|
|
2240
|
+
"before:pointer-events-none after:pointer-events-none",
|
|
2241
|
+
"before:transition-[height] after:transition-[height] before:duration-100 after:duration-100 before:ease-out after:ease-out",
|
|
2242
|
+
"before:[--scroll-area-overflow-y-start:inherit]",
|
|
2243
|
+
"after:[--scroll-area-overflow-y-end:inherit]",
|
|
2244
|
+
"[--scroll-area-fade:var(--background)] before:h-[min(var(--scroll-area-fade-size,40px),var(--scroll-area-overflow-y-start))] before:bg-[linear-gradient(to_bottom,var(--scroll-area-fade),transparent)] after:h-[min(var(--scroll-area-fade-size,40px),var(--scroll-area-overflow-y-end,40px))] after:bg-[linear-gradient(to_top,var(--scroll-area-fade),transparent)]"
|
|
2245
|
+
]),
|
|
2246
|
+
style: {
|
|
2247
|
+
"--scroll-area-overflow-y-start": "inherit",
|
|
2248
|
+
"--scroll-area-overflow-y-end": "inherit",
|
|
2249
|
+
...fadeColorStyle
|
|
2250
|
+
}
|
|
2251
|
+
}),
|
|
2252
|
+
/* @__PURE__ */ jsx("div", {
|
|
2253
|
+
"data-slot": "scroll-area-horizontal-shadow",
|
|
2254
|
+
className: cn(scrollShadow === "horizontal" || scrollShadow === "both" ? "block" : "hidden", [
|
|
2255
|
+
"pointer-events-none absolute inset-0 z-10",
|
|
2256
|
+
"before:absolute before:top-0 before:block before:h-full before:rounded-md before:rounded-r-none before:content-[''] after:absolute after:top-0 after:block after:h-full after:rounded-md after:rounded-l-none after:content-['']",
|
|
2257
|
+
"before:pointer-events-none after:pointer-events-none",
|
|
2258
|
+
"before:transition-[width] after:transition-[width] before:duration-100 after:duration-100 before:ease-out after:ease-out",
|
|
2259
|
+
"before:[--scroll-area-overflow-x-start:inherit]",
|
|
2260
|
+
"after:[--scroll-area-overflow-x-end:inherit]",
|
|
2261
|
+
"[--scroll-area-fade:var(--background)] before:left-0 before:w-[min(var(--scroll-area-fade-size,40px),var(--scroll-area-overflow-x-start))] before:bg-[linear-gradient(to_right,var(--scroll-area-fade),transparent)] after:right-0 after:w-[min(var(--scroll-area-fade-size,40px),var(--scroll-area-overflow-x-end,40px))] after:bg-[linear-gradient(to_left,var(--scroll-area-fade),transparent)]"
|
|
2262
|
+
]),
|
|
2263
|
+
style: {
|
|
2264
|
+
"--scroll-area-overflow-x-start": "inherit",
|
|
2265
|
+
"--scroll-area-overflow-x-end": "inherit",
|
|
2266
|
+
...fadeColorStyle
|
|
2267
|
+
}
|
|
2268
|
+
}),
|
|
2269
|
+
children
|
|
2270
|
+
]
|
|
2271
|
+
}),
|
|
2272
|
+
!hideScrollbar && (orientation === "both" ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ScrollBar, {
|
|
2273
|
+
orientation: "vertical",
|
|
2274
|
+
className: scrollbarClassName,
|
|
2275
|
+
style: verticalScrollbarStyle
|
|
2276
|
+
}), /* @__PURE__ */ jsx(ScrollBar, {
|
|
2277
|
+
orientation: "horizontal",
|
|
2278
|
+
className: scrollbarClassName,
|
|
2279
|
+
style: horizontalScrollbarStyle
|
|
2280
|
+
})] }) : /* @__PURE__ */ jsx(ScrollBar, {
|
|
2281
|
+
orientation,
|
|
2282
|
+
className: scrollbarClassName,
|
|
2283
|
+
style: orientation === "vertical" ? verticalScrollbarStyle : horizontalScrollbarStyle
|
|
2284
|
+
})),
|
|
2285
|
+
/* @__PURE__ */ jsx(ScrollArea.Corner, {
|
|
2286
|
+
"data-slot": "scroll-area-corner",
|
|
2287
|
+
style: cornerStyle
|
|
2288
|
+
}),
|
|
2289
|
+
/* @__PURE__ */ jsx("style", { children: `
|
|
2290
|
+
[data-slot="scroll-area-viewport"][data-fade-mode="auto"][data-scroll-shadow="vertical"],
|
|
2291
|
+
[data-slot="scroll-area-viewport"][data-fade-mode="auto"][data-scroll-shadow="both"] {
|
|
2292
|
+
--scroll-area-fade-y-start: min(var(--scroll-area-overflow-y-start, 0px), var(--scroll-area-fade-size, 40px));
|
|
2293
|
+
--scroll-area-fade-y-end: min(var(--scroll-area-overflow-y-end, 0px), var(--scroll-area-fade-size, 40px));
|
|
2294
|
+
-webkit-mask-image: linear-gradient(
|
|
2295
|
+
to bottom,
|
|
2296
|
+
transparent 0,
|
|
2297
|
+
black var(--scroll-area-fade-y-start),
|
|
2298
|
+
black calc(100% - var(--scroll-area-fade-y-end)),
|
|
2299
|
+
transparent 100%
|
|
2300
|
+
);
|
|
2301
|
+
mask-image: linear-gradient(
|
|
2302
|
+
to bottom,
|
|
2303
|
+
transparent 0,
|
|
2304
|
+
black var(--scroll-area-fade-y-start),
|
|
2305
|
+
black calc(100% - var(--scroll-area-fade-y-end)),
|
|
2306
|
+
transparent 100%
|
|
2307
|
+
);
|
|
2308
|
+
}
|
|
2309
|
+
[data-slot="scroll-area-viewport"][data-fade-mode="auto"][data-scroll-shadow="horizontal"],
|
|
2310
|
+
[data-slot="scroll-area-viewport"][data-fade-mode="auto"][data-scroll-shadow="both"] {
|
|
2311
|
+
--scroll-area-fade-x-start: min(var(--scroll-area-overflow-x-start, 0px), var(--scroll-area-fade-size, 40px));
|
|
2312
|
+
--scroll-area-fade-x-end: min(var(--scroll-area-overflow-x-end, 0px), var(--scroll-area-fade-size, 40px));
|
|
2313
|
+
-webkit-mask-image: linear-gradient(
|
|
2314
|
+
to right,
|
|
2315
|
+
transparent 0,
|
|
2316
|
+
black var(--scroll-area-fade-x-start),
|
|
2317
|
+
black calc(100% - var(--scroll-area-fade-x-end)),
|
|
2318
|
+
transparent 100%
|
|
2319
|
+
);
|
|
2320
|
+
mask-image: linear-gradient(
|
|
2321
|
+
to right,
|
|
2322
|
+
transparent 0,
|
|
2323
|
+
black var(--scroll-area-fade-x-start),
|
|
2324
|
+
black calc(100% - var(--scroll-area-fade-x-end)),
|
|
2325
|
+
transparent 100%
|
|
2326
|
+
);
|
|
2327
|
+
}
|
|
2328
|
+
[data-slot="scroll-area-viewport"][data-fade-mode="auto"][data-scroll-shadow="both"] {
|
|
2329
|
+
-webkit-mask-image: linear-gradient(
|
|
2330
|
+
to bottom,
|
|
2331
|
+
transparent 0,
|
|
2332
|
+
black var(--scroll-area-fade-y-start),
|
|
2333
|
+
black calc(100% - var(--scroll-area-fade-y-end)),
|
|
2334
|
+
transparent 100%
|
|
2335
|
+
),
|
|
2336
|
+
linear-gradient(
|
|
2337
|
+
to right,
|
|
2338
|
+
transparent 0,
|
|
2339
|
+
black var(--scroll-area-fade-x-start),
|
|
2340
|
+
black calc(100% - var(--scroll-area-fade-x-end)),
|
|
2341
|
+
transparent 100%
|
|
2342
|
+
);
|
|
2343
|
+
mask-image: linear-gradient(
|
|
2344
|
+
to bottom,
|
|
2345
|
+
transparent 0,
|
|
2346
|
+
black var(--scroll-area-fade-y-start),
|
|
2347
|
+
black calc(100% - var(--scroll-area-fade-y-end)),
|
|
2348
|
+
transparent 100%
|
|
2349
|
+
),
|
|
2350
|
+
linear-gradient(
|
|
2351
|
+
to right,
|
|
2352
|
+
transparent 0,
|
|
2353
|
+
black var(--scroll-area-fade-x-start),
|
|
2354
|
+
black calc(100% - var(--scroll-area-fade-x-end)),
|
|
2355
|
+
transparent 100%
|
|
2356
|
+
);
|
|
2357
|
+
-webkit-mask-composite: source-in;
|
|
2358
|
+
mask-composite: intersect;
|
|
2359
|
+
}
|
|
2360
|
+
[data-slot="scroll-area-viewport"][data-fade-mode="auto"] [data-slot="scroll-area-vertical-shadow"],
|
|
2361
|
+
[data-slot="scroll-area-viewport"][data-fade-mode="auto"] [data-slot="scroll-area-horizontal-shadow"] {
|
|
2362
|
+
display: none;
|
|
2363
|
+
}
|
|
2364
|
+
` })
|
|
2365
|
+
]
|
|
2366
|
+
});
|
|
2367
|
+
}
|
|
2368
|
+
function ScrollAreaContent({ className, children, ...props }) {
|
|
2369
|
+
return /* @__PURE__ */ jsx(ScrollArea.Content, {
|
|
2370
|
+
"data-slot": "scroll-area-content",
|
|
2371
|
+
className: cn("w-full", className),
|
|
2372
|
+
...props,
|
|
2373
|
+
children
|
|
2374
|
+
});
|
|
2375
|
+
}
|
|
2376
|
+
function ScrollBar({ className, orientation = "vertical", ...props }) {
|
|
2377
|
+
return /* @__PURE__ */ jsx(ScrollArea.Scrollbar, {
|
|
2378
|
+
className: cn("relative z-20 m-1.5 flex rounded-full bg-muted opacity-0 transition-opacity delay-200 data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:flex-col data-[orientation=vertical]:w-1.5 data-hovering:opacity-100 data-scrolling:opacity-100 data-hovering:delay-0 data-scrolling:delay-0 data-hovering:duration-100 data-scrolling:duration-100", className),
|
|
2379
|
+
"data-base-ui-swipe-ignore": true,
|
|
2380
|
+
"data-slot": "scroll-area-scrollbar",
|
|
2381
|
+
orientation,
|
|
2382
|
+
...props,
|
|
2383
|
+
children: /* @__PURE__ */ jsx(ScrollArea.Thumb, {
|
|
2384
|
+
className: "relative flex-1 rounded-full bg-muted-foreground/40",
|
|
2385
|
+
"data-slot": "scroll-area-thumb"
|
|
2386
|
+
})
|
|
2387
|
+
});
|
|
2388
|
+
}
|
|
2389
|
+
export { Lightbox, LightboxBackdrop, LightboxCaptions, LightboxClose, LightboxContent, LightboxCounter, LightboxDownload, LightboxMore, LightboxNext, LightboxPrevious, LightboxSlide, LightboxSlides, LightboxThumbnail, LightboxThumbnails, LightboxToolbar, LightboxTrigger, LightboxViewport, LightboxZoom, LightboxZoomButton, useLightbox };
|