@mantine-vue/lightbox 3.3.0

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.
Files changed (48) hide show
  1. package/README.md +14 -0
  2. package/cjs/index.cjs +1482 -0
  3. package/cjs/index.cjs.map +1 -0
  4. package/esm/index.mjs +1431 -0
  5. package/esm/index.mjs.map +1 -0
  6. package/lib/Lightbox.d.ts +35 -0
  7. package/lib/Lightbox.props.d.ts +57 -0
  8. package/lib/Lightbox.vue.d.ts +52 -0
  9. package/lib/LightboxCaption/LightboxCaption.d.ts +5 -0
  10. package/lib/LightboxCaption/LightboxCaption.vue.d.ts +3 -0
  11. package/lib/LightboxCaption/index.d.ts +1 -0
  12. package/lib/LightboxCloseButton/LightboxCloseButton.d.ts +9 -0
  13. package/lib/LightboxCloseButton/LightboxCloseButton.vue.d.ts +13 -0
  14. package/lib/LightboxCloseButton/index.d.ts +1 -0
  15. package/lib/LightboxNavigation/LightboxNavigation.d.ts +5 -0
  16. package/lib/LightboxNavigation/LightboxNavigation.vue.d.ts +3 -0
  17. package/lib/LightboxNavigation/index.d.ts +1 -0
  18. package/lib/LightboxProvider/LightboxProvider.d.ts +7 -0
  19. package/lib/LightboxProvider/LightboxProvider.vue.d.ts +8 -0
  20. package/lib/LightboxProvider/index.d.ts +1 -0
  21. package/lib/LightboxRoot/LightboxRoot.d.ts +4 -0
  22. package/lib/LightboxRoot/LightboxRoot.types.d.ts +73 -0
  23. package/lib/LightboxRoot/LightboxRoot.vue.d.ts +35 -0
  24. package/lib/LightboxRoot/index.d.ts +1 -0
  25. package/lib/LightboxSlide/LightboxSlide.d.ts +3 -0
  26. package/lib/LightboxSlide/LightboxSlide.types.d.ts +16 -0
  27. package/lib/LightboxSlide/LightboxSlide.vue.d.ts +11 -0
  28. package/lib/LightboxSlide/index.d.ts +1 -0
  29. package/lib/LightboxSlides/LightboxSlides.d.ts +9 -0
  30. package/lib/LightboxSlides/LightboxSlides.vue.d.ts +13 -0
  31. package/lib/LightboxSlides/index.d.ts +1 -0
  32. package/lib/LightboxThumbnails/LightboxThumbnails.d.ts +5 -0
  33. package/lib/LightboxThumbnails/LightboxThumbnails.vue.d.ts +3 -0
  34. package/lib/LightboxThumbnails/index.d.ts +1 -0
  35. package/lib/LightboxToolbar/LightboxToolbar.d.ts +9 -0
  36. package/lib/LightboxToolbar/LightboxToolbar.vue.d.ts +7 -0
  37. package/lib/LightboxToolbar/index.d.ts +1 -0
  38. package/lib/LightboxToolbar/toolbar-items.d.ts +5 -0
  39. package/lib/default-labels.d.ts +2 -0
  40. package/lib/hooks/use-lightbox-zoom.d.ts +38 -0
  41. package/lib/index.d.mts +13 -0
  42. package/lib/index.d.ts +13 -0
  43. package/lib/lightbox.context.d.ts +21 -0
  44. package/lib/lightbox.store.d.ts +54 -0
  45. package/lib/types.d.ts +75 -0
  46. package/package.json +50 -0
  47. package/styles.css +201 -0
  48. package/styles.layer.css +203 -0
