@mantine/lightbox 9.5.2

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 (116) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +21 -0
  3. package/cjs/Lightbox.cjs +56 -0
  4. package/cjs/Lightbox.cjs.map +1 -0
  5. package/cjs/Lightbox.module.cjs +30 -0
  6. package/cjs/Lightbox.module.cjs.map +1 -0
  7. package/cjs/LightboxCaption/LightboxCaption.cjs +30 -0
  8. package/cjs/LightboxCaption/LightboxCaption.cjs.map +1 -0
  9. package/cjs/LightboxCloseButton/LightboxCloseButton.cjs +32 -0
  10. package/cjs/LightboxCloseButton/LightboxCloseButton.cjs.map +1 -0
  11. package/cjs/LightboxNavigation/LightboxNavigation.cjs +105 -0
  12. package/cjs/LightboxNavigation/LightboxNavigation.cjs.map +1 -0
  13. package/cjs/LightboxProvider/LightboxProvider.cjs +29 -0
  14. package/cjs/LightboxProvider/LightboxProvider.cjs.map +1 -0
  15. package/cjs/LightboxRoot/LightboxRoot.cjs +407 -0
  16. package/cjs/LightboxRoot/LightboxRoot.cjs.map +1 -0
  17. package/cjs/LightboxSlide/ImageSlide.cjs +27 -0
  18. package/cjs/LightboxSlide/ImageSlide.cjs.map +1 -0
  19. package/cjs/LightboxSlide/LightboxSlide.cjs +55 -0
  20. package/cjs/LightboxSlide/LightboxSlide.cjs.map +1 -0
  21. package/cjs/LightboxSlide/VideoSlide.cjs +42 -0
  22. package/cjs/LightboxSlide/VideoSlide.cjs.map +1 -0
  23. package/cjs/LightboxSlides/LightboxSlides.cjs +56 -0
  24. package/cjs/LightboxSlides/LightboxSlides.cjs.map +1 -0
  25. package/cjs/LightboxThumbnails/LightboxThumbnails.cjs +74 -0
  26. package/cjs/LightboxThumbnails/LightboxThumbnails.cjs.map +1 -0
  27. package/cjs/LightboxToolbar/LightboxToolbar.cjs +80 -0
  28. package/cjs/LightboxToolbar/LightboxToolbar.cjs.map +1 -0
  29. package/cjs/LightboxToolbar/toolbar-items.cjs +156 -0
  30. package/cjs/LightboxToolbar/toolbar-items.cjs.map +1 -0
  31. package/cjs/_virtual/_rolldown/runtime.cjs +24 -0
  32. package/cjs/default-labels.cjs +20 -0
  33. package/cjs/default-labels.cjs.map +1 -0
  34. package/cjs/hooks/use-lightbox-keyboard.cjs +107 -0
  35. package/cjs/hooks/use-lightbox-keyboard.cjs.map +1 -0
  36. package/cjs/hooks/use-lightbox-lock-scroll.cjs +30 -0
  37. package/cjs/hooks/use-lightbox-lock-scroll.cjs.map +1 -0
  38. package/cjs/hooks/use-lightbox-swipe-close.cjs +38 -0
  39. package/cjs/hooks/use-lightbox-swipe-close.cjs.map +1 -0
  40. package/cjs/hooks/use-lightbox-zoom.cjs +257 -0
  41. package/cjs/hooks/use-lightbox-zoom.cjs.map +1 -0
  42. package/cjs/index.cjs +34 -0
  43. package/cjs/lightbox.context.cjs +8 -0
  44. package/cjs/lightbox.context.cjs.map +1 -0
  45. package/cjs/lightbox.store.cjs +90 -0
  46. package/cjs/lightbox.store.cjs.map +1 -0
  47. package/esm/Lightbox.mjs +56 -0
  48. package/esm/Lightbox.mjs.map +1 -0
  49. package/esm/Lightbox.module.mjs +30 -0
  50. package/esm/Lightbox.module.mjs.map +1 -0
  51. package/esm/LightboxCaption/LightboxCaption.mjs +30 -0
  52. package/esm/LightboxCaption/LightboxCaption.mjs.map +1 -0
  53. package/esm/LightboxCloseButton/LightboxCloseButton.mjs +32 -0
  54. package/esm/LightboxCloseButton/LightboxCloseButton.mjs.map +1 -0
  55. package/esm/LightboxNavigation/LightboxNavigation.mjs +105 -0
  56. package/esm/LightboxNavigation/LightboxNavigation.mjs.map +1 -0
  57. package/esm/LightboxProvider/LightboxProvider.mjs +29 -0
  58. package/esm/LightboxProvider/LightboxProvider.mjs.map +1 -0
  59. package/esm/LightboxRoot/LightboxRoot.mjs +404 -0
  60. package/esm/LightboxRoot/LightboxRoot.mjs.map +1 -0
  61. package/esm/LightboxSlide/ImageSlide.mjs +27 -0
  62. package/esm/LightboxSlide/ImageSlide.mjs.map +1 -0
  63. package/esm/LightboxSlide/LightboxSlide.mjs +55 -0
  64. package/esm/LightboxSlide/LightboxSlide.mjs.map +1 -0
  65. package/esm/LightboxSlide/VideoSlide.mjs +42 -0
  66. package/esm/LightboxSlide/VideoSlide.mjs.map +1 -0
  67. package/esm/LightboxSlides/LightboxSlides.mjs +56 -0
  68. package/esm/LightboxSlides/LightboxSlides.mjs.map +1 -0
  69. package/esm/LightboxThumbnails/LightboxThumbnails.mjs +72 -0
  70. package/esm/LightboxThumbnails/LightboxThumbnails.mjs.map +1 -0
  71. package/esm/LightboxToolbar/LightboxToolbar.mjs +80 -0
  72. package/esm/LightboxToolbar/LightboxToolbar.mjs.map +1 -0
  73. package/esm/LightboxToolbar/toolbar-items.mjs +153 -0
  74. package/esm/LightboxToolbar/toolbar-items.mjs.map +1 -0
  75. package/esm/default-labels.mjs +20 -0
  76. package/esm/default-labels.mjs.map +1 -0
  77. package/esm/hooks/use-lightbox-keyboard.mjs +107 -0
  78. package/esm/hooks/use-lightbox-keyboard.mjs.map +1 -0
  79. package/esm/hooks/use-lightbox-lock-scroll.mjs +30 -0
  80. package/esm/hooks/use-lightbox-lock-scroll.mjs.map +1 -0
  81. package/esm/hooks/use-lightbox-swipe-close.mjs +38 -0
  82. package/esm/hooks/use-lightbox-swipe-close.mjs.map +1 -0
  83. package/esm/hooks/use-lightbox-zoom.mjs +257 -0
  84. package/esm/hooks/use-lightbox-zoom.mjs.map +1 -0
  85. package/esm/index.mjs +14 -0
  86. package/esm/lightbox.context.mjs +8 -0
  87. package/esm/lightbox.context.mjs.map +1 -0
  88. package/esm/lightbox.store.mjs +79 -0
  89. package/esm/lightbox.store.mjs.map +1 -0
  90. package/lib/Lightbox.d.ts +63 -0
  91. package/lib/LightboxCaption/LightboxCaption.d.ts +18 -0
  92. package/lib/LightboxCloseButton/LightboxCloseButton.d.ts +16 -0
  93. package/lib/LightboxNavigation/LightboxNavigation.d.ts +16 -0
  94. package/lib/LightboxProvider/LightboxProvider.d.ts +19 -0
  95. package/lib/LightboxRoot/LightboxRoot.d.ts +88 -0
  96. package/lib/LightboxSlide/ImageSlide.d.ts +12 -0
  97. package/lib/LightboxSlide/LightboxSlide.d.ts +25 -0
  98. package/lib/LightboxSlide/VideoSlide.d.ts +12 -0
  99. package/lib/LightboxSlides/LightboxSlides.d.ts +20 -0
  100. package/lib/LightboxThumbnails/LightboxThumbnails.d.ts +16 -0
  101. package/lib/LightboxToolbar/LightboxToolbar.d.ts +19 -0
  102. package/lib/LightboxToolbar/toolbar-items.d.ts +5 -0
  103. package/lib/default-labels.d.ts +2 -0
  104. package/lib/hooks/use-lightbox-keyboard.d.ts +15 -0
  105. package/lib/hooks/use-lightbox-lock-scroll.d.ts +7 -0
  106. package/lib/hooks/use-lightbox-swipe-close.d.ts +10 -0
  107. package/lib/hooks/use-lightbox-zoom.d.ts +33 -0
  108. package/lib/index.d.mts +67 -0
  109. package/lib/index.d.ts +67 -0
  110. package/lib/lightbox.context.d.ts +36 -0
  111. package/lib/lightbox.store.d.ts +66 -0
  112. package/lib/lightbox.types.d.ts +131 -0
  113. package/lib/test-utils.d.ts +7 -0
  114. package/package.json +63 -0
  115. package/styles.css +262 -0
  116. package/styles.layer.css +263 -0