package/esm/index.mjs ADDED
@@ -0,0 +1,1431 @@
1
+ import { Box, Collapse, FocusTrap, Transition, UnstyledButton, VisuallyHidden, createSafeContext, factory, useDirection, useProps, useStyles } from "@mantine-vue/core";
2
+ import { Fragment, Teleport, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createSlots, createTextVNode, createVNode, defineComponent, guardReactiveProps, h, mergeProps, nextTick, normalizeProps, onBeforeUnmount, openBlock, reactive, ref, renderList, renderSlot, resolveDynamicComponent, shallowReactive, toDisplayString, unref, useAttrs, useSlots, watch, withCtx, withModifiers } from "vue";
3
+ import emblaCarouselVue from "embla-carousel-vue";
4
+ //#region src/lightbox.context.ts
5
+ var [provideLightboxContext, useLightboxContext] = createSafeContext("Lightbox component was not found in tree");
6
+ //#endregion
7
+ //#region src/LightboxCaption/LightboxCaption.vue
8
+ var LightboxCaption_default = /* @__PURE__ */ defineComponent({
9
+ name: "LightboxCaption",
10
+ inheritAttrs: false,
11
+ __name: "LightboxCaption",
12
+ setup(__props) {
13
+ const attrs = useAttrs();
14
+ const ctx = useLightboxContext();
15
+ const caption = computed(() => ctx.slides[ctx.currentIndex]?.caption);
16
+ return (_ctx, _cache) => {
17
+ return caption.value ? (openBlock(), createBlock(unref(Box), normalizeProps(mergeProps({ key: 0 }, {
18
+ ...unref(attrs),
19
+ ...unref(ctx).getStyles("caption")
20
+ })), {
21
+ default: withCtx(() => [(openBlock(), createBlock(resolveDynamicComponent(() => caption.value)))]),
22
+ _: 1
23
+ }, 16)) : createCommentVNode("", true);
24
+ };
25
+ }
26
+ });
27
+ //#endregion
28
+ //#region src/LightboxNavigation/LightboxNavigation.vue?vue&type=script&setup=true&lang.ts
29
+ var _hoisted_1$3 = {
30
+ xmlns: "http://www.w3.org/2000/svg",
31
+ viewBox: "0 0 24 24",
32
+ width: "36",
33
+ height: "36",
34
+ fill: "none",
35
+ stroke: "currentColor",
36
+ "stroke-width": "2",
37
+ "stroke-linecap": "round",
38
+ "stroke-linejoin": "round",
39
+ "aria-hidden": "true"
40
+ };
41
+ var _hoisted_2$2 = ["points"];
42
+ var _hoisted_3$1 = {
43
+ xmlns: "http://www.w3.org/2000/svg",
44
+ viewBox: "0 0 24 24",
45
+ width: "36",
46
+ height: "36",
47
+ fill: "none",
48
+ stroke: "currentColor",
49
+ "stroke-width": "2",
50
+ "stroke-linecap": "round",
51
+ "stroke-linejoin": "round",
52
+ "aria-hidden": "true"
53
+ };
54
+ var _hoisted_4 = ["points"];
55
+ //#endregion
56
+ //#region src/LightboxNavigation/LightboxNavigation.vue
57
+ var LightboxNavigation_default = /* @__PURE__ */ defineComponent({
58
+ name: "LightboxNavigation",
59
+ inheritAttrs: false,
60
+ __name: "LightboxNavigation",
61
+ setup(__props) {
62
+ const attrs = useAttrs();
63
+ const ctx = useLightboxContext();
64
+ const { dir } = useDirection();
65
+ const canPrev = computed(() => ctx.loop ? ctx.slides.length > 1 : ctx.currentIndex > 0);
66
+ const canNext = computed(() => ctx.loop ? ctx.slides.length > 1 : ctx.currentIndex < ctx.slides.length - 1);
67
+ return (_ctx, _cache) => {
68
+ return openBlock(), createBlock(unref(Box), normalizeProps(guardReactiveProps({
69
+ ...unref(attrs),
70
+ ...unref(ctx).getStyles("navigation")
71
+ })), {
72
+ default: withCtx(() => [createVNode(unref(UnstyledButton), mergeProps(unref(ctx).getStyles("navigationButton"), {
73
+ "data-inactive": !canPrev.value || void 0,
74
+ "aria-disabled": !canPrev.value || void 0,
75
+ "aria-label": unref(ctx).labels.previousSlideLabel,
76
+ tabindex: canPrev.value ? 0 : -1,
77
+ onClick: unref(ctx).prev
78
+ }), {
79
+ default: withCtx(() => [(openBlock(), createElementBlock("svg", _hoisted_1$3, [createElementVNode("polyline", { points: unref(dir) === "rtl" ? "9 18 15 12 9 6" : "15 18 9 12 15 6" }, null, 8, _hoisted_2$2)]))]),
80
+ _: 1
81
+ }, 16, [
82
+ "data-inactive",
83
+ "aria-disabled",
84
+ "aria-label",
85
+ "tabindex",
86
+ "onClick"
87
+ ]), createVNode(unref(UnstyledButton), mergeProps(unref(ctx).getStyles("navigationButton"), {
88
+ "data-inactive": !canNext.value || void 0,
89
+ "aria-disabled": !canNext.value || void 0,
90
+ "aria-label": unref(ctx).labels.nextSlideLabel,
91
+ tabindex: canNext.value ? 0 : -1,
92
+ onClick: unref(ctx).next
93
+ }), {
94
+ default: withCtx(() => [(openBlock(), createElementBlock("svg", _hoisted_3$1, [createElementVNode("polyline", { points: unref(dir) === "rtl" ? "15 18 9 12 15 6" : "9 18 15 12 9 6" }, null, 8, _hoisted_4)]))]),
95
+ _: 1
96
+ }, 16, [
97
+ "data-inactive",
98
+ "aria-disabled",
99
+ "aria-label",
100
+ "tabindex",
101
+ "onClick"
102
+ ])]),
103
+ _: 1
104
+ }, 16);
105
+ };
106
+ }
107
+ });
108
+ //#endregion
109
+ //#region src/default-labels.ts
110
+ var DEFAULT_LABELS = {
111
+ lightboxLabel: "Gallery",
112
+ slideLabel: (index, total) => `Slide ${index} of ${total}`,
113
+ slidesLabel: "Slides",
114
+ previousSlideLabel: "Previous slide",
115
+ nextSlideLabel: "Next slide",
116
+ thumbnailLabel: (index) => `Go to slide ${index}`,
117
+ enterFullscreenLabel: "Enter fullscreen",
118
+ exitFullscreenLabel: "Exit fullscreen",
119
+ showThumbnailsLabel: "Show thumbnails",
120
+ hideThumbnailsLabel: "Hide thumbnails",
121
+ downloadLabel: "Download",
122
+ closeLabel: "Close"
123
+ };
124
+ //#endregion
125
+ //#region src/hooks/use-lightbox-zoom.ts
126
+ var INITIAL_ZOOM_STATE = {
127
+ scale: 1,
128
+ translateX: 0,
129
+ translateY: 0,
130
+ isZoomed: false
131
+ };
132
+ function clampTranslate(state, image) {
133
+ if (!image) return state;
134
+ const maxX = image.offsetWidth * (state.scale - 1) / 2;
135
+ const maxY = image.offsetHeight * (state.scale - 1) / 2;
136
+ return {
137
+ ...state,
138
+ translateX: Math.max(-maxX, Math.min(maxX, state.translateX)),
139
+ translateY: Math.max(-maxY, Math.min(maxY, state.translateY))
140
+ };
141
+ }
142
+ function useLightboxZoom({ enabled, maxScale, currentIndex }) {
143
+ const state = reactive({ ...INITIAL_ZOOM_STATE });
144
+ const imageRef = ref(null);
145
+ const isDragging = ref(false);
146
+ const isAdjusting = ref(false);
147
+ const didDrag = ref(false);
148
+ const dragStart = {
149
+ x: 0,
150
+ y: 0
151
+ };
152
+ const translateStart = {
153
+ x: 0,
154
+ y: 0
155
+ };
156
+ const lastPinchDistance = ref(null);
157
+ const lastPointerType = ref("mouse");
158
+ const lastClickPointerType = ref("mouse");
159
+ let adjustTimeout = -1;
160
+ const setState = (next) => Object.assign(state, next);
161
+ const resetZoom = () => {
162
+ setState(INITIAL_ZOOM_STATE);
163
+ isDragging.value = false;
164
+ };
165
+ const beginAdjust = () => {
166
+ isAdjusting.value = true;
167
+ window.clearTimeout(adjustTimeout);
168
+ adjustTimeout = window.setTimeout(() => {
169
+ isAdjusting.value = false;
170
+ }, 120);
171
+ };
172
+ const toggleZoom = () => {
173
+ if (!enabled()) return;
174
+ setState(state.isZoomed ? INITIAL_ZOOM_STATE : {
175
+ scale: Math.min(2, maxScale()),
176
+ translateX: 0,
177
+ translateY: 0,
178
+ isZoomed: true
179
+ });
180
+ };
181
+ const zoomAtPoint = (deltaScale, clientX, clientY) => {
182
+ const nextScale = Math.max(1, Math.min(maxScale(), state.scale + deltaScale));
183
+ if (nextScale === 1) {
184
+ resetZoom();
185
+ return;
186
+ }
187
+ const image = imageRef.value;
188
+ let translateX = state.translateX;
189
+ let translateY = state.translateY;
190
+ if (image && clientX !== void 0 && clientY !== void 0) {
191
+ const rect = image.getBoundingClientRect();
192
+ const scaleRatio = nextScale / state.scale;
193
+ const offsetX = clientX - (rect.left + rect.width / 2) + state.translateX;
194
+ const offsetY = clientY - (rect.top + rect.height / 2) + state.translateY;
195
+ translateX = offsetX * (1 - scaleRatio) + state.translateX * scaleRatio;
196
+ translateY = offsetY * (1 - scaleRatio) + state.translateY * scaleRatio;
197
+ }
198
+ setState(clampTranslate({
199
+ scale: nextScale,
200
+ translateX,
201
+ translateY,
202
+ isZoomed: true
203
+ }, image));
204
+ };
205
+ const panZoom = (deltaX, deltaY) => {
206
+ if (!state.isZoomed) return false;
207
+ setState(clampTranslate({
208
+ ...state,
209
+ translateX: state.translateX + deltaX,
210
+ translateY: state.translateY + deltaY
211
+ }, imageRef.value));
212
+ return true;
213
+ };
214
+ const handleWheel = (event) => {
215
+ if (event.deltaY === 0) return;
216
+ event.preventDefault();
217
+ beginAdjust();
218
+ zoomAtPoint(event.deltaY > 0 ? -.2 : .2, event.clientX, event.clientY);
219
+ };
220
+ const setImageRef = (node) => {
221
+ imageRef.value?.removeEventListener("wheel", handleWheel);
222
+ imageRef.value = node instanceof HTMLImageElement ? node : null;
223
+ imageRef.value?.addEventListener("wheel", handleWheel, { passive: false });
224
+ };
225
+ const handlePointerDown = (event) => {
226
+ lastPointerType.value = event.pointerType || "mouse";
227
+ if (!state.isZoomed || !enabled()) return;
228
+ isDragging.value = true;
229
+ didDrag.value = false;
230
+ dragStart.x = event.clientX;
231
+ dragStart.y = event.clientY;
232
+ translateStart.x = state.translateX;
233
+ translateStart.y = state.translateY;
234
+ if (typeof event.target?.setPointerCapture === "function") event.target.setPointerCapture(event.pointerId);
235
+ };
236
+ const handlePointerMove = (event) => {
237
+ if (!isDragging.value || !enabled()) return;
238
+ const dx = event.clientX - dragStart.x;
239
+ const dy = event.clientY - dragStart.y;
240
+ if (Math.abs(dx) > 3 || Math.abs(dy) > 3) didDrag.value = true;
241
+ setState(clampTranslate({
242
+ ...state,
243
+ translateX: translateStart.x + dx,
244
+ translateY: translateStart.y + dy
245
+ }, imageRef.value));
246
+ };
247
+ const handlePointerUp = () => {
248
+ isDragging.value = false;
249
+ };
250
+ const handlePointerCancel = () => {
251
+ isDragging.value = false;
252
+ didDrag.value = false;
253
+ lastPinchDistance.value = null;
254
+ };
255
+ const handleTouchMove = (event) => {
256
+ if (!enabled() || event.touches.length !== 2) {
257
+ lastPinchDistance.value = null;
258
+ return;
259
+ }
260
+ const [touch1, touch2] = event.touches;
261
+ const distance = Math.hypot(touch1.clientX - touch2.clientX, touch1.clientY - touch2.clientY);
262
+ if (lastPinchDistance.value !== null) {
263
+ beginAdjust();
264
+ zoomAtPoint((distance - lastPinchDistance.value) * .01, (touch1.clientX + touch2.clientX) / 2, (touch1.clientY + touch2.clientY) / 2);
265
+ }
266
+ lastPinchDistance.value = distance;
267
+ };
268
+ const handleTouchEnd = () => {
269
+ lastPinchDistance.value = null;
270
+ };
271
+ const handleClick = (event) => {
272
+ if (!enabled() || event.detail > 1) return;
273
+ if (didDrag.value) {
274
+ didDrag.value = false;
275
+ return;
276
+ }
277
+ const pointerType = lastPointerType.value;
278
+ lastPointerType.value = "mouse";
279
+ lastClickPointerType.value = pointerType;
280
+ if (pointerType !== "touch") toggleZoom();
281
+ };
282
+ const handleDoubleClick = () => {
283
+ if (enabled() && lastClickPointerType.value === "touch") toggleZoom();
284
+ };
285
+ const imageStyle = computed(() => ({ transform: `scale(${state.scale}) translate(${state.translateX / state.scale}px, ${state.translateY / state.scale}px)` }));
286
+ const publicState = computed(() => ({
287
+ scale: state.scale,
288
+ isZoomed: state.isZoomed
289
+ }));
290
+ const getImageProps = () => ({
291
+ ref: setImageRef,
292
+ style: imageStyle.value,
293
+ "data-zoom-enabled": enabled() || void 0,
294
+ "data-zoomed": state.isZoomed || void 0,
295
+ "data-dragging": isDragging.value || isAdjusting.value || void 0,
296
+ onPointerdown: handlePointerDown,
297
+ onPointermove: handlePointerMove,
298
+ onPointerup: handlePointerUp,
299
+ onPointercancel: handlePointerCancel,
300
+ onLostpointercapture: handlePointerUp,
301
+ onClick: handleClick,
302
+ onDblclick: handleDoubleClick,
303
+ onTouchmove: handleTouchMove,
304
+ onTouchend: handleTouchEnd
305
+ });
306
+ watch([currentIndex, enabled], ([, isEnabled]) => {
307
+ if (!isEnabled || state.isZoomed) resetZoom();
308
+ if (!isEnabled) {
309
+ lastPinchDistance.value = null;
310
+ didDrag.value = false;
311
+ }
312
+ });
313
+ onBeforeUnmount(() => {
314
+ window.clearTimeout(adjustTimeout);
315
+ imageRef.value?.removeEventListener("wheel", handleWheel);
316
+ });
317
+ return {
318
+ zoomState: publicState,
319
+ toggleZoom,
320
+ resetZoom,
321
+ panZoom,
322
+ getImageProps
323
+ };
324
+ }
325
+ var Lightbox_module_default = {
326
+ root: "_root_13vd4_1",
327
+ overlay: "_overlay_13vd4_9",
328
+ content: "_content_13vd4_15",
329
+ toolbar: "_toolbar_13vd4_24",
330
+ toolbarGroup: "_toolbarGroup_13vd4_33",
331
+ toolbarButton: "_toolbarButton_13vd4_38",
332
+ counter: "_counter_13vd4_55",
333
+ slides: "_slides_13vd4_61",
334
+ slidesViewport: "_slidesViewport_13vd4_67",
335
+ slidesContainer: "_slidesContainer_13vd4_72",
336
+ slide: "_slide_13vd4_61",
337
+ slideImage: "_slideImage_13vd4_85",
338
+ slideVideo: "_slideVideo_13vd4_108",
339
+ thumbnails: "_thumbnails_13vd4_115",
340
+ thumbnailsViewport: "_thumbnailsViewport_13vd4_121",
341
+ thumbnailsContainer: "_thumbnailsContainer_13vd4_128",
342
+ thumbnail: "_thumbnail_13vd4_115",
343
+ thumbnailImage: "_thumbnailImage_13vd4_156",
344
+ navigation: "_navigation_13vd4_161",
345
+ navigationButton: "_navigationButton_13vd4_172",
346
+ caption: "_caption_13vd4_190",
347
+ closeButton: "_closeButton_13vd4_198"
348
+ };
349
+ //#endregion
350
+ //#region src/LightboxRoot/LightboxRoot.vue?vue&type=script&setup=true&lang.ts
351
+ var _hoisted_1$2 = ["aria-label"];
352
+ var _hoisted_2$1 = {
353
+ key: 0,
354
+ "data-autofocus": "",
355
+ tabindex: "0",
356
+ style: {
357
+ "position": "fixed",
358
+ "width": "1px",
359
+ "height": "1px",
360
+ "overflow": "hidden"
361
+ }
362
+ };
363
+ var lightboxRootDefaultProps = {
364
+ withZoom: false,
365
+ withThumbnails: false,
366
+ withFullscreen: false,
367
+ withDownload: false,
368
+ loop: false,
369
+ closeOnClickOutside: false,
370
+ closeOnSwipeDown: true,
371
+ withKeyboardEvents: true,
372
+ returnFocus: true,
373
+ withInitialFocusPlaceholder: true,
374
+ withinPortal: true,
375
+ transitionDuration: 200,
376
+ zoomMaxScale: 3,
377
+ withSlideTransition: false
378
+ };
379
+ //#endregion
380
+ //#region src/LightboxRoot/LightboxRoot.vue
381
+ var LightboxRoot_default = /* @__PURE__ */ defineComponent({
382
+ name: "LightboxRoot",
383
+ inheritAttrs: false,
384
+ __name: "LightboxRoot",
385
+ props: {
386
+ opened: { type: Boolean },
387
+ slides: {},
388
+ currentIndex: { default: void 0 },
389
+ labels: { default: void 0 },
390
+ withZoom: {
391
+ type: Boolean,
392
+ default: void 0
393
+ },
394
+ withThumbnails: {
395
+ type: Boolean,
396
+ default: void 0
397
+ },
398
+ withFullscreen: {
399
+ type: Boolean,
400
+ default: void 0
401
+ },
402
+ withDownload: {
403
+ type: Boolean,
404
+ default: void 0
405
+ },
406
+ loop: {
407
+ type: Boolean,
408
+ default: void 0
409
+ },
410
+ closeOnClickOutside: {
411
+ type: Boolean,
412
+ default: void 0
413
+ },
414
+ closeOnSwipeDown: {
415
+ type: Boolean,
416
+ default: void 0
417
+ },
418
+ withKeyboardEvents: {
419
+ type: Boolean,
420
+ default: void 0
421
+ },
422
+ returnFocus: {
423
+ type: Boolean,
424
+ default: void 0
425
+ },
426
+ withInitialFocusPlaceholder: {
427
+ type: Boolean,
428
+ default: void 0
429
+ },
430
+ withinPortal: {
431
+ type: Boolean,
432
+ default: void 0
433
+ },
434
+ zIndex: { default: void 0 },
435
+ transitionDuration: { default: void 0 },
436
+ transitionProps: { default: void 0 },
437
+ emblaOptions: { default: void 0 },
438
+ zoomMaxScale: { default: void 0 },
439
+ withSlideTransition: {
440
+ type: Boolean,
441
+ default: void 0
442
+ },
443
+ classNames: { default: void 0 },
444
+ styles: { default: void 0 },
445
+ vars: { default: void 0 },
446
+ unstyled: {
447
+ type: Boolean,
448
+ default: void 0
449
+ }
450
+ },
451
+ emits: [
452
+ "close",
453
+ "update:opened",
454
+ "indexChange",
455
+ "update:currentIndex"
456
+ ],
457
+ setup(__props, { emit: __emit }) {
458
+ const rawProps = __props;
459
+ const emit = __emit;
460
+ const props = useProps("LightboxRoot", lightboxRootDefaultProps, rawProps);
461
+ const attrs = useAttrs();
462
+ const contentRef = ref(null);
463
+ const internalIndex = ref(props.currentIndex ?? 0);
464
+ const thumbnailsVisible = ref(Boolean(props.withThumbnails));
465
+ const isFullscreen = ref(false);
466
+ const ownsFullscreen = ref(false);
467
+ const { dir } = useDirection();
468
+ let previousActive = null;
469
+ let previousOverflow = "";
470
+ const labels = computed(() => ({
471
+ ...DEFAULT_LABELS,
472
+ ...props.labels
473
+ }));
474
+ const currentIndex = computed(() => props.currentIndex ?? internalIndex.value);
475
+ const currentSlideLabel = computed(() => {
476
+ const slide = props.slides[currentIndex.value];
477
+ if (!slide || slide.type === "custom") return void 0;
478
+ return slide.type === "video" ? slide.label : slide.alt;
479
+ });
480
+ const zoom = useLightboxZoom({
481
+ enabled: () => props.withZoom,
482
+ maxScale: () => props.zoomMaxScale,
483
+ currentIndex: () => currentIndex.value
484
+ });
485
+ const getStyles = useStyles({
486
+ name: "Lightbox",
487
+ props,
488
+ classes: Lightbox_module_default,
489
+ classNames: props.classNames,
490
+ styles: props.styles,
491
+ unstyled: props.unstyled,
492
+ vars: props.vars
493
+ });
494
+ const rootStyle = computed(() => ({
495
+ "--lightbox-transition-duration": `${props.transitionDuration}ms`,
496
+ "--lightbox-z-index": props.zIndex == null ? void 0 : String(props.zIndex)
497
+ }));
498
+ const contentTransition = computed(() => ({
499
+ transition: {
500
+ common: { transformOrigin: "center center" },
501
+ in: {
502
+ opacity: 1,
503
+ transform: "scale(1)"
504
+ },
505
+ out: {
506
+ opacity: 0,
507
+ transform: "scale(0.95)"
508
+ },
509
+ transitionProperty: "transform, opacity"
510
+ },
511
+ duration: props.transitionDuration,
512
+ timingFunction: "ease",
513
+ ...props.transitionProps
514
+ }));
515
+ const overlayTransition = computed(() => ({
516
+ transition: "fade",
517
+ duration: contentTransition.value.duration,
518
+ exitDuration: contentTransition.value.exitDuration,
519
+ timingFunction: contentTransition.value.timingFunction,
520
+ enterDelay: contentTransition.value.enterDelay,
521
+ exitDelay: contentTransition.value.exitDelay,
522
+ keepMounted: contentTransition.value.keepMounted
523
+ }));
524
+ const setIndex = (index) => {
525
+ const next = Math.max(0, Math.min(Math.round(index), Math.max(0, props.slides.length - 1)));
526
+ if (next === currentIndex.value) return;
527
+ internalIndex.value = next;
528
+ emit("update:currentIndex", next);
529
+ emit("indexChange", next);
530
+ };
531
+ const next = () => setIndex(currentIndex.value + 1 < props.slides.length ? currentIndex.value + 1 : props.loop && props.slides.length ? 0 : currentIndex.value);
532
+ const prev = () => setIndex(currentIndex.value > 0 ? currentIndex.value - 1 : props.loop && props.slides.length ? props.slides.length - 1 : 0);
533
+ const close = () => {
534
+ emit("update:opened", false);
535
+ emit("close");
536
+ };
537
+ const toggleThumbnails = () => {
538
+ thumbnailsVisible.value = !thumbnailsVisible.value;
539
+ };
540
+ const toggleFullscreen = async () => {
541
+ if (!props.withFullscreen || typeof document === "undefined") return;
542
+ if (document.fullscreenElement) {
543
+ ownsFullscreen.value = false;
544
+ await document.exitFullscreen?.();
545
+ } else {
546
+ await contentRef.value?.requestFullscreen?.();
547
+ ownsFullscreen.value = document.fullscreenElement === contentRef.value;
548
+ }
549
+ isFullscreen.value = Boolean(document.fullscreenElement);
550
+ };
551
+ const handleKeydown = (event) => {
552
+ if (!props.opened) return;
553
+ if (event.key === "Escape") {
554
+ event.preventDefault();
555
+ close();
556
+ return;
557
+ }
558
+ if (!props.withKeyboardEvents || event.defaultPrevented || event.ctrlKey || event.metaKey || event.altKey) return;
559
+ if (event.target instanceof Element) {
560
+ if (event.target.closest("input, textarea, select, video, audio, [contenteditable]")) return;
561
+ if (event.target.closest("[data-lightbox-slide]") && event.target.closest("button, a[href], [role=\"button\"], [role=\"link\"], [role=\"slider\"], [role=\"textbox\"], [role=\"combobox\"], [role=\"listbox\"], [role=\"menu\"], [role=\"tablist\"], [role=\"tree\"], [role=\"grid\"]")) return;
562
+ }
563
+ const leading = dir.value === "rtl" ? next : prev;
564
+ const trailing = dir.value === "rtl" ? prev : next;
565
+ if (event.key === "ArrowLeft") {
566
+ event.preventDefault();
567
+ if (!zoom.panZoom(50, 0)) leading();
568
+ }
569
+ if (event.key === "ArrowRight") {
570
+ event.preventDefault();
571
+ if (!zoom.panZoom(-50, 0)) trailing();
572
+ }
573
+ if (event.key === "ArrowUp" && zoom.panZoom(0, 50)) event.preventDefault();
574
+ if (event.key === "ArrowDown" && zoom.panZoom(0, -50)) event.preventDefault();
575
+ if (event.key.toLowerCase() === "f" && props.withFullscreen) {
576
+ event.preventDefault();
577
+ toggleFullscreen();
578
+ }
579
+ if (event.key.toLowerCase() === "t" && props.withThumbnails) {
580
+ event.preventDefault();
581
+ toggleThumbnails();
582
+ }
583
+ if (event.key.toLowerCase() === "z" && props.withZoom) {
584
+ event.preventDefault();
585
+ zoom.toggleZoom();
586
+ }
587
+ };
588
+ const handleFullscreen = () => {
589
+ isFullscreen.value = Boolean(document.fullscreenElement);
590
+ if (!document.fullscreenElement) ownsFullscreen.value = false;
591
+ };
592
+ watch(() => props.currentIndex, (value) => {
593
+ if (value != null) internalIndex.value = value;
594
+ });
595
+ watch(() => props.withThumbnails, (value) => {
596
+ thumbnailsVisible.value = Boolean(value);
597
+ });
598
+ watch(() => props.slides.length, (length) => {
599
+ if (length > 0 && currentIndex.value > length - 1) setIndex(length - 1);
600
+ });
601
+ watch(() => props.opened, async (opened) => {
602
+ if (typeof document === "undefined") return;
603
+ if (opened) {
604
+ previousActive = document.activeElement instanceof HTMLElement ? document.activeElement : null;
605
+ previousOverflow = document.body.style.overflow;
606
+ document.body.style.overflow = "hidden";
607
+ document.addEventListener("keydown", handleKeydown);
608
+ document.addEventListener("fullscreenchange", handleFullscreen);
609
+ await nextTick();
610
+ contentRef.value?.focus({ preventScroll: true });
611
+ } else {
612
+ zoom.resetZoom();
613
+ thumbnailsVisible.value = Boolean(props.withThumbnails);
614
+ if (ownsFullscreen.value && document.fullscreenElement) {
615
+ ownsFullscreen.value = false;
616
+ document.exitFullscreen?.().catch(() => {});
617
+ }
618
+ document.body.style.overflow = previousOverflow;
619
+ document.removeEventListener("keydown", handleKeydown);
620
+ document.removeEventListener("fullscreenchange", handleFullscreen);
621
+ if (props.returnFocus) previousActive?.focus({ preventScroll: true });
622
+ }
623
+ }, { immediate: true });
624
+ onBeforeUnmount(() => {
625
+ if (typeof document !== "undefined") {
626
+ if (ownsFullscreen.value && document.fullscreenElement) document.exitFullscreen?.().catch(() => {});
627
+ document.body.style.overflow = previousOverflow;
628
+ document.removeEventListener("keydown", handleKeydown);
629
+ document.removeEventListener("fullscreenchange", handleFullscreen);
630
+ }
631
+ });
632
+ provideLightboxContext({
633
+ getStyles,
634
+ get labels() {
635
+ return labels.value;
636
+ },
637
+ get opened() {
638
+ return props.opened;
639
+ },
640
+ get slides() {
641
+ return props.slides;
642
+ },
643
+ get currentIndex() {
644
+ return currentIndex.value;
645
+ },
646
+ setIndex,
647
+ next,
648
+ prev,
649
+ close,
650
+ get thumbnailsVisible() {
651
+ return thumbnailsVisible.value;
652
+ },
653
+ toggleThumbnails,
654
+ get isFullscreen() {
655
+ return isFullscreen.value;
656
+ },
657
+ toggleFullscreen,
658
+ get zoomed() {
659
+ return zoom.zoomState.value.isZoomed;
660
+ },
661
+ toggleZoom: zoom.toggleZoom,
662
+ get zoomScale() {
663
+ return zoom.zoomState.value.scale;
664
+ },
665
+ panZoom: zoom.panZoom,
666
+ getImageZoomProps: zoom.getImageProps,
667
+ get withZoom() {
668
+ return props.withZoom;
669
+ },
670
+ get withThumbnails() {
671
+ return props.withThumbnails;
672
+ },
673
+ get withFullscreen() {
674
+ return props.withFullscreen;
675
+ },
676
+ get withDownload() {
677
+ return props.withDownload;
678
+ },
679
+ get loop() {
680
+ return props.loop;
681
+ },
682
+ get closeOnSwipeDown() {
683
+ return props.closeOnSwipeDown;
684
+ },
685
+ get closeOnClickOutside() {
686
+ return props.closeOnClickOutside;
687
+ },
688
+ get withSlideTransition() {
689
+ return props.withSlideTransition;
690
+ },
691
+ get transitionDuration() {
692
+ return props.transitionDuration;
693
+ },
694
+ get emblaOptions() {
695
+ return props.emblaOptions;
696
+ }
697
+ });
698
+ return (_ctx, _cache) => {
699
+ return openBlock(), createBlock(Teleport, {
700
+ to: "body",
701
+ disabled: !unref(props).withinPortal
702
+ }, [createVNode(unref(Box), mergeProps(unref(getStyles)("root"), { style: rootStyle.value }), {
703
+ default: withCtx(() => [createVNode(unref(Transition), mergeProps({ mounted: unref(props).opened }, overlayTransition.value), {
704
+ default: withCtx((transitionStyles) => [createElementVNode("div", mergeProps(unref(getStyles)("overlay", { style: transitionStyles }), { onClick: _cache[0] || (_cache[0] = ($event) => unref(props).closeOnClickOutside ? close() : void 0) }), null, 16)]),
705
+ _: 1
706
+ }, 16, ["mounted"]), createVNode(unref(Transition), mergeProps({ mounted: unref(props).opened }, contentTransition.value), {
707
+ default: withCtx((transitionStyles) => [createVNode(unref(FocusTrap), { active: unref(props).opened }, {
708
+ default: withCtx(() => [createElementVNode("div", mergeProps({
709
+ ref_key: "contentRef",
710
+ ref: contentRef
711
+ }, {
712
+ ...unref(attrs),
713
+ ...unref(getStyles)("content", { style: transitionStyles })
714
+ }, {
715
+ role: "dialog",
716
+ "aria-modal": "true",
717
+ "aria-label": labels.value.lightboxLabel,
718
+ tabindex: "-1",
719
+ onClick: _cache[1] || (_cache[1] = withModifiers(($event) => unref(props).closeOnClickOutside ? close() : void 0, ["self"]))
720
+ }), [
721
+ unref(props).withInitialFocusPlaceholder ? (openBlock(), createElementBlock("span", _hoisted_2$1)) : createCommentVNode("", true),
722
+ createVNode(unref(VisuallyHidden), {
723
+ role: "status",
724
+ "aria-live": "polite",
725
+ "aria-atomic": "true"
726
+ }, {
727
+ default: withCtx(() => [unref(props).slides.length ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createTextVNode(toDisplayString(labels.value.slideLabel(currentIndex.value + 1, unref(props).slides.length)), 1), currentSlideLabel.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createTextVNode(": " + toDisplayString(currentSlideLabel.value), 1)], 64)) : createCommentVNode("", true)], 64)) : createCommentVNode("", true)]),
728
+ _: 1
729
+ }),
730
+ renderSlot(_ctx.$slots, "default")
731
+ ], 16, _hoisted_1$2)]),
732
+ _: 2
733
+ }, 1032, ["active"])]),
734
+ _: 3
735
+ }, 16, ["mounted"])]),
736
+ _: 3
737
+ }, 16, ["style"])], 8, ["disabled"]);
738
+ };
739
+ }
740
+ });
741
+ //#endregion
742
+ //#region src/LightboxSlide/LightboxSlide.vue?vue&type=script&setup=true&lang.ts
743
+ var _hoisted_1$1 = [
744
+ "src",
745
+ "poster",
746
+ "aria-label",
747
+ "autoplay",
748
+ "tabindex"
749
+ ];
750
+ var _hoisted_2 = [
751
+ "src",
752
+ "kind",
753
+ "srclang",
754
+ "label",
755
+ "default"
756
+ ];
757
+ var _hoisted_3 = [
758
+ "src",
759
+ "alt",
760
+ "srcset",
761
+ "sizes",
762
+ "loading"
763
+ ];
764
+ //#endregion
765
+ //#region src/LightboxSlide/LightboxSlide.vue
766
+ var LightboxSlide_default = /* @__PURE__ */ defineComponent({
767
+ name: "LightboxSlide",
768
+ inheritAttrs: false,
769
+ __name: "LightboxSlide",
770
+ props: {
771
+ slide: {},
772
+ index: {}
773
+ },
774
+ setup(__props) {
775
+ const props = __props;
776
+ const attrs = useAttrs();
777
+ const ctx = useLightboxContext();
778
+ const active = computed(() => props.index === ctx.currentIndex);
779
+ const image = computed(() => props.slide);
780
+ const video = computed(() => props.slide);
781
+ const custom = computed(() => props.slide);
782
+ const videoRef = ref(null);
783
+ watch(active, (isActive) => {
784
+ if (!videoRef.value) return;
785
+ if (!isActive) videoRef.value.pause();
786
+ else if (video.value.autoPlay) videoRef.value.play().catch(() => {});
787
+ }, { flush: "post" });
788
+ function handleClick(event) {
789
+ if (ctx.closeOnClickOutside && !event.defaultPrevented && event.target === event.currentTarget) ctx.close();
790
+ }
791
+ return (_ctx, _cache) => {
792
+ return openBlock(), createBlock(unref(Box), mergeProps({
793
+ ...unref(attrs),
794
+ ...unref(ctx).getStyles("slide")
795
+ }, {
796
+ role: "group",
797
+ "aria-roledescription": "slide",
798
+ "data-lightbox-slide": "",
799
+ "aria-label": unref(ctx).labels.slideLabel(__props.index + 1, unref(ctx).slides.length),
800
+ "aria-hidden": !active.value || void 0,
801
+ inert: !active.value || void 0,
802
+ onClick: handleClick
803
+ }), {
804
+ default: withCtx(() => [renderSlot(_ctx.$slots, "default", {
805
+ slide: __props.slide,
806
+ active: active.value
807
+ }, () => [__props.slide.type === "custom" ? (openBlock(), createBlock(resolveDynamicComponent(() => custom.value.render({ active: active.value })), { key: 0 })) : __props.slide.type === "video" ? (openBlock(), createElementBlock("video", mergeProps({
808
+ key: 1,
809
+ ref_key: "videoRef",
810
+ ref: videoRef
811
+ }, unref(ctx).getStyles("slideVideo"), {
812
+ src: video.value.src,
813
+ poster: video.value.poster,
814
+ "aria-label": video.value.label,
815
+ autoplay: video.value.autoPlay && active.value,
816
+ controls: "",
817
+ tabindex: active.value ? 0 : -1
818
+ }), [(openBlock(true), createElementBlock(Fragment, null, renderList(video.value.tracks, (track) => {
819
+ return openBlock(), createElementBlock("track", {
820
+ key: `${track.src}-${track.srcLang}`,
821
+ src: track.src,
822
+ kind: track.kind ?? "subtitles",
823
+ srclang: track.srcLang,
824
+ label: track.label,
825
+ default: track.default
826
+ }, null, 8, _hoisted_2);
827
+ }), 128))], 16, _hoisted_1$1)) : (openBlock(), createElementBlock("img", mergeProps({ key: 2 }, {
828
+ ...unref(ctx).getStyles("slideImage"),
829
+ ...active.value && unref(ctx).withZoom ? unref(ctx).getImageZoomProps() : {}
830
+ }, {
831
+ src: image.value.src,
832
+ alt: image.value.alt ?? "",
833
+ srcset: image.value.srcSet,
834
+ sizes: image.value.sizes,
835
+ loading: image.value.loading ?? (active.value ? "eager" : "lazy"),
836
+ draggable: "false"
837
+ }), null, 16, _hoisted_3))])]),
838
+ _: 3
839
+ }, 16, [
840
+ "aria-label",
841
+ "aria-hidden",
842
+ "inert"
843
+ ]);
844
+ };
845
+ }
846
+ });
847
+ //#endregion
848
+ //#region src/LightboxSlides/LightboxSlides.vue
849
+ var LightboxSlides_default = /* @__PURE__ */ defineComponent({
850
+ name: "LightboxSlides",
851
+ inheritAttrs: false,
852
+ __name: "LightboxSlides",
853
+ setup(__props) {
854
+ const attrs = useAttrs();
855
+ const ctx = useLightboxContext();
856
+ const { dir } = useDirection();
857
+ const swipeStart = ref(null);
858
+ const startIndex = ctx.currentIndex;
859
+ const [emblaNode, emblaApi] = emblaCarouselVue(computed(() => {
860
+ const userWatchDrag = ctx.emblaOptions?.watchDrag;
861
+ return {
862
+ align: "center",
863
+ containScroll: false,
864
+ direction: dir.value,
865
+ ...ctx.emblaOptions,
866
+ axis: "x",
867
+ loop: ctx.loop,
868
+ startIndex,
869
+ watchDrag: (api, event) => {
870
+ if (ctx.zoomed) return false;
871
+ return typeof userWatchDrag === "function" ? userWatchDrag(api, event) : userWatchDrag ?? true;
872
+ }
873
+ };
874
+ }), computed(() => []));
875
+ function handleSelect(api) {
876
+ const index = api.selectedScrollSnap();
877
+ if (index !== ctx.currentIndex) ctx.setIndex(index);
878
+ }
879
+ watch(emblaApi, (api, previousApi) => {
880
+ previousApi?.off("select", handleSelect);
881
+ api?.on("select", handleSelect);
882
+ }, { immediate: true });
883
+ watch(() => ctx.currentIndex, (index) => {
884
+ const api = emblaApi.value;
885
+ if (api && api.selectedScrollSnap() !== index) api.scrollTo(index, !ctx.withSlideTransition);
886
+ });
887
+ function handlePointerDown(event) {
888
+ swipeStart.value = {
889
+ x: event.clientX,
890
+ y: event.clientY
891
+ };
892
+ }
893
+ function handlePointerUp(event) {
894
+ if (!swipeStart.value || !ctx.closeOnSwipeDown || ctx.zoomed) return;
895
+ const x = event.clientX - swipeStart.value.x;
896
+ const y = event.clientY - swipeStart.value.y;
897
+ swipeStart.value = null;
898
+ if (y > 80 && y > Math.abs(x)) ctx.close();
899
+ }
900
+ onBeforeUnmount(() => emblaApi.value?.off("select", handleSelect));
901
+ return (_ctx, _cache) => {
902
+ return openBlock(), createBlock(unref(Box), mergeProps({
903
+ ...unref(attrs),
904
+ ...unref(ctx).getStyles("slides")
905
+ }, {
906
+ role: "region",
907
+ "aria-roledescription": "carousel",
908
+ "aria-label": unref(ctx).labels.slidesLabel
909
+ }), {
910
+ default: withCtx(() => [createElementVNode("div", mergeProps({
911
+ ref_key: "emblaNode",
912
+ ref: emblaNode
913
+ }, unref(ctx).getStyles("slidesViewport"), {
914
+ onPointerdown: handlePointerDown,
915
+ onPointerup: handlePointerUp,
916
+ onPointercancel: _cache[0] || (_cache[0] = ($event) => swipeStart.value = null)
917
+ }), [createElementVNode("div", normalizeProps(guardReactiveProps(unref(ctx).getStyles("slidesContainer"))), [renderSlot(_ctx.$slots, "default")], 16)], 16)]),
918
+ _: 3
919
+ }, 16, ["aria-label"]);
920
+ };
921
+ }
922
+ });
923
+ //#endregion
924
+ //#region src/LightboxThumbnails/LightboxThumbnails.vue?vue&type=script&setup=true&lang.ts
925
+ var _hoisted_1 = ["src"];
926
+ //#endregion
927
+ //#region src/LightboxThumbnails/LightboxThumbnails.vue
928
+ var LightboxThumbnails_default = /* @__PURE__ */ defineComponent({
929
+ name: "LightboxThumbnails",
930
+ inheritAttrs: false,
931
+ __name: "LightboxThumbnails",
932
+ setup(__props) {
933
+ const attrs = useAttrs();
934
+ const ctx = useLightboxContext();
935
+ const { dir } = useDirection();
936
+ const [emblaNode, emblaApi] = emblaCarouselVue(computed(() => ({
937
+ containScroll: "keepSnaps",
938
+ dragFree: true,
939
+ direction: dir.value,
940
+ active: ctx.withThumbnails
941
+ })), computed(() => []));
942
+ function getThumbnailSource(slide) {
943
+ return slide.thumbSrc ?? (slide.type === "video" ? slide.poster : void 0) ?? (slide.type !== "custom" ? slide.src : void 0);
944
+ }
945
+ watch([emblaApi, () => ctx.currentIndex], ([api, index]) => api?.scrollTo(index), { immediate: true });
946
+ return (_ctx, _cache) => {
947
+ return unref(ctx).withThumbnails ? (openBlock(), createBlock(unref(Collapse), mergeProps({
948
+ key: 0,
949
+ expanded: unref(ctx).thumbnailsVisible,
950
+ "transition-duration": unref(ctx).transitionDuration,
951
+ "keep-mounted": false
952
+ }, {
953
+ ...unref(attrs),
954
+ ...unref(ctx).getStyles("thumbnails")
955
+ }), {
956
+ default: withCtx(() => [createElementVNode("div", mergeProps({
957
+ ref_key: "emblaNode",
958
+ ref: emblaNode
959
+ }, unref(ctx).getStyles("thumbnailsViewport")), [createElementVNode("div", normalizeProps(guardReactiveProps(unref(ctx).getStyles("thumbnailsContainer"))), [(openBlock(true), createElementBlock(Fragment, null, renderList(unref(ctx).slides, (slide, index) => {
960
+ return openBlock(), createBlock(unref(UnstyledButton), mergeProps({ key: index }, { ref_for: true }, unref(ctx).getStyles("thumbnail"), {
961
+ "data-active": index === unref(ctx).currentIndex || void 0,
962
+ "aria-label": unref(ctx).labels.thumbnailLabel(index + 1, unref(ctx).slides.length),
963
+ "aria-current": index === unref(ctx).currentIndex || void 0,
964
+ onClick: ($event) => unref(ctx).setIndex(index)
965
+ }), {
966
+ default: withCtx(() => [slide.type === "custom" && slide.renderThumb ? (openBlock(), createBlock(resolveDynamicComponent(() => slide.renderThumb?.()), { key: 0 })) : getThumbnailSource(slide) ? (openBlock(), createElementBlock("img", mergeProps({
967
+ key: 1,
968
+ ref_for: true
969
+ }, unref(ctx).getStyles("thumbnailImage"), {
970
+ src: getThumbnailSource(slide),
971
+ alt: "",
972
+ loading: "lazy",
973
+ draggable: "false"
974
+ }), null, 16, _hoisted_1)) : createCommentVNode("", true)]),
975
+ _: 2
976
+ }, 1040, [
977
+ "data-active",
978
+ "aria-label",
979
+ "aria-current",
980
+ "onClick"
981
+ ]);
982
+ }), 128))], 16)], 16)]),
983
+ _: 1
984
+ }, 16, ["expanded", "transition-duration"])) : createCommentVNode("", true);
985
+ };
986
+ }
987
+ });
988
+ //#endregion
989
+ //#region src/LightboxToolbar/toolbar-items.ts
990
+ var ALLOWED_DOWNLOAD_PROTOCOLS = [
991
+ "http:",
992
+ "https:",
993
+ "blob:",
994
+ "data:"
995
+ ];
996
+ var downloadsInProgress = /* @__PURE__ */ new Set();
997
+ var icon = (path) => h("svg", {
998
+ viewBox: "0 0 24 24",
999
+ width: 20,
1000
+ height: 20,
1001
+ fill: "currentColor",
1002
+ "aria-hidden": "true"
1003
+ }, [h("path", { d: path })]);
1004
+ function parseDownloadUrl(src) {
1005
+ try {
1006
+ const url = new URL(src, window.location.href);
1007
+ return ALLOWED_DOWNLOAD_PROTOCOLS.includes(url.protocol) ? url : null;
1008
+ } catch {
1009
+ return null;
1010
+ }
1011
+ }
1012
+ function getDownloadFileName(url) {
1013
+ if (url.protocol === "data:") return "download";
1014
+ const lastSegment = url.pathname.split("/").pop() || "";
1015
+ try {
1016
+ return decodeURIComponent(lastSegment) || "download";
1017
+ } catch {
1018
+ return lastSegment || "download";
1019
+ }
1020
+ }
1021
+ function openInNewTab(src) {
1022
+ const link = document.createElement("a");
1023
+ link.href = src;
1024
+ link.target = "_blank";
1025
+ link.rel = "noopener noreferrer";
1026
+ document.body.appendChild(link);
1027
+ link.click();
1028
+ link.remove();
1029
+ }
1030
+ function downloadFile(src) {
1031
+ const url = parseDownloadUrl(src);
1032
+ if (!url || downloadsInProgress.has(src)) return;
1033
+ if ((url.protocol === "http:" || url.protocol === "https:") && url.origin !== window.location.origin) {
1034
+ openInNewTab(src);
1035
+ return;
1036
+ }
1037
+ downloadsInProgress.add(src);
1038
+ fetch(src).then((response) => {
1039
+ if (!response.ok) throw new Error(`Failed to download ${src}: ${response.status}`);
1040
+ return response.blob();
1041
+ }).then((blob) => {
1042
+ const objectUrl = URL.createObjectURL(blob);
1043
+ const link = document.createElement("a");
1044
+ link.href = objectUrl;
1045
+ link.download = getDownloadFileName(url);
1046
+ document.body.appendChild(link);
1047
+ link.click();
1048
+ link.remove();
1049
+ window.setTimeout(() => URL.revokeObjectURL(objectUrl), 1e3);
1050
+ }).catch(() => openInNewTab(src)).finally(() => downloadsInProgress.delete(src));
1051
+ }
1052
+ function createFullscreenToolbarItem(toggle, isFullscreen, labels = DEFAULT_LABELS) {
1053
+ return {
1054
+ key: "fullscreen",
1055
+ icon: icon(isFullscreen ? "M9 3v6H3V7h4V3h2zm6 0h2v4h4v2h-6V3zM3 15h6v6H7v-4H3v-2zm12 0h6v2h-4v4h-2v-6z" : "M3 3h6v2H5v4H3V3zm12 0h6v6h-2V5h-4V3zM3 15h2v4h4v2H3v-6zm18 0v6h-6v-2h4v-4h2z"),
1056
+ label: isFullscreen ? labels.exitFullscreenLabel : labels.enterFullscreenLabel,
1057
+ onClick: toggle,
1058
+ position: "right"
1059
+ };
1060
+ }
1061
+ function createThumbnailsToolbarItem(toggle, visible, labels = DEFAULT_LABELS) {
1062
+ return {
1063
+ key: "thumbnails",
1064
+ icon: icon("M3 3h8v8H3V3zm0 10h8v8H3v-8zm10-10h8v8h-8V3zm0 10h8v8h-8v-8z"),
1065
+ label: visible ? labels.hideThumbnailsLabel : labels.showThumbnailsLabel,
1066
+ onClick: toggle,
1067
+ position: "left"
1068
+ };
1069
+ }
1070
+ function createDownloadToolbarItem(src, labels = DEFAULT_LABELS) {
1071
+ return {
1072
+ key: "download",
1073
+ icon: icon("M12 16l-6-6h4V4h4v6h4l-6 6zm-8 2h16v2H4v-2z"),
1074
+ label: labels.downloadLabel,
1075
+ position: "right",
1076
+ onClick: () => downloadFile(src)
1077
+ };
1078
+ }
1079
+ function createCloseToolbarItem(onClose, labels = DEFAULT_LABELS) {
1080
+ return {
1081
+ key: "close",
1082
+ icon: icon("M18.3 5.7a1 1 0 0 0-1.4 0L12 10.6 7.1 5.7a1 1 0 0 0-1.4 1.4L10.6 12l-4.9 4.9a1 1 0 1 0 1.4 1.4L12 13.4l4.9 4.9a1 1 0 0 0 1.4-1.4L13.4 12l4.9-4.9a1 1 0 0 0 0-1.4z"),
1083
+ label: labels.closeLabel,
1084
+ onClick: onClose,
1085
+ position: "right"
1086
+ };
1087
+ }
1088
+ //#endregion
1089
+ //#region src/LightboxToolbar/LightboxToolbar.vue
1090
+ var LightboxToolbar_default = /* @__PURE__ */ defineComponent({
1091
+ name: "LightboxToolbar",
1092
+ inheritAttrs: false,
1093
+ __name: "LightboxToolbar",
1094
+ props: { toolbarItems: { type: [Array, Function] } },
1095
+ setup(__props) {
1096
+ const props = __props;
1097
+ const attrs = useAttrs();
1098
+ const ctx = useLightboxContext();
1099
+ const payload = computed(() => ({
1100
+ slides: ctx.slides,
1101
+ currentIndex: ctx.currentIndex,
1102
+ setIndex: ctx.setIndex,
1103
+ next: ctx.next,
1104
+ prev: ctx.prev,
1105
+ close: ctx.close,
1106
+ thumbnailsVisible: ctx.thumbnailsVisible,
1107
+ toggleThumbnails: ctx.toggleThumbnails,
1108
+ isFullscreen: ctx.isFullscreen,
1109
+ toggleFullscreen: ctx.toggleFullscreen,
1110
+ zoomed: ctx.zoomed,
1111
+ toggleZoom: ctx.toggleZoom,
1112
+ labels: ctx.labels
1113
+ }));
1114
+ const items = computed(() => {
1115
+ if (typeof props.toolbarItems === "function") return props.toolbarItems(payload.value);
1116
+ if (props.toolbarItems) return props.toolbarItems;
1117
+ const result = [];
1118
+ if (ctx.withThumbnails) result.push(createThumbnailsToolbarItem(ctx.toggleThumbnails, ctx.thumbnailsVisible, ctx.labels));
1119
+ if (ctx.withFullscreen) result.push(createFullscreenToolbarItem(ctx.toggleFullscreen, ctx.isFullscreen, ctx.labels));
1120
+ const slide = ctx.slides[ctx.currentIndex];
1121
+ if (ctx.withDownload && slide && slide.type !== "custom") result.push(createDownloadToolbarItem(slide.src, ctx.labels));
1122
+ result.push(createCloseToolbarItem(ctx.close, ctx.labels));
1123
+ return result;
1124
+ });
1125
+ const left = computed(() => items.value.filter((item) => item.position === "left"));
1126
+ const right = computed(() => items.value.filter((item) => item.position !== "left"));
1127
+ return (_ctx, _cache) => {
1128
+ return openBlock(), createBlock(unref(Box), normalizeProps(guardReactiveProps({
1129
+ ...unref(attrs),
1130
+ ...unref(ctx).getStyles("toolbar")
1131
+ })), {
1132
+ default: withCtx(() => [
1133
+ createElementVNode("div", normalizeProps(guardReactiveProps(unref(ctx).getStyles("toolbarGroup"))), [(openBlock(true), createElementBlock(Fragment, null, renderList(left.value, (item) => {
1134
+ return openBlock(), createBlock(unref(UnstyledButton), mergeProps({ key: item.key }, { ref_for: true }, unref(ctx).getStyles("toolbarButton"), {
1135
+ "aria-label": item.label,
1136
+ onClick: item.onClick
1137
+ }), {
1138
+ default: withCtx(() => [(openBlock(), createBlock(resolveDynamicComponent(() => item.icon)))]),
1139
+ _: 2
1140
+ }, 1040, ["aria-label", "onClick"]);
1141
+ }), 128))], 16),
1142
+ createElementVNode("span", mergeProps(unref(ctx).getStyles("counter"), { "aria-hidden": "true" }), toDisplayString(unref(ctx).slides.length ? `${unref(ctx).currentIndex + 1} / ${unref(ctx).slides.length}` : ""), 17),
1143
+ createElementVNode("div", normalizeProps(guardReactiveProps(unref(ctx).getStyles("toolbarGroup"))), [(openBlock(true), createElementBlock(Fragment, null, renderList(right.value, (item) => {
1144
+ return openBlock(), createBlock(unref(UnstyledButton), mergeProps({ key: item.key }, { ref_for: true }, unref(ctx).getStyles("toolbarButton"), {
1145
+ "aria-label": item.label,
1146
+ onClick: item.onClick
1147
+ }), {
1148
+ default: withCtx(() => [(openBlock(), createBlock(resolveDynamicComponent(() => item.icon)))]),
1149
+ _: 2
1150
+ }, 1040, ["aria-label", "onClick"]);
1151
+ }), 128))], 16)
1152
+ ]),
1153
+ _: 1
1154
+ }, 16);
1155
+ };
1156
+ }
1157
+ });
1158
+ //#endregion
1159
+ //#region src/Lightbox.vue
1160
+ var Lightbox_default = /* @__PURE__ */ defineComponent({
1161
+ name: "Lightbox",
1162
+ inheritAttrs: false,
1163
+ __name: "Lightbox",
1164
+ props: {
1165
+ opened: { type: Boolean },
1166
+ slides: {},
1167
+ currentIndex: {},
1168
+ labels: {},
1169
+ withZoom: {
1170
+ type: Boolean,
1171
+ default: void 0
1172
+ },
1173
+ withThumbnails: {
1174
+ type: Boolean,
1175
+ default: void 0
1176
+ },
1177
+ withFullscreen: {
1178
+ type: Boolean,
1179
+ default: void 0
1180
+ },
1181
+ withDownload: {
1182
+ type: Boolean,
1183
+ default: void 0
1184
+ },
1185
+ loop: {
1186
+ type: Boolean,
1187
+ default: void 0
1188
+ },
1189
+ closeOnClickOutside: {
1190
+ type: Boolean,
1191
+ default: void 0
1192
+ },
1193
+ closeOnSwipeDown: {
1194
+ type: Boolean,
1195
+ default: void 0
1196
+ },
1197
+ withKeyboardEvents: {
1198
+ type: Boolean,
1199
+ default: void 0
1200
+ },
1201
+ returnFocus: {
1202
+ type: Boolean,
1203
+ default: void 0
1204
+ },
1205
+ withInitialFocusPlaceholder: {
1206
+ type: Boolean,
1207
+ default: void 0
1208
+ },
1209
+ withinPortal: {
1210
+ type: Boolean,
1211
+ default: void 0
1212
+ },
1213
+ zIndex: {},
1214
+ transitionDuration: {},
1215
+ transitionProps: {},
1216
+ emblaOptions: {},
1217
+ zoomMaxScale: {},
1218
+ withSlideTransition: {
1219
+ type: Boolean,
1220
+ default: void 0
1221
+ },
1222
+ toolbarItems: {},
1223
+ withNavigation: {
1224
+ type: Boolean,
1225
+ default: true
1226
+ },
1227
+ classNames: {},
1228
+ styles: {},
1229
+ vars: {},
1230
+ unstyled: {
1231
+ type: Boolean,
1232
+ default: void 0
1233
+ }
1234
+ },
1235
+ emits: [
1236
+ "close",
1237
+ "update:opened",
1238
+ "indexChange",
1239
+ "update:currentIndex"
1240
+ ],
1241
+ setup(__props, { emit: __emit }) {
1242
+ const props = __props;
1243
+ const emit = __emit;
1244
+ const slots = useSlots();
1245
+ const attrs = useAttrs();
1246
+ return (_ctx, _cache) => {
1247
+ return openBlock(), createBlock(unref(LightboxRoot_default), mergeProps({
1248
+ ...unref(attrs),
1249
+ ...props
1250
+ }, {
1251
+ onClose: _cache[0] || (_cache[0] = ($event) => emit("close")),
1252
+ "onUpdate:opened": _cache[1] || (_cache[1] = ($event) => emit("update:opened", $event)),
1253
+ onIndexChange: _cache[2] || (_cache[2] = ($event) => emit("indexChange", $event)),
1254
+ "onUpdate:currentIndex": _cache[3] || (_cache[3] = ($event) => emit("update:currentIndex", $event))
1255
+ }), {
1256
+ default: withCtx(() => [renderSlot(_ctx.$slots, "default", {}, () => [
1257
+ createVNode(unref(LightboxToolbar_default), { "toolbar-items": props.toolbarItems }, null, 8, ["toolbar-items"]),
1258
+ createVNode(unref(LightboxSlides_default), null, {
1259
+ default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(props.slides, (slide, index) => {
1260
+ return openBlock(), createBlock(unref(LightboxSlide_default), {
1261
+ key: index,
1262
+ slide,
1263
+ index
1264
+ }, createSlots({ _: 2 }, [unref(slots).slide ? {
1265
+ name: "default",
1266
+ fn: withCtx((payload) => [renderSlot(_ctx.$slots, "slide", mergeProps({ ref_for: true }, {
1267
+ ...payload,
1268
+ index
1269
+ }))]),
1270
+ key: "0"
1271
+ } : void 0]), 1032, ["slide", "index"]);
1272
+ }), 128))]),
1273
+ _: 3
1274
+ }),
1275
+ props.withNavigation ? (openBlock(), createBlock(unref(LightboxNavigation_default), { key: 0 })) : createCommentVNode("", true),
1276
+ createVNode(unref(LightboxCaption_default)),
1277
+ createVNode(unref(LightboxThumbnails_default))
1278
+ ])]),
1279
+ _: 3
1280
+ }, 16);
1281
+ };
1282
+ }
1283
+ });
1284
+ //#endregion
1285
+ //#region src/lightbox.store.ts
1286
+ var clampIndex = (index, count) => Math.max(0, Math.min(Number.isFinite(index) ? Math.round(index) : 0, Math.max(0, count - 1)));
1287
+ var createLightboxStore = () => {
1288
+ const state = shallowReactive({
1289
+ opened: false,
1290
+ slides: [],
1291
+ currentIndex: 0,
1292
+ loop: false
1293
+ });
1294
+ return {
1295
+ state,
1296
+ getState: () => state,
1297
+ setState: (next) => Object.assign(state, next)
1298
+ };
1299
+ };
1300
+ var useLightboxStore = (store) => store.state;
1301
+ function updateLightboxStateAction(update, store) {
1302
+ const state = store.getState();
1303
+ store.setState({
1304
+ ...state,
1305
+ ...update(state)
1306
+ });
1307
+ }
1308
+ var openLightboxAction = (payload, store) => updateLightboxStateAction(() => ({
1309
+ opened: true,
1310
+ slides: payload.slides,
1311
+ currentIndex: clampIndex(payload.startIndex ?? 0, payload.slides.length)
1312
+ }), store);
1313
+ var closeLightboxAction = (store) => updateLightboxStateAction(() => ({ opened: false }), store);
1314
+ var nextLightboxAction = (store) => {
1315
+ updateLightboxStateAction((state) => {
1316
+ const next = state.currentIndex + 1;
1317
+ return { currentIndex: next < state.slides.length ? next : state.loop && state.slides.length ? 0 : state.currentIndex };
1318
+ }, store);
1319
+ };
1320
+ var prevLightboxAction = (store) => {
1321
+ updateLightboxStateAction((state) => {
1322
+ const prev = state.currentIndex - 1;
1323
+ return { currentIndex: prev >= 0 ? prev : state.loop && state.slides.length ? state.slides.length - 1 : 0 };
1324
+ }, store);
1325
+ };
1326
+ var setLightboxIndexAction = (index, store) => {
1327
+ updateLightboxStateAction((state) => ({ currentIndex: clampIndex(index, state.slides.length) }), store);
1328
+ };
1329
+ var lightboxActions = {
1330
+ open: openLightboxAction,
1331
+ close: closeLightboxAction,
1332
+ next: nextLightboxAction,
1333
+ prev: prevLightboxAction,
1334
+ setIndex: setLightboxIndexAction,
1335
+ updateState: updateLightboxStateAction
1336
+ };
1337
+ function createLightbox() {
1338
+ const store = createLightboxStore();
1339
+ return [store, {
1340
+ open: (payload) => openLightboxAction(payload, store),
1341
+ close: () => closeLightboxAction(store),
1342
+ next: () => nextLightboxAction(store),
1343
+ prev: () => prevLightboxAction(store),
1344
+ setIndex: (index) => setLightboxIndexAction(index, store)
1345
+ }];
1346
+ }
1347
+ var [lightboxStore, lightbox] = createLightbox();
1348
+ //#endregion
1349
+ //#region src/LightboxCloseButton/LightboxCloseButton.vue
1350
+ var LightboxCloseButton_default = /* @__PURE__ */ defineComponent({
1351
+ name: "LightboxCloseButton",
1352
+ inheritAttrs: false,
1353
+ __name: "LightboxCloseButton",
1354
+ setup(__props) {
1355
+ const attrs = useAttrs();
1356
+ const ctx = useLightboxContext();
1357
+ return (_ctx, _cache) => {
1358
+ return openBlock(), createBlock(unref(UnstyledButton), mergeProps({
1359
+ ...unref(attrs),
1360
+ ...unref(ctx).getStyles("closeButton")
1361
+ }, {
1362
+ "aria-label": unref(ctx).labels.closeLabel,
1363
+ onClick: unref(ctx).close
1364
+ }), {
1365
+ default: withCtx(() => [renderSlot(_ctx.$slots, "default", {}, () => [_cache[0] || (_cache[0] = createTextVNode("×", -1))])]),
1366
+ _: 3
1367
+ }, 16, ["aria-label", "onClick"]);
1368
+ };
1369
+ }
1370
+ });
1371
+ //#endregion
1372
+ //#region src/LightboxProvider/LightboxProvider.vue
1373
+ var LightboxProvider_default = /* @__PURE__ */ defineComponent({
1374
+ name: "LightboxProvider",
1375
+ __name: "LightboxProvider",
1376
+ props: {
1377
+ store: { default: () => lightboxStore },
1378
+ loop: {
1379
+ type: Boolean,
1380
+ default: void 0
1381
+ }
1382
+ },
1383
+ setup(__props) {
1384
+ const props = __props;
1385
+ const attrs = useAttrs();
1386
+ const state = useLightboxStore(props.store);
1387
+ watch(() => props.loop, (loop) => props.store.setState({
1388
+ ...props.store.getState(),
1389
+ loop: Boolean(loop)
1390
+ }), { immediate: true });
1391
+ return (_ctx, _cache) => {
1392
+ return openBlock(), createBlock(Lightbox_default, mergeProps({
1393
+ ...unref(attrs),
1394
+ loop: props.loop
1395
+ }, {
1396
+ opened: unref(state).opened,
1397
+ slides: unref(state).slides,
1398
+ "current-index": unref(state).currentIndex,
1399
+ onClose: _cache[0] || (_cache[0] = ($event) => unref(lightboxActions).close(props.store)),
1400
+ onIndexChange: _cache[1] || (_cache[1] = ($event) => unref(lightboxActions).setIndex($event, props.store))
1401
+ }), null, 16, [
1402
+ "opened",
1403
+ "slides",
1404
+ "current-index"
1405
+ ]);
1406
+ };
1407
+ }
1408
+ });
1409
+ //#endregion
1410
+ //#region src/Lightbox.ts
1411
+ var Lightbox = factory(Lightbox_default, {
1412
+ classes: Lightbox_module_default,
1413
+ Root: LightboxRoot_default,
1414
+ Toolbar: LightboxToolbar_default,
1415
+ Slides: LightboxSlides_default,
1416
+ Slide: LightboxSlide_default,
1417
+ Thumbnails: LightboxThumbnails_default,
1418
+ Navigation: LightboxNavigation_default,
1419
+ Caption: LightboxCaption_default,
1420
+ CloseButton: LightboxCloseButton_default,
1421
+ Provider: LightboxProvider_default,
1422
+ open: lightbox.open,
1423
+ close: lightbox.close,
1424
+ next: lightbox.next,
1425
+ prev: lightbox.prev,
1426
+ setIndex: lightbox.setIndex
1427
+ });
1428
+ //#endregion
1429
+ export { DEFAULT_LABELS, Lightbox, LightboxCaption_default as LightboxCaption, LightboxCloseButton_default as LightboxCloseButton, LightboxNavigation_default as LightboxNavigation, LightboxProvider_default as LightboxProviderComponent, LightboxRoot_default as LightboxRoot, LightboxSlide_default as LightboxSlide, LightboxSlides_default as LightboxSlides, LightboxThumbnails_default as LightboxThumbnails, LightboxToolbar_default as LightboxToolbar, closeLightboxAction, createCloseToolbarItem, createDownloadToolbarItem, createFullscreenToolbarItem, createLightbox, createLightboxStore, createThumbnailsToolbarItem, lightbox, lightboxActions, lightboxRootDefaultProps, lightboxStore, nextLightboxAction, openLightboxAction, prevLightboxAction, setLightboxIndexAction, updateLightboxStateAction, useLightboxStore };
1430
+
1431
+ //# sourceMappingURL=index.mjs.map