@@ -0,0 +1,107 @@
1
+ "use client";
2
+ import { useEffect } from "react";
3
+ //#region packages/@mantine/lightbox/src/hooks/use-lightbox-keyboard.ts
4
+ /** Elements that consume typing wherever they are, including the lightbox chrome */
5
+ const TEXT_ENTRY_SELECTOR = "input, textarea, select, video, audio, [contenteditable]:not([contenteditable=\"false\"])";
6
+ /**
7
+ * Elements that consume arrow keys and letter shortcuts only inside slide content – the
8
+ * lightbox toolbar and navigation are buttons too, and keyboard shortcuts have to keep
9
+ * working while one of them has focus.
10
+ */
11
+ const SLIDE_WIDGET_SELECTOR = [
12
+ "button",
13
+ "a[href]",
14
+ "[role=\"button\"]",
15
+ "[role=\"link\"]",
16
+ "[role=\"checkbox\"]",
17
+ "[role=\"radio\"]",
18
+ "[role=\"menuitem\"]",
19
+ "[role=\"option\"]",
20
+ "[role=\"slider\"]",
21
+ "[role=\"spinbutton\"]",
22
+ "[role=\"switch\"]",
23
+ "[role=\"tab\"]",
24
+ "[role=\"textbox\"]",
25
+ "[role=\"combobox\"]",
26
+ "[role=\"listbox\"]",
27
+ "[role=\"menu\"]",
28
+ "[role=\"menubar\"]",
29
+ "[role=\"tablist\"]",
30
+ "[role=\"tree\"]",
31
+ "[role=\"treeitem\"]",
32
+ "[role=\"grid\"]",
33
+ "[role=\"gridcell\"]"
34
+ ].join(", ");
35
+ const ZOOM_PAN_STEP = 50;
36
+ function useLightboxKeyboard({ opened, enabled, dir, onClose, onNext, onPrev, onToggleFullscreen, onToggleThumbnails, onToggleZoom, onZoomPan }) {
37
+ useEffect(() => {
38
+ if (!opened) return;
39
+ const onLeadingArrow = dir === "rtl" ? onNext : onPrev;
40
+ const onTrailingArrow = dir === "rtl" ? onPrev : onNext;
41
+ const handler = (event) => {
42
+ if (event.defaultPrevented) return;
43
+ if (event.key === "Escape") {
44
+ event.preventDefault();
45
+ onClose();
46
+ return;
47
+ }
48
+ if (!enabled || event.ctrlKey || event.metaKey || event.altKey) return;
49
+ if (event.target instanceof Element) {
50
+ if (event.target.closest(TEXT_ENTRY_SELECTOR)) return;
51
+ if (event.target.closest("[data-lightbox-slide]") && event.target.closest(SLIDE_WIDGET_SELECTOR)) return;
52
+ }
53
+ switch (event.key) {
54
+ case "ArrowLeft":
55
+ event.preventDefault();
56
+ if (!onZoomPan?.(ZOOM_PAN_STEP, 0)) onLeadingArrow();
57
+ break;
58
+ case "ArrowRight":
59
+ event.preventDefault();
60
+ if (!onZoomPan?.(-50, 0)) onTrailingArrow();
61
+ break;
62
+ case "ArrowUp":
63
+ if (onZoomPan?.(0, ZOOM_PAN_STEP)) event.preventDefault();
64
+ break;
65
+ case "ArrowDown":
66
+ if (onZoomPan?.(0, -50)) event.preventDefault();
67
+ break;
68
+ case "f":
69
+ case "F":
70
+ if (onToggleFullscreen) {
71
+ event.preventDefault();
72
+ onToggleFullscreen();
73
+ }
74
+ break;
75
+ case "t":
76
+ case "T":
77
+ if (onToggleThumbnails) {
78
+ event.preventDefault();
79
+ onToggleThumbnails();
80
+ }
81
+ break;
82
+ case "z":
83
+ case "Z": if (onToggleZoom) {
84
+ event.preventDefault();
85
+ onToggleZoom();
86
+ }
87
+ }
88
+ };
89
+ document.addEventListener("keydown", handler);
90
+ return () => document.removeEventListener("keydown", handler);
91
+ }, [
92
+ opened,
93
+ enabled,
94
+ dir,
95
+ onClose,
96
+ onNext,
97
+ onPrev,
98
+ onToggleFullscreen,
99
+ onToggleThumbnails,
100
+ onToggleZoom,
101
+ onZoomPan
102
+ ]);
103
+ }
104
+ //#endregion
105
+ export { useLightboxKeyboard };
106
+
107
+ //# sourceMappingURL=use-lightbox-keyboard.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-lightbox-keyboard.mjs","names":[],"sources":["../../src/hooks/use-lightbox-keyboard.ts"],"sourcesContent":["import { useEffect } from 'react';\n\ninterface UseLightboxKeyboardInput {\n opened: boolean;\n enabled: boolean;\n /** Text direction of the lightbox, arrow keys follow it the same way the navigation buttons do */\n dir: 'ltr' | 'rtl';\n onClose: () => void;\n onNext: () => void;\n onPrev: () => void;\n onToggleFullscreen?: () => void;\n onToggleThumbnails?: () => void;\n onToggleZoom?: () => void;\n onZoomPan?: (deltaX: number, deltaY: number) => boolean;\n}\n\n/** Elements that consume typing wherever they are, including the lightbox chrome */\nconst TEXT_ENTRY_SELECTOR =\n 'input, textarea, select, video, audio, [contenteditable]:not([contenteditable=\"false\"])';\n\n/**\n * Elements that consume arrow keys and letter shortcuts only inside slide content – the\n * lightbox toolbar and navigation are buttons too, and keyboard shortcuts have to keep\n * working while one of them has focus.\n */\nconst SLIDE_WIDGET_SELECTOR = [\n 'button',\n 'a[href]',\n '[role=\"button\"]',\n '[role=\"link\"]',\n '[role=\"checkbox\"]',\n '[role=\"radio\"]',\n '[role=\"menuitem\"]',\n '[role=\"option\"]',\n '[role=\"slider\"]',\n '[role=\"spinbutton\"]',\n '[role=\"switch\"]',\n '[role=\"tab\"]',\n '[role=\"textbox\"]',\n '[role=\"combobox\"]',\n '[role=\"listbox\"]',\n '[role=\"menu\"]',\n '[role=\"menubar\"]',\n '[role=\"tablist\"]',\n '[role=\"tree\"]',\n '[role=\"treeitem\"]',\n '[role=\"grid\"]',\n '[role=\"gridcell\"]',\n].join(', ');\n\nconst ZOOM_PAN_STEP = 50;\n\nexport function useLightboxKeyboard({\n opened,\n enabled,\n dir,\n onClose,\n onNext,\n onPrev,\n onToggleFullscreen,\n onToggleThumbnails,\n onToggleZoom,\n onZoomPan,\n}: UseLightboxKeyboardInput) {\n useEffect(() => {\n if (!opened) {\n return undefined;\n }\n\n // Slides advance along the reading direction, so in RTL the next slide is the one to the\n // left – the arrow keys follow the same rule as the navigation chevrons, which are\n // already mirrored. Zoom panning stays physical: an arrow always moves the image the way\n // it points.\n const onLeadingArrow = dir === 'rtl' ? onNext : onPrev;\n const onTrailingArrow = dir === 'rtl' ? onPrev : onNext;\n\n const handler = (event: KeyboardEvent) => {\n if (event.defaultPrevented) {\n return;\n }\n\n if (event.key === 'Escape') {\n event.preventDefault();\n onClose();\n return;\n }\n\n if (!enabled || event.ctrlKey || event.metaKey || event.altKey) {\n return;\n }\n\n if (event.target instanceof Element) {\n if (event.target.closest(TEXT_ENTRY_SELECTOR)) {\n return;\n }\n\n if (\n event.target.closest('[data-lightbox-slide]') &&\n event.target.closest(SLIDE_WIDGET_SELECTOR)\n ) {\n return;\n }\n }\n\n switch (event.key) {\n case 'ArrowLeft':\n event.preventDefault();\n if (!onZoomPan?.(ZOOM_PAN_STEP, 0)) {\n onLeadingArrow();\n }\n break;\n case 'ArrowRight':\n event.preventDefault();\n if (!onZoomPan?.(-ZOOM_PAN_STEP, 0)) {\n onTrailingArrow();\n }\n break;\n case 'ArrowUp':\n if (onZoomPan?.(0, ZOOM_PAN_STEP)) {\n event.preventDefault();\n }\n break;\n case 'ArrowDown':\n if (onZoomPan?.(0, -ZOOM_PAN_STEP)) {\n event.preventDefault();\n }\n break;\n case 'f':\n case 'F':\n if (onToggleFullscreen) {\n event.preventDefault();\n onToggleFullscreen();\n }\n break;\n case 't':\n case 'T':\n if (onToggleThumbnails) {\n event.preventDefault();\n onToggleThumbnails();\n }\n break;\n case 'z':\n case 'Z':\n if (onToggleZoom) {\n event.preventDefault();\n onToggleZoom();\n }\n break;\n default:\n break;\n }\n };\n\n document.addEventListener('keydown', handler);\n return () => document.removeEventListener('keydown', handler);\n }, [\n opened,\n enabled,\n dir,\n onClose,\n onNext,\n onPrev,\n onToggleFullscreen,\n onToggleThumbnails,\n onToggleZoom,\n onZoomPan,\n ]);\n}\n"],"mappings":";;;;AAiBA,MAAM,sBACJ;;;;;;AAOF,MAAM,wBAAwB;CAC5B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC,CAAC,KAAK,IAAI;AAEX,MAAM,gBAAgB;AAEtB,SAAgB,oBAAoB,EAClC,QACA,SACA,KACA,SACA,QACA,QACA,oBACA,oBACA,cACA,aAC2B;CAC3B,gBAAgB;EACd,IAAI,CAAC,QACH;EAOF,MAAM,iBAAiB,QAAQ,QAAQ,SAAS;EAChD,MAAM,kBAAkB,QAAQ,QAAQ,SAAS;EAEjD,MAAM,WAAW,UAAyB;GACxC,IAAI,MAAM,kBACR;GAGF,IAAI,MAAM,QAAQ,UAAU;IAC1B,MAAM,eAAe;IACrB,QAAQ;IACR;GACF;GAEA,IAAI,CAAC,WAAW,MAAM,WAAW,MAAM,WAAW,MAAM,QACtD;GAGF,IAAI,MAAM,kBAAkB,SAAS;IACnC,IAAI,MAAM,OAAO,QAAQ,mBAAmB,GAC1C;IAGF,IACE,MAAM,OAAO,QAAQ,uBAAuB,KAC5C,MAAM,OAAO,QAAQ,qBAAqB,GAE1C;GAEJ;GAEA,QAAQ,MAAM,KAAd;IACE,KAAK;KACH,MAAM,eAAe;KACrB,IAAI,CAAC,YAAY,eAAe,CAAC,GAC/B,eAAe;KAEjB;IACF,KAAK;KACH,MAAM,eAAe;KACrB,IAAI,CAAC,YAAY,KAAgB,CAAC,GAChC,gBAAgB;KAElB;IACF,KAAK;KACH,IAAI,YAAY,GAAG,aAAa,GAC9B,MAAM,eAAe;KAEvB;IACF,KAAK;KACH,IAAI,YAAY,GAAG,GAAc,GAC/B,MAAM,eAAe;KAEvB;IACF,KAAK;IACL,KAAK;KACH,IAAI,oBAAoB;MACtB,MAAM,eAAe;MACrB,mBAAmB;KACrB;KACA;IACF,KAAK;IACL,KAAK;KACH,IAAI,oBAAoB;MACtB,MAAM,eAAe;MACrB,mBAAmB;KACrB;KACA;IACF,KAAK;IACL,KAAK,KACH,IAAI,cAAc;KAChB,MAAM,eAAe;KACrB,aAAa;IACf;GAIJ;EACF;EAEA,SAAS,iBAAiB,WAAW,OAAO;EAC5C,aAAa,SAAS,oBAAoB,WAAW,OAAO;CAC9D,GAAG;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;AACH"}
@@ -0,0 +1,30 @@
1
+ "use client";
2
+ import { useMantineTheme } from "@mantine/core";
3
+ import { useEffect, useRef, useState } from "react";
4
+ import { useReducedMotion } from "@mantine/hooks";
5
+ //#region packages/@mantine/lightbox/src/hooks/use-lightbox-lock-scroll.ts
6
+ function useLightboxLockScroll({ opened, exitDuration, exitDelay }) {
7
+ const [unlocked, setUnlocked] = useState(!opened);
8
+ const [prevOpened, setPrevOpened] = useState(opened);
9
+ const timeout = useRef(-1);
10
+ const theme = useMantineTheme();
11
+ const shouldReduceMotion = useReducedMotion();
12
+ const unlockDelay = exitDelay + ((theme.respectReducedMotion ? shouldReduceMotion : false) ? 0 : exitDuration);
13
+ if (opened !== prevOpened) {
14
+ setPrevOpened(opened);
15
+ if (opened) setUnlocked(false);
16
+ }
17
+ useEffect(() => {
18
+ if (opened || unlockDelay === 0) {
19
+ window.clearTimeout(timeout.current);
20
+ return;
21
+ }
22
+ timeout.current = window.setTimeout(() => setUnlocked(true), unlockDelay);
23
+ return () => window.clearTimeout(timeout.current);
24
+ }, [opened, unlockDelay]);
25
+ return opened || !unlocked && unlockDelay > 0;
26
+ }
27
+ //#endregion
28
+ export { useLightboxLockScroll };
29
+
30
+ //# sourceMappingURL=use-lightbox-lock-scroll.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-lightbox-lock-scroll.mjs","names":[],"sources":["../../src/hooks/use-lightbox-lock-scroll.ts"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport { useMantineTheme } from '@mantine/core';\nimport { useReducedMotion } from '@mantine/hooks';\n\ninterface UseLightboxLockScrollInput {\n opened: boolean;\n exitDuration: number;\n exitDelay: number;\n}\n\nexport function useLightboxLockScroll({\n opened,\n exitDuration,\n exitDelay,\n}: UseLightboxLockScrollInput) {\n const [unlocked, setUnlocked] = useState(!opened);\n const [prevOpened, setPrevOpened] = useState(opened);\n const timeout = useRef<number>(-1);\n const theme = useMantineTheme();\n const shouldReduceMotion = useReducedMotion();\n const reduceMotion = theme.respectReducedMotion ? shouldReduceMotion : false;\n const unlockDelay = exitDelay + (reduceMotion ? 0 : exitDuration);\n\n // Opening arms the lock in the same render rather than one commit later, so the page\n // cannot scroll in the frame between mounting the lightbox and the effect running.\n if (opened !== prevOpened) {\n setPrevOpened(opened);\n if (opened) {\n setUnlocked(false);\n }\n }\n\n useEffect(() => {\n if (opened || unlockDelay === 0) {\n window.clearTimeout(timeout.current);\n return undefined;\n }\n\n // The lock is released only after the exit transition has played out, otherwise the\n // page jumps by the scrollbar width while the lightbox is still fading out.\n timeout.current = window.setTimeout(() => setUnlocked(true), unlockDelay);\n return () => window.clearTimeout(timeout.current);\n }, [opened, unlockDelay]);\n\n return opened || (!unlocked && unlockDelay > 0);\n}\n"],"mappings":";;;;;AAUA,SAAgB,sBAAsB,EACpC,QACA,cACA,aAC6B;CAC7B,MAAM,CAAC,UAAU,eAAe,SAAS,CAAC,MAAM;CAChD,MAAM,CAAC,YAAY,iBAAiB,SAAS,MAAM;CACnD,MAAM,UAAU,OAAe,EAAE;CACjC,MAAM,QAAQ,gBAAgB;CAC9B,MAAM,qBAAqB,iBAAiB;CAE5C,MAAM,cAAc,cADC,MAAM,uBAAuB,qBAAqB,SACvB,IAAI;CAIpD,IAAI,WAAW,YAAY;EACzB,cAAc,MAAM;EACpB,IAAI,QACF,YAAY,KAAK;CAErB;CAEA,gBAAgB;EACd,IAAI,UAAU,gBAAgB,GAAG;GAC/B,OAAO,aAAa,QAAQ,OAAO;GACnC;EACF;EAIA,QAAQ,UAAU,OAAO,iBAAiB,YAAY,IAAI,GAAG,WAAW;EACxE,aAAa,OAAO,aAAa,QAAQ,OAAO;CAClD,GAAG,CAAC,QAAQ,WAAW,CAAC;CAExB,OAAO,UAAW,CAAC,YAAY,cAAc;AAC/C"}
@@ -0,0 +1,38 @@
1
+ "use client";
2
+ import { useCallback, useRef } from "react";
3
+ //#region packages/@mantine/lightbox/src/hooks/use-lightbox-swipe-close.ts
4
+ const SWIPE_THRESHOLD = 100;
5
+ function useLightboxSwipeClose({ enabled, onClose, isZoomed }) {
6
+ const touchStart = useRef(null);
7
+ return {
8
+ handleTouchStart: useCallback((event) => {
9
+ if (!enabled || isZoomed) return;
10
+ if (event.touches.length !== 1) {
11
+ touchStart.current = null;
12
+ return;
13
+ }
14
+ touchStart.current = {
15
+ x: event.touches[0].clientX,
16
+ y: event.touches[0].clientY
17
+ };
18
+ }, [enabled, isZoomed]),
19
+ handleTouchEnd: useCallback((event) => {
20
+ if (!enabled || isZoomed || !touchStart.current || event.changedTouches.length !== 1) {
21
+ touchStart.current = null;
22
+ return;
23
+ }
24
+ const deltaY = event.changedTouches[0].clientY - touchStart.current.y;
25
+ const deltaX = Math.abs(event.changedTouches[0].clientX - touchStart.current.x);
26
+ if (deltaY > SWIPE_THRESHOLD && deltaX < deltaY) onClose();
27
+ touchStart.current = null;
28
+ }, [
29
+ enabled,
30
+ isZoomed,
31
+ onClose
32
+ ])
33
+ };
34
+ }
35
+ //#endregion
36
+ export { useLightboxSwipeClose };
37
+
38
+ //# sourceMappingURL=use-lightbox-swipe-close.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-lightbox-swipe-close.mjs","names":[],"sources":["../../src/hooks/use-lightbox-swipe-close.ts"],"sourcesContent":["import { useCallback, useRef } from 'react';\n\ninterface UseLightboxSwipeCloseInput {\n enabled: boolean;\n onClose: () => void;\n isZoomed: boolean;\n}\n\nconst SWIPE_THRESHOLD = 100;\n\nexport function useLightboxSwipeClose({ enabled, onClose, isZoomed }: UseLightboxSwipeCloseInput) {\n const touchStart = useRef<{ x: number; y: number } | null>(null);\n\n const handleTouchStart = useCallback(\n (event: React.TouchEvent) => {\n if (!enabled || isZoomed) {\n return;\n }\n if (event.touches.length !== 1) {\n touchStart.current = null;\n return;\n }\n touchStart.current = {\n x: event.touches[0].clientX,\n y: event.touches[0].clientY,\n };\n },\n [enabled, isZoomed]\n );\n\n const handleTouchEnd = useCallback(\n (event: React.TouchEvent) => {\n if (!enabled || isZoomed || !touchStart.current || event.changedTouches.length !== 1) {\n touchStart.current = null;\n return;\n }\n\n const deltaY = event.changedTouches[0].clientY - touchStart.current.y;\n const deltaX = Math.abs(event.changedTouches[0].clientX - touchStart.current.x);\n\n if (deltaY > SWIPE_THRESHOLD && deltaX < deltaY) {\n onClose();\n }\n\n touchStart.current = null;\n },\n [enabled, isZoomed, onClose]\n );\n\n return { handleTouchStart, handleTouchEnd };\n}\n"],"mappings":";;;AAQA,MAAM,kBAAkB;AAExB,SAAgB,sBAAsB,EAAE,SAAS,SAAS,YAAwC;CAChG,MAAM,aAAa,OAAwC,IAAI;CAsC/D,OAAO;EAAE,kBApCgB,aACtB,UAA4B;GAC3B,IAAI,CAAC,WAAW,UACd;GAEF,IAAI,MAAM,QAAQ,WAAW,GAAG;IAC9B,WAAW,UAAU;IACrB;GACF;GACA,WAAW,UAAU;IACnB,GAAG,MAAM,QAAQ,EAAE,CAAC;IACpB,GAAG,MAAM,QAAQ,EAAE,CAAC;GACtB;EACF,GACA,CAAC,SAAS,QAAQ,CAsBI;EAAG,gBAnBJ,aACpB,UAA4B;GAC3B,IAAI,CAAC,WAAW,YAAY,CAAC,WAAW,WAAW,MAAM,eAAe,WAAW,GAAG;IACpF,WAAW,UAAU;IACrB;GACF;GAEA,MAAM,SAAS,MAAM,eAAe,EAAE,CAAC,UAAU,WAAW,QAAQ;GACpE,MAAM,SAAS,KAAK,IAAI,MAAM,eAAe,EAAE,CAAC,UAAU,WAAW,QAAQ,CAAC;GAE9E,IAAI,SAAS,mBAAmB,SAAS,QACvC,QAAQ;GAGV,WAAW,UAAU;EACvB,GACA;GAAC;GAAS;GAAU;EAAO,CAGW;CAAE;AAC5C"}
@@ -0,0 +1,257 @@
1
+ "use client";
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
+ //#region packages/@mantine/lightbox/src/hooks/use-lightbox-zoom.ts
4
+ const INITIAL_ZOOM_STATE = {
5
+ scale: 1,
6
+ translateX: 0,
7
+ translateY: 0,
8
+ isZoomed: false
9
+ };
10
+ function clampTranslate(state, image) {
11
+ if (!image) return state;
12
+ const maxX = image.offsetWidth * (state.scale - 1) / 2;
13
+ const maxY = image.offsetHeight * (state.scale - 1) / 2;
14
+ return {
15
+ ...state,
16
+ translateX: Math.max(-maxX, Math.min(maxX, state.translateX)),
17
+ translateY: Math.max(-maxY, Math.min(maxY, state.translateY))
18
+ };
19
+ }
20
+ function useLightboxZoom({ enabled, maxScale, currentIndex }) {
21
+ const [zoomState, setZoomState] = useState(INITIAL_ZOOM_STATE);
22
+ const [isDragging, setIsDragging] = useState(false);
23
+ const imageRef = useRef(null);
24
+ const isZoomedRef = useRef(false);
25
+ const didDrag = useRef(false);
26
+ const dragStart = useRef({
27
+ x: 0,
28
+ y: 0
29
+ });
30
+ const translateStart = useRef({
31
+ x: 0,
32
+ y: 0
33
+ });
34
+ const lastPinchDistance = useRef(null);
35
+ const lastPointerType = useRef("mouse");
36
+ const lastClickPointerType = useRef("mouse");
37
+ const [isAdjusting, setIsAdjusting] = useState(false);
38
+ const adjustTimeout = useRef(-1);
39
+ useEffect(() => {
40
+ isZoomedRef.current = zoomState.isZoomed;
41
+ }, [zoomState.isZoomed]);
42
+ const beginAdjust = useCallback(() => {
43
+ setIsAdjusting(true);
44
+ window.clearTimeout(adjustTimeout.current);
45
+ adjustTimeout.current = window.setTimeout(() => setIsAdjusting(false), 120);
46
+ }, []);
47
+ const beginAdjustRef = useRef(beginAdjust);
48
+ useEffect(() => {
49
+ beginAdjustRef.current = beginAdjust;
50
+ }, [beginAdjust]);
51
+ useEffect(() => () => window.clearTimeout(adjustTimeout.current), []);
52
+ const resetZoom = useCallback(() => {
53
+ setZoomState(INITIAL_ZOOM_STATE);
54
+ setIsDragging(false);
55
+ }, []);
56
+ const [prevIndex, setPrevIndex] = useState(currentIndex);
57
+ const [prevEnabled, setPrevEnabled] = useState(enabled);
58
+ if (currentIndex !== prevIndex || enabled !== prevEnabled) {
59
+ setPrevIndex(currentIndex);
60
+ setPrevEnabled(enabled);
61
+ if (currentIndex !== prevIndex || !enabled) {
62
+ setZoomState(INITIAL_ZOOM_STATE);
63
+ setIsDragging(false);
64
+ }
65
+ }
66
+ useEffect(() => {
67
+ if (!enabled) {
68
+ lastPinchDistance.current = null;
69
+ didDrag.current = false;
70
+ }
71
+ }, [enabled]);
72
+ const toggleZoom = useCallback(() => {
73
+ if (!enabled) return;
74
+ setZoomState((prev) => prev.isZoomed ? INITIAL_ZOOM_STATE : {
75
+ scale: Math.min(2, maxScale),
76
+ translateX: 0,
77
+ translateY: 0,
78
+ isZoomed: true
79
+ });
80
+ }, [enabled, maxScale]);
81
+ const zoomAtPoint = useCallback((deltaScale, clientX, clientY) => {
82
+ setZoomState((prev) => {
83
+ const nextScale = Math.max(1, Math.min(maxScale, prev.scale + deltaScale));
84
+ if (nextScale === 1) return INITIAL_ZOOM_STATE;
85
+ const image = imageRef.current;
86
+ let translateX = prev.translateX;
87
+ let translateY = prev.translateY;
88
+ if (image && clientX !== void 0 && clientY !== void 0) {
89
+ const rect = image.getBoundingClientRect();
90
+ const scaleRatio = nextScale / prev.scale;
91
+ const offsetX = clientX - (rect.left + rect.width / 2) + prev.translateX;
92
+ const offsetY = clientY - (rect.top + rect.height / 2) + prev.translateY;
93
+ translateX = offsetX * (1 - scaleRatio) + prev.translateX * scaleRatio;
94
+ translateY = offsetY * (1 - scaleRatio) + prev.translateY * scaleRatio;
95
+ }
96
+ return clampTranslate({
97
+ scale: nextScale,
98
+ translateX,
99
+ translateY,
100
+ isZoomed: true
101
+ }, image);
102
+ });
103
+ }, [maxScale]);
104
+ const panZoom = useCallback((deltaX, deltaY) => {
105
+ if (!isZoomedRef.current) return false;
106
+ setZoomState((prev) => clampTranslate({
107
+ ...prev,
108
+ translateX: prev.translateX + deltaX,
109
+ translateY: prev.translateY + deltaY
110
+ }, imageRef.current));
111
+ return true;
112
+ }, []);
113
+ const zoomAtPointRef = useRef(zoomAtPoint);
114
+ useEffect(() => {
115
+ zoomAtPointRef.current = zoomAtPoint;
116
+ }, [zoomAtPoint]);
117
+ const handleNativeWheel = useRef((event) => {
118
+ if (event.deltaY === 0) return;
119
+ event.preventDefault();
120
+ beginAdjustRef.current();
121
+ zoomAtPointRef.current(event.deltaY > 0 ? -.2 : .2, event.clientX, event.clientY);
122
+ });
123
+ const setImageRef = useCallback((node) => {
124
+ imageRef.current?.removeEventListener("wheel", handleNativeWheel.current);
125
+ imageRef.current = node;
126
+ node?.addEventListener("wheel", handleNativeWheel.current, { passive: false });
127
+ }, []);
128
+ const handlePointerDown = useCallback((event) => {
129
+ lastPointerType.current = event.pointerType;
130
+ if (!zoomState.isZoomed || !enabled) return;
131
+ setIsDragging(true);
132
+ didDrag.current = false;
133
+ dragStart.current = {
134
+ x: event.clientX,
135
+ y: event.clientY
136
+ };
137
+ translateStart.current = {
138
+ x: zoomState.translateX,
139
+ y: zoomState.translateY
140
+ };
141
+ const target = event.target;
142
+ if (typeof target?.setPointerCapture === "function") target.setPointerCapture(event.pointerId);
143
+ }, [
144
+ zoomState.isZoomed,
145
+ zoomState.translateX,
146
+ zoomState.translateY,
147
+ enabled
148
+ ]);
149
+ const handlePointerMove = useCallback((event) => {
150
+ if (!isDragging || !enabled) return;
151
+ const dx = event.clientX - dragStart.current.x;
152
+ const dy = event.clientY - dragStart.current.y;
153
+ if (Math.abs(dx) > 3 || Math.abs(dy) > 3) didDrag.current = true;
154
+ setZoomState((prev) => clampTranslate({
155
+ ...prev,
156
+ translateX: translateStart.current.x + dx,
157
+ translateY: translateStart.current.y + dy
158
+ }, imageRef.current));
159
+ }, [isDragging, enabled]);
160
+ const handlePointerUp = useCallback(() => {
161
+ setIsDragging(false);
162
+ }, []);
163
+ const handlePointerCancel = useCallback(() => {
164
+ setIsDragging(false);
165
+ didDrag.current = false;
166
+ lastPinchDistance.current = null;
167
+ }, []);
168
+ const handleTouchMove = useCallback((event) => {
169
+ if (!enabled || event.touches.length !== 2) {
170
+ lastPinchDistance.current = null;
171
+ return;
172
+ }
173
+ const touch1 = event.touches[0];
174
+ const touch2 = event.touches[1];
175
+ const distance = Math.hypot(touch1.clientX - touch2.clientX, touch1.clientY - touch2.clientY);
176
+ if (lastPinchDistance.current !== null) {
177
+ beginAdjust();
178
+ const delta = (distance - lastPinchDistance.current) * .01;
179
+ zoomAtPoint(delta, (touch1.clientX + touch2.clientX) / 2, (touch1.clientY + touch2.clientY) / 2);
180
+ }
181
+ lastPinchDistance.current = distance;
182
+ }, [
183
+ enabled,
184
+ zoomAtPoint,
185
+ beginAdjust
186
+ ]);
187
+ const handleTouchEnd = useCallback(() => {
188
+ lastPinchDistance.current = null;
189
+ }, []);
190
+ const handleClick = useCallback((event) => {
191
+ if (!enabled || event.detail > 1) return;
192
+ if (didDrag.current) {
193
+ didDrag.current = false;
194
+ return;
195
+ }
196
+ const pointerType = lastPointerType.current;
197
+ lastPointerType.current = "mouse";
198
+ lastClickPointerType.current = pointerType;
199
+ if (pointerType === "touch") return;
200
+ toggleZoom();
201
+ }, [enabled, toggleZoom]);
202
+ const handleDoubleClick = useCallback(() => {
203
+ if (!enabled || lastClickPointerType.current !== "touch") return;
204
+ toggleZoom();
205
+ }, [enabled, toggleZoom]);
206
+ const getImageProps = useCallback(() => ({
207
+ ref: setImageRef,
208
+ style: { transform: `scale(${zoomState.scale}) translate(${zoomState.translateX / zoomState.scale}px, ${zoomState.translateY / zoomState.scale}px)` },
209
+ "data-zoom-enabled": enabled || void 0,
210
+ "data-zoomed": zoomState.isZoomed || void 0,
211
+ "data-dragging": isDragging || isAdjusting || void 0,
212
+ onPointerDown: handlePointerDown,
213
+ onPointerMove: handlePointerMove,
214
+ onPointerUp: handlePointerUp,
215
+ onPointerCancel: handlePointerCancel,
216
+ onLostPointerCapture: handlePointerUp,
217
+ onClick: handleClick,
218
+ onDoubleClick: handleDoubleClick,
219
+ onTouchMove: handleTouchMove,
220
+ onTouchEnd: handleTouchEnd
221
+ }), [
222
+ zoomState,
223
+ isDragging,
224
+ isAdjusting,
225
+ enabled,
226
+ setImageRef,
227
+ handlePointerDown,
228
+ handlePointerMove,
229
+ handlePointerUp,
230
+ handlePointerCancel,
231
+ handleClick,
232
+ handleDoubleClick,
233
+ handleTouchMove,
234
+ handleTouchEnd
235
+ ]);
236
+ const publicZoomState = useMemo(() => ({
237
+ scale: zoomState.scale,
238
+ isZoomed: zoomState.isZoomed
239
+ }), [zoomState.scale, zoomState.isZoomed]);
240
+ return useMemo(() => ({
241
+ zoomState: publicZoomState,
242
+ toggleZoom,
243
+ resetZoom,
244
+ panZoom,
245
+ getImageProps
246
+ }), [
247
+ publicZoomState,
248
+ toggleZoom,
249
+ resetZoom,
250
+ panZoom,
251
+ getImageProps
252
+ ]);
253
+ }
254
+ //#endregion
255
+ export { useLightboxZoom };
256
+
257
+ //# sourceMappingURL=use-lightbox-zoom.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-lightbox-zoom.mjs","names":[],"sources":["../../src/hooks/use-lightbox-zoom.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\n\ninterface UseLightboxZoomInput {\n enabled: boolean;\n maxScale: number;\n currentIndex: number;\n}\n\ninterface ZoomState {\n scale: number;\n translateX: number;\n translateY: number;\n isZoomed: boolean;\n}\n\nconst INITIAL_ZOOM_STATE: ZoomState = {\n scale: 1,\n translateX: 0,\n translateY: 0,\n isZoomed: false,\n};\n\nfunction clampTranslate(state: ZoomState, image: HTMLImageElement | null): ZoomState {\n if (!image) {\n return state;\n }\n\n const maxX = (image.offsetWidth * (state.scale - 1)) / 2;\n const maxY = (image.offsetHeight * (state.scale - 1)) / 2;\n\n return {\n ...state,\n translateX: Math.max(-maxX, Math.min(maxX, state.translateX)),\n translateY: Math.max(-maxY, Math.min(maxY, state.translateY)),\n };\n}\n\nexport function useLightboxZoom({ enabled, maxScale, currentIndex }: UseLightboxZoomInput) {\n const [zoomState, setZoomState] = useState<ZoomState>(INITIAL_ZOOM_STATE);\n const [isDragging, setIsDragging] = useState(false);\n const imageRef = useRef<HTMLImageElement>(null);\n const isZoomedRef = useRef(false);\n const didDrag = useRef(false);\n const dragStart = useRef({ x: 0, y: 0 });\n const translateStart = useRef({ x: 0, y: 0 });\n const lastPinchDistance = useRef<number | null>(null);\n const lastPointerType = useRef<string>('mouse');\n const lastClickPointerType = useRef<string>('mouse');\n const [isAdjusting, setIsAdjusting] = useState(false);\n const adjustTimeout = useRef<number>(-1);\n\n useEffect(() => {\n isZoomedRef.current = zoomState.isZoomed;\n }, [zoomState.isZoomed]);\n\n const beginAdjust = useCallback(() => {\n setIsAdjusting(true);\n window.clearTimeout(adjustTimeout.current);\n adjustTimeout.current = window.setTimeout(() => setIsAdjusting(false), 120);\n }, []);\n\n const beginAdjustRef = useRef(beginAdjust);\n\n useEffect(() => {\n beginAdjustRef.current = beginAdjust;\n }, [beginAdjust]);\n\n useEffect(() => () => window.clearTimeout(adjustTimeout.current), []);\n\n const resetZoom = useCallback(() => {\n setZoomState(INITIAL_ZOOM_STATE);\n setIsDragging(false);\n }, []);\n\n const [prevIndex, setPrevIndex] = useState(currentIndex);\n const [prevEnabled, setPrevEnabled] = useState(enabled);\n\n // Reset during render rather than from an effect: the new slide is then painted at scale\n // 1 in the same commit instead of flashing the previous slide's zoom for one frame.\n //\n // Turning zoom off has to clear the state as well – the image loses its handlers, but a\n // lingering `isZoomed` keeps the carousel's `watchDrag` guard rejecting drags, so the\n // lightbox would silently stop being swipeable.\n if (currentIndex !== prevIndex || enabled !== prevEnabled) {\n setPrevIndex(currentIndex);\n setPrevEnabled(enabled);\n\n if (currentIndex !== prevIndex || !enabled) {\n setZoomState(INITIAL_ZOOM_STATE);\n setIsDragging(false);\n }\n }\n\n useEffect(() => {\n if (!enabled) {\n lastPinchDistance.current = null;\n didDrag.current = false;\n }\n }, [enabled]);\n\n const toggleZoom = useCallback(() => {\n if (!enabled) {\n return;\n }\n setZoomState((prev) =>\n prev.isZoomed\n ? INITIAL_ZOOM_STATE\n : { scale: Math.min(2, maxScale), translateX: 0, translateY: 0, isZoomed: true }\n );\n }, [enabled, maxScale]);\n\n const zoomAtPoint = useCallback(\n (deltaScale: number, clientX?: number, clientY?: number) => {\n setZoomState((prev) => {\n const nextScale = Math.max(1, Math.min(maxScale, prev.scale + deltaScale));\n if (nextScale === 1) {\n return INITIAL_ZOOM_STATE;\n }\n\n const image = imageRef.current;\n let translateX = prev.translateX;\n let translateY = prev.translateY;\n\n if (image && clientX !== undefined && clientY !== undefined) {\n const rect = image.getBoundingClientRect();\n const scaleRatio = nextScale / prev.scale;\n const offsetX = clientX - (rect.left + rect.width / 2) + prev.translateX;\n const offsetY = clientY - (rect.top + rect.height / 2) + prev.translateY;\n translateX = offsetX * (1 - scaleRatio) + prev.translateX * scaleRatio;\n translateY = offsetY * (1 - scaleRatio) + prev.translateY * scaleRatio;\n }\n\n return clampTranslate({ scale: nextScale, translateX, translateY, isZoomed: true }, image);\n });\n },\n [maxScale]\n );\n\n const panZoom = useCallback((deltaX: number, deltaY: number) => {\n if (!isZoomedRef.current) {\n return false;\n }\n setZoomState((prev) =>\n clampTranslate(\n { ...prev, translateX: prev.translateX + deltaX, translateY: prev.translateY + deltaY },\n imageRef.current\n )\n );\n return true;\n }, []);\n\n const zoomAtPointRef = useRef(zoomAtPoint);\n\n useEffect(() => {\n zoomAtPointRef.current = zoomAtPoint;\n }, [zoomAtPoint]);\n\n const handleNativeWheel = useRef((event: WheelEvent) => {\n // Horizontal trackpad gestures report deltaY === 0 – zooming on those would both\n // hijack the gesture and pick a direction at random.\n if (event.deltaY === 0) {\n return;\n }\n\n event.preventDefault();\n beginAdjustRef.current();\n zoomAtPointRef.current(event.deltaY > 0 ? -0.2 : 0.2, event.clientX, event.clientY);\n });\n\n const setImageRef = useCallback((node: HTMLImageElement | null) => {\n imageRef.current?.removeEventListener('wheel', handleNativeWheel.current);\n imageRef.current = node;\n node?.addEventListener('wheel', handleNativeWheel.current, { passive: false });\n }, []);\n\n const handlePointerDown = useCallback(\n (event: React.PointerEvent) => {\n lastPointerType.current = event.pointerType;\n\n if (!zoomState.isZoomed || !enabled) {\n return;\n }\n setIsDragging(true);\n didDrag.current = false;\n dragStart.current = { x: event.clientX, y: event.clientY };\n translateStart.current = { x: zoomState.translateX, y: zoomState.translateY };\n const target = event.target as HTMLElement;\n if (typeof target?.setPointerCapture === 'function') {\n target.setPointerCapture(event.pointerId);\n }\n },\n [zoomState.isZoomed, zoomState.translateX, zoomState.translateY, enabled]\n );\n\n const handlePointerMove = useCallback(\n (event: React.PointerEvent) => {\n if (!isDragging || !enabled) {\n return;\n }\n const dx = event.clientX - dragStart.current.x;\n const dy = event.clientY - dragStart.current.y;\n if (Math.abs(dx) > 3 || Math.abs(dy) > 3) {\n didDrag.current = true;\n }\n setZoomState((prev) =>\n clampTranslate(\n {\n ...prev,\n translateX: translateStart.current.x + dx,\n translateY: translateStart.current.y + dy,\n },\n imageRef.current\n )\n );\n },\n [isDragging, enabled]\n );\n\n const handlePointerUp = useCallback(() => {\n setIsDragging(false);\n }, []);\n\n const handlePointerCancel = useCallback(() => {\n setIsDragging(false);\n didDrag.current = false;\n lastPinchDistance.current = null;\n }, []);\n\n const handleTouchMove = useCallback(\n (event: React.TouchEvent) => {\n if (!enabled || event.touches.length !== 2) {\n lastPinchDistance.current = null;\n return;\n }\n const touch1 = event.touches[0];\n const touch2 = event.touches[1];\n const distance = Math.hypot(touch1.clientX - touch2.clientX, touch1.clientY - touch2.clientY);\n\n if (lastPinchDistance.current !== null) {\n beginAdjust();\n const delta = (distance - lastPinchDistance.current) * 0.01;\n zoomAtPoint(\n delta,\n (touch1.clientX + touch2.clientX) / 2,\n (touch1.clientY + touch2.clientY) / 2\n );\n }\n lastPinchDistance.current = distance;\n },\n [enabled, zoomAtPoint, beginAdjust]\n );\n\n const handleTouchEnd = useCallback(() => {\n lastPinchDistance.current = null;\n }, []);\n\n const handleClick = useCallback(\n (event: React.MouseEvent) => {\n if (!enabled || event.detail > 1) {\n return;\n }\n if (didDrag.current) {\n didDrag.current = false;\n return;\n }\n\n // Touch taps are handled by the carousel (swipe) and by double-tap to zoom –\n // reacting to the synthetic click as well would fight with both. This checks the\n // input that produced the click rather than whether the device supports touch, so\n // a mouse still zooms on a touchscreen laptop. The recorded type is consumed here\n // so a later click without a preceding pointerdown is not treated as a touch.\n const pointerType = lastPointerType.current;\n lastPointerType.current = 'mouse';\n lastClickPointerType.current = pointerType;\n\n if (pointerType === 'touch') {\n return;\n }\n\n toggleZoom();\n },\n [enabled, toggleZoom]\n );\n\n const handleDoubleClick = useCallback(() => {\n if (!enabled || lastClickPointerType.current !== 'touch') {\n return;\n }\n\n toggleZoom();\n }, [enabled, toggleZoom]);\n\n const getImageProps = useCallback(\n () => ({\n ref: setImageRef,\n style: {\n transform: `scale(${zoomState.scale}) translate(${zoomState.translateX / zoomState.scale}px, ${zoomState.translateY / zoomState.scale}px)`,\n },\n 'data-zoom-enabled': enabled || undefined,\n 'data-zoomed': zoomState.isZoomed || undefined,\n 'data-dragging': isDragging || isAdjusting || undefined,\n onPointerDown: handlePointerDown,\n onPointerMove: handlePointerMove,\n onPointerUp: handlePointerUp,\n onPointerCancel: handlePointerCancel,\n onLostPointerCapture: handlePointerUp,\n onClick: handleClick,\n onDoubleClick: handleDoubleClick,\n onTouchMove: handleTouchMove,\n onTouchEnd: handleTouchEnd,\n }),\n [\n zoomState,\n isDragging,\n isAdjusting,\n enabled,\n setImageRef,\n handlePointerDown,\n handlePointerMove,\n handlePointerUp,\n handlePointerCancel,\n handleClick,\n handleDoubleClick,\n handleTouchMove,\n handleTouchEnd,\n ]\n );\n\n // Both objects are memoized: the lightbox context is built from them, and returning fresh\n // objects on every render would make that context change identity on every root render,\n // re-rendering every slide, thumbnail and custom render function along with it.\n const publicZoomState = useMemo(\n () => ({ scale: zoomState.scale, isZoomed: zoomState.isZoomed }),\n [zoomState.scale, zoomState.isZoomed]\n );\n\n return useMemo(\n () => ({\n zoomState: publicZoomState,\n toggleZoom,\n resetZoom,\n panZoom,\n getImageProps,\n }),\n [publicZoomState, toggleZoom, resetZoom, panZoom, getImageProps]\n );\n}\n"],"mappings":"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"}
package/esm/index.mjs ADDED
@@ -0,0 +1,14 @@
1
+ import { createLightbox, createLightboxStore, lightbox, lightboxStore, useLightboxStore } from "./lightbox.store.mjs";
2
+ import { DEFAULT_LABELS } from "./default-labels.mjs";
3
+ import { LightboxCaption } from "./LightboxCaption/LightboxCaption.mjs";
4
+ import { LightboxCloseButton } from "./LightboxCloseButton/LightboxCloseButton.mjs";
5
+ import { LightboxNavigation } from "./LightboxNavigation/LightboxNavigation.mjs";
6
+ import { LightboxProviderComponent } from "./LightboxProvider/LightboxProvider.mjs";
7
+ import { LightboxRoot } from "./LightboxRoot/LightboxRoot.mjs";
8
+ import { LightboxSlide } from "./LightboxSlide/LightboxSlide.mjs";
9
+ import { LightboxSlides } from "./LightboxSlides/LightboxSlides.mjs";
10
+ import { LightboxThumbnails } from "./LightboxThumbnails/LightboxThumbnails.mjs";
11
+ import { createCloseToolbarItem, createDownloadToolbarItem, createFullscreenToolbarItem, createThumbnailsToolbarItem } from "./LightboxToolbar/toolbar-items.mjs";
12
+ import { LightboxToolbar } from "./LightboxToolbar/LightboxToolbar.mjs";
13
+ import { Lightbox } from "./Lightbox.mjs";
14
+ export { DEFAULT_LABELS, Lightbox, LightboxCaption, LightboxCloseButton, LightboxNavigation, LightboxProviderComponent, LightboxRoot, LightboxSlide, LightboxSlides, LightboxThumbnails, LightboxToolbar, createCloseToolbarItem, createDownloadToolbarItem, createFullscreenToolbarItem, createLightbox, createLightboxStore, createThumbnailsToolbarItem, lightbox, lightboxStore, useLightboxStore };
@@ -0,0 +1,8 @@
1
+ "use client";
2
+ import { createSafeContext } from "@mantine/core";
3
+ //#region packages/@mantine/lightbox/src/lightbox.context.ts
4
+ const [LightboxContextProvider, useLightboxContext] = createSafeContext("Lightbox component was not found in tree");
5
+ //#endregion
6
+ export { LightboxContextProvider, useLightboxContext };
7
+
8
+ //# sourceMappingURL=lightbox.context.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lightbox.context.mjs","names":[],"sources":["../src/lightbox.context.ts"],"sourcesContent":["import type { EmblaCarouselType } from 'embla-carousel';\nimport { createSafeContext, GetStylesApi } from '@mantine/core';\nimport type { LightboxLabels, LightboxSlideData } from './lightbox.types';\nimport type { LightboxRootFactory } from './LightboxRoot/LightboxRoot';\n\nexport interface LightboxContextValue {\n getStyles: GetStylesApi<LightboxRootFactory>;\n labels: LightboxLabels;\n opened: boolean;\n slides: LightboxSlideData[];\n currentIndex: number;\n setIndex: (index: number) => void;\n next: () => void;\n prev: () => void;\n embla: EmblaCarouselType | null;\n emblaRef: React.RefCallback<HTMLDivElement> | null;\n withZoom: boolean;\n withThumbnails: boolean;\n withFullscreen: boolean;\n withDownload: boolean;\n thumbnailsVisible: boolean;\n toggleThumbnails: () => void;\n isFullscreen: boolean;\n toggleFullscreen: () => void;\n zoomState: { scale: number; isZoomed: boolean };\n toggleZoom: () => void;\n getImageZoomProps: () => Record<string, any>;\n onClose: () => void;\n loop: boolean;\n closeOnClickOutside: boolean;\n closeOnSwipeDown: boolean;\n transitionDuration: number;\n}\n\nexport const [LightboxContextProvider, useLightboxContext] =\n createSafeContext<LightboxContextValue>('Lightbox component was not found in tree');\n"],"mappings":";;;AAkCA,MAAa,CAAC,yBAAyB,sBACrC,kBAAwC,0CAA0C"}
@@ -0,0 +1,79 @@
1
+ "use client";
2
+ import { createStore, useStore } from "@mantine/store";
3
+ //#region packages/@mantine/lightbox/src/lightbox.store.ts
4
+ /** Creates a new lightbox store, use to run multiple independent lightboxes */
5
+ const createLightboxStore = () => createStore({
6
+ opened: false,
7
+ slides: [],
8
+ currentIndex: 0,
9
+ loop: false
10
+ });
11
+ /** Subscribes to the given lightbox store and returns its current state */
12
+ const useLightboxStore = (store) => useStore(store);
13
+ function clampIndex(index, slidesCount) {
14
+ return Math.max(0, Math.min(Number.isFinite(index) ? Math.round(index) : 0, Math.max(0, slidesCount - 1)));
15
+ }
16
+ function updateLightboxStateAction(update, store) {
17
+ const state = store.getState();
18
+ store.setState({
19
+ ...state,
20
+ ...update(state)
21
+ });
22
+ }
23
+ function openLightboxAction(payload, store) {
24
+ updateLightboxStateAction(() => ({
25
+ opened: true,
26
+ slides: payload.slides,
27
+ currentIndex: clampIndex(payload.startIndex ?? 0, payload.slides.length)
28
+ }), store);
29
+ }
30
+ function closeLightboxAction(store) {
31
+ updateLightboxStateAction(() => ({ opened: false }), store);
32
+ }
33
+ function nextLightboxAction(store) {
34
+ updateLightboxStateAction((state) => {
35
+ const nextIndex = state.currentIndex + 1;
36
+ if (nextIndex < state.slides.length) return { currentIndex: nextIndex };
37
+ return { currentIndex: state.loop && state.slides.length > 0 ? 0 : state.currentIndex };
38
+ }, store);
39
+ }
40
+ function prevLightboxAction(store) {
41
+ updateLightboxStateAction((state) => {
42
+ const prevIndex = state.currentIndex - 1;
43
+ if (prevIndex >= 0) return { currentIndex: prevIndex };
44
+ return { currentIndex: state.loop && state.slides.length > 0 ? state.slides.length - 1 : 0 };
45
+ }, store);
46
+ }
47
+ function setLightboxIndexAction(index, store) {
48
+ updateLightboxStateAction((state) => ({ currentIndex: clampIndex(index, state.slides.length) }), store);
49
+ }
50
+ const lightboxActions = {
51
+ open: openLightboxAction,
52
+ close: closeLightboxAction,
53
+ next: nextLightboxAction,
54
+ prev: prevLightboxAction,
55
+ setIndex: setLightboxIndexAction,
56
+ updateState: updateLightboxStateAction
57
+ };
58
+ /** Creates an isolated store and a set of actions bound to it, use to run multiple independent lightboxes */
59
+ function createLightbox() {
60
+ const store = createLightboxStore();
61
+ return [store, {
62
+ /** Opens the lightbox with the given slides, optionally at the given index */
63
+ open: (payload) => openLightboxAction(payload, store),
64
+ /** Closes the lightbox */
65
+ close: () => closeLightboxAction(store),
66
+ /** Navigates to the next slide */
67
+ next: () => nextLightboxAction(store),
68
+ /** Navigates to the previous slide */
69
+ prev: () => prevLightboxAction(store),
70
+ /** Sets the current slide index, the value is clamped to the slides range */
71
+ setIndex: (index) => setLightboxIndexAction(index, store)
72
+ }];
73
+ }
74
+ /** Default lightbox store (`lightboxStore`) and actions bound to it (`lightbox`), used by `Lightbox.Provider` and static `Lightbox.open/close/next/prev/setIndex` methods */
75
+ const [lightboxStore, lightbox] = createLightbox();
76
+ //#endregion
77
+ export { closeLightboxAction, createLightbox, createLightboxStore, lightbox, lightboxActions, lightboxStore, nextLightboxAction, openLightboxAction, prevLightboxAction, setLightboxIndexAction, updateLightboxStateAction, useLightboxStore };
78
+
79
+ //# sourceMappingURL=lightbox.store.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lightbox.store.mjs","names":[],"sources":["../src/lightbox.store.ts"],"sourcesContent":["import { createStore, MantineStore, useStore } from '@mantine/store';\nimport type { LightboxSlideData } from './lightbox.types';\n\nexport interface LightboxState {\n /** Controls whether the lightbox is opened */\n opened: boolean;\n\n /** Array of slide data objects */\n slides: LightboxSlideData[];\n\n /** Index of the current slide */\n currentIndex: number;\n\n /** Determines whether `next` and `prev` actions wrap around, synced from the `loop` prop of `Lightbox.Provider` */\n loop: boolean;\n}\n\nexport type LightboxStore = MantineStore<LightboxState>;\n\n/** Creates a new lightbox store, use to run multiple independent lightboxes */\nexport const createLightboxStore = () =>\n createStore<LightboxState>({\n opened: false,\n slides: [],\n currentIndex: 0,\n loop: false,\n });\n\n/** Subscribes to the given lightbox store and returns its current state */\nexport const useLightboxStore = (store: LightboxStore) => useStore(store);\n\nfunction clampIndex(index: number, slidesCount: number) {\n const normalized = Number.isFinite(index) ? Math.round(index) : 0;\n return Math.max(0, Math.min(normalized, Math.max(0, slidesCount - 1)));\n}\n\nexport function updateLightboxStateAction(\n update: (state: LightboxState) => Partial<LightboxState>,\n store: LightboxStore\n) {\n const state = store.getState();\n store.setState({ ...state, ...update(state) });\n}\n\nexport function openLightboxAction(\n payload: { slides: LightboxSlideData[]; startIndex?: number },\n store: LightboxStore\n) {\n updateLightboxStateAction(\n () => ({\n opened: true,\n slides: payload.slides,\n currentIndex: clampIndex(payload.startIndex ?? 0, payload.slides.length),\n }),\n store\n );\n}\n\nexport function closeLightboxAction(store: LightboxStore) {\n updateLightboxStateAction(() => ({ opened: false }), store);\n}\n\nexport function nextLightboxAction(store: LightboxStore) {\n updateLightboxStateAction((state) => {\n const nextIndex = state.currentIndex + 1;\n\n if (nextIndex < state.slides.length) {\n return { currentIndex: nextIndex };\n }\n\n return { currentIndex: state.loop && state.slides.length > 0 ? 0 : state.currentIndex };\n }, store);\n}\n\nexport function prevLightboxAction(store: LightboxStore) {\n updateLightboxStateAction((state) => {\n const prevIndex = state.currentIndex - 1;\n\n if (prevIndex >= 0) {\n return { currentIndex: prevIndex };\n }\n\n return {\n currentIndex: state.loop && state.slides.length > 0 ? state.slides.length - 1 : 0,\n };\n }, store);\n}\n\nexport function setLightboxIndexAction(index: number, store: LightboxStore) {\n updateLightboxStateAction(\n (state) => ({ currentIndex: clampIndex(index, state.slides.length) }),\n store\n );\n}\n\nexport const lightboxActions = {\n open: openLightboxAction,\n close: closeLightboxAction,\n next: nextLightboxAction,\n prev: prevLightboxAction,\n setIndex: setLightboxIndexAction,\n updateState: updateLightboxStateAction,\n};\n\n/** Creates an isolated store and a set of actions bound to it, use to run multiple independent lightboxes */\nexport function createLightbox() {\n const store = createLightboxStore();\n const actions = {\n /** Opens the lightbox with the given slides, optionally at the given index */\n open: (payload: { slides: LightboxSlideData[]; startIndex?: number }) =>\n openLightboxAction(payload, store),\n\n /** Closes the lightbox */\n close: () => closeLightboxAction(store),\n\n /** Navigates to the next slide */\n next: () => nextLightboxAction(store),\n\n /** Navigates to the previous slide */\n prev: () => prevLightboxAction(store),\n\n /** Sets the current slide index, the value is clamped to the slides range */\n setIndex: (index: number) => setLightboxIndexAction(index, store),\n };\n\n return [store, actions] as const;\n}\n\n/** Default lightbox store (`lightboxStore`) and actions bound to it (`lightbox`), used by `Lightbox.Provider` and static `Lightbox.open/close/next/prev/setIndex` methods */\nexport const [lightboxStore, lightbox] = createLightbox();\n"],"mappings":";;;;AAoBA,MAAa,4BACX,YAA2B;CACzB,QAAQ;CACR,QAAQ,CAAC;CACT,cAAc;CACd,MAAM;AACR,CAAC;;AAGH,MAAa,oBAAoB,UAAyB,SAAS,KAAK;AAExE,SAAS,WAAW,OAAe,aAAqB;CAEtD,OAAO,KAAK,IAAI,GAAG,KAAK,IADL,OAAO,SAAS,KAAK,IAAI,KAAK,MAAM,KAAK,IAAI,GACxB,KAAK,IAAI,GAAG,cAAc,CAAC,CAAC,CAAC;AACvE;AAEA,SAAgB,0BACd,QACA,OACA;CACA,MAAM,QAAQ,MAAM,SAAS;CAC7B,MAAM,SAAS;EAAE,GAAG;EAAO,GAAG,OAAO,KAAK;CAAE,CAAC;AAC/C;AAEA,SAAgB,mBACd,SACA,OACA;CACA,iCACS;EACL,QAAQ;EACR,QAAQ,QAAQ;EAChB,cAAc,WAAW,QAAQ,cAAc,GAAG,QAAQ,OAAO,MAAM;CACzE,IACA,KACF;AACF;AAEA,SAAgB,oBAAoB,OAAsB;CACxD,iCAAiC,EAAE,QAAQ,MAAM,IAAI,KAAK;AAC5D;AAEA,SAAgB,mBAAmB,OAAsB;CACvD,2BAA2B,UAAU;EACnC,MAAM,YAAY,MAAM,eAAe;EAEvC,IAAI,YAAY,MAAM,OAAO,QAC3B,OAAO,EAAE,cAAc,UAAU;EAGnC,OAAO,EAAE,cAAc,MAAM,QAAQ,MAAM,OAAO,SAAS,IAAI,IAAI,MAAM,aAAa;CACxF,GAAG,KAAK;AACV;AAEA,SAAgB,mBAAmB,OAAsB;CACvD,2BAA2B,UAAU;EACnC,MAAM,YAAY,MAAM,eAAe;EAEvC,IAAI,aAAa,GACf,OAAO,EAAE,cAAc,UAAU;EAGnC,OAAO,EACL,cAAc,MAAM,QAAQ,MAAM,OAAO,SAAS,IAAI,MAAM,OAAO,SAAS,IAAI,EAClF;CACF,GAAG,KAAK;AACV;AAEA,SAAgB,uBAAuB,OAAe,OAAsB;CAC1E,2BACG,WAAW,EAAE,cAAc,WAAW,OAAO,MAAM,OAAO,MAAM,EAAE,IACnE,KACF;AACF;AAEA,MAAa,kBAAkB;CAC7B,MAAM;CACN,OAAO;CACP,MAAM;CACN,MAAM;CACN,UAAU;CACV,aAAa;AACf;;AAGA,SAAgB,iBAAiB;CAC/B,MAAM,QAAQ,oBAAoB;CAmBlC,OAAO,CAAC,OAAO;;EAhBb,OAAO,YACL,mBAAmB,SAAS,KAAK;;EAGnC,aAAa,oBAAoB,KAAK;;EAGtC,YAAY,mBAAmB,KAAK;;EAGpC,YAAY,mBAAmB,KAAK;;EAGpC,WAAW,UAAkB,uBAAuB,OAAO,KAAK;CAG7C,CAAC;AACxB;;AAGA,MAAa,CAAC,eAAe,YAAY,eAAe"}