@djangocfg/ui-tools 2.1.110 → 2.1.111

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 (159) hide show
  1. package/README.md +242 -49
  2. package/dist/JsonSchemaForm-65NLLK56.mjs +4 -0
  3. package/dist/JsonSchemaForm-65NLLK56.mjs.map +1 -0
  4. package/dist/JsonSchemaForm-PY6DH3HE.cjs +13 -0
  5. package/dist/JsonSchemaForm-PY6DH3HE.cjs.map +1 -0
  6. package/dist/JsonTree-6RYAOPSS.mjs +4 -0
  7. package/dist/JsonTree-6RYAOPSS.mjs.map +1 -0
  8. package/dist/JsonTree-7OH6CIHT.cjs +10 -0
  9. package/dist/JsonTree-7OH6CIHT.cjs.map +1 -0
  10. package/dist/MapContainer-GXQLP5WY.mjs +214 -0
  11. package/dist/MapContainer-GXQLP5WY.mjs.map +1 -0
  12. package/dist/MapContainer-RYG4HPH4.cjs +221 -0
  13. package/dist/MapContainer-RYG4HPH4.cjs.map +1 -0
  14. package/dist/{Mermaid.client-4OCKJ6QD.mjs → Mermaid.client-OKACITCW.mjs} +16 -7
  15. package/dist/Mermaid.client-OKACITCW.mjs.map +1 -0
  16. package/dist/{Mermaid.client-ZP6OE46Z.cjs → Mermaid.client-PNXEC6YL.cjs} +16 -7
  17. package/dist/Mermaid.client-PNXEC6YL.cjs.map +1 -0
  18. package/dist/{PlaygroundLayout-XXVBU4WZ.cjs → PlaygroundLayout-SYMEAG3J.cjs} +25 -24
  19. package/dist/PlaygroundLayout-SYMEAG3J.cjs.map +1 -0
  20. package/dist/{PlaygroundLayout-LMQTVXSP.mjs → PlaygroundLayout-UQRBU5RH.mjs} +4 -3
  21. package/dist/PlaygroundLayout-UQRBU5RH.mjs.map +1 -0
  22. package/dist/{PrettyCode.client-2CLSV2VD.cjs → PrettyCode.client-DANYYQYO.cjs} +11 -4
  23. package/dist/PrettyCode.client-DANYYQYO.cjs.map +1 -0
  24. package/dist/{PrettyCode.client-Y2BVON7R.mjs → PrettyCode.client-RS5ZTNBT.mjs} +11 -4
  25. package/dist/PrettyCode.client-RS5ZTNBT.mjs.map +1 -0
  26. package/dist/chunk-2DSR7V2L.mjs +561 -0
  27. package/dist/chunk-2DSR7V2L.mjs.map +1 -0
  28. package/dist/chunk-47T5ECYV.cjs +1357 -0
  29. package/dist/chunk-47T5ECYV.cjs.map +1 -0
  30. package/dist/chunk-5QT3QYFZ.cjs +189 -0
  31. package/dist/chunk-5QT3QYFZ.cjs.map +1 -0
  32. package/dist/chunk-7IIRYG4S.mjs +1057 -0
  33. package/dist/chunk-7IIRYG4S.mjs.map +1 -0
  34. package/dist/{chunk-FB5QBSI3.cjs → chunk-DI3HUXHK.cjs} +15 -195
  35. package/dist/chunk-DI3HUXHK.cjs.map +1 -0
  36. package/dist/chunk-EVGWYASL.cjs +1528 -0
  37. package/dist/chunk-EVGWYASL.cjs.map +1 -0
  38. package/dist/chunk-F2N7P5XU.cjs +30 -0
  39. package/dist/chunk-F2N7P5XU.cjs.map +1 -0
  40. package/dist/{chunk-L6UHASYQ.mjs → chunk-G6PRZP5I.mjs} +7 -186
  41. package/dist/chunk-G6PRZP5I.mjs.map +1 -0
  42. package/dist/chunk-JWB2EWQO.mjs +5 -0
  43. package/dist/chunk-JWB2EWQO.mjs.map +1 -0
  44. package/dist/chunk-LTJX2JXE.mjs +338 -0
  45. package/dist/chunk-LTJX2JXE.mjs.map +1 -0
  46. package/dist/chunk-OVNC4KW6.mjs +1494 -0
  47. package/dist/chunk-OVNC4KW6.mjs.map +1 -0
  48. package/dist/chunk-PNZSJN6T.cjs +1086 -0
  49. package/dist/chunk-PNZSJN6T.cjs.map +1 -0
  50. package/dist/chunk-TEFRA7GW.cjs +565 -0
  51. package/dist/chunk-TEFRA7GW.cjs.map +1 -0
  52. package/dist/chunk-UOMPPIED.mjs +1343 -0
  53. package/dist/chunk-UOMPPIED.mjs.map +1 -0
  54. package/dist/chunk-W6YHQI4F.mjs +187 -0
  55. package/dist/chunk-W6YHQI4F.mjs.map +1 -0
  56. package/dist/chunk-XTBRWVIV.cjs +346 -0
  57. package/dist/chunk-XTBRWVIV.cjs.map +1 -0
  58. package/dist/components-C7ZL7OMY.mjs +5 -0
  59. package/dist/components-C7ZL7OMY.mjs.map +1 -0
  60. package/dist/components-CJ2IB65O.cjs +27 -0
  61. package/dist/components-CJ2IB65O.cjs.map +1 -0
  62. package/dist/components-EASJYK45.mjs +6 -0
  63. package/dist/components-EASJYK45.mjs.map +1 -0
  64. package/dist/components-LDRFDV4A.cjs +22 -0
  65. package/dist/components-LDRFDV4A.cjs.map +1 -0
  66. package/dist/components-VZKUTDJK.mjs +5 -0
  67. package/dist/components-VZKUTDJK.mjs.map +1 -0
  68. package/dist/components-Y64GTIMQ.cjs +42 -0
  69. package/dist/components-Y64GTIMQ.cjs.map +1 -0
  70. package/dist/index.cjs +701 -4813
  71. package/dist/index.cjs.map +1 -1
  72. package/dist/index.d.cts +1274 -1026
  73. package/dist/index.d.ts +1274 -1026
  74. package/dist/index.mjs +358 -4730
  75. package/dist/index.mjs.map +1 -1
  76. package/package.json +27 -4
  77. package/src/components/index.ts +17 -0
  78. package/src/components/lazy-wrapper.tsx +281 -0
  79. package/src/index.ts +92 -7
  80. package/src/tools/AudioPlayer/components/HybridAudioPlayer.tsx +14 -5
  81. package/src/tools/AudioPlayer/lazy.tsx +85 -0
  82. package/src/tools/Gallery/components/Gallery.tsx +182 -0
  83. package/src/tools/Gallery/components/GalleryCarousel.tsx +251 -0
  84. package/src/tools/Gallery/components/GalleryCompact.tsx +173 -0
  85. package/src/tools/Gallery/components/GalleryGrid.tsx +493 -0
  86. package/src/tools/Gallery/components/GalleryImage.tsx +66 -0
  87. package/src/tools/Gallery/components/GalleryLightbox.tsx +331 -0
  88. package/src/tools/Gallery/components/GalleryMedia.tsx +66 -0
  89. package/src/tools/Gallery/components/GalleryThumbnails.tsx +173 -0
  90. package/src/tools/Gallery/components/GalleryThumbnailsVirtual.tsx +138 -0
  91. package/src/tools/Gallery/components/GalleryVideo.tsx +222 -0
  92. package/src/tools/Gallery/components/index.ts +13 -0
  93. package/src/tools/Gallery/hooks/index.ts +23 -0
  94. package/src/tools/Gallery/hooks/useGallery.ts +137 -0
  95. package/src/tools/Gallery/hooks/useImageDimensions.ts +223 -0
  96. package/src/tools/Gallery/hooks/usePinchZoom.ts +234 -0
  97. package/src/tools/Gallery/hooks/usePreloadImages.ts +71 -0
  98. package/src/tools/Gallery/hooks/useSwipe.ts +86 -0
  99. package/src/tools/Gallery/hooks/useVirtualList.ts +129 -0
  100. package/src/tools/Gallery/hooks/useZoom.ts +316 -0
  101. package/src/tools/Gallery/index.ts +66 -0
  102. package/src/tools/Gallery/types.ts +183 -0
  103. package/src/tools/Gallery/utils/imageAnalysis.ts +52 -0
  104. package/src/tools/Gallery/utils/index.ts +11 -0
  105. package/src/tools/ImageViewer/components/ImageToolbar.tsx +20 -8
  106. package/src/tools/ImageViewer/components/ImageViewer.tsx +12 -4
  107. package/src/tools/ImageViewer/lazy.tsx +37 -0
  108. package/src/tools/JsonForm/lazy.tsx +43 -0
  109. package/src/tools/JsonForm/widgets/ColorWidget.tsx +4 -1
  110. package/src/tools/JsonTree/lazy.tsx +45 -0
  111. package/src/tools/LottiePlayer/lazy.tsx +57 -0
  112. package/src/tools/Map/components/CustomOverlay.tsx +54 -0
  113. package/src/tools/Map/components/DrawControl.tsx +36 -0
  114. package/src/tools/Map/components/GeocoderControl.tsx +70 -0
  115. package/src/tools/Map/components/LayerSwitcher.tsx +225 -0
  116. package/src/tools/Map/components/MapCluster.tsx +273 -0
  117. package/src/tools/Map/components/MapContainer.tsx +191 -0
  118. package/src/tools/Map/components/MapControls.tsx +44 -0
  119. package/src/tools/Map/components/MapLegend.tsx +161 -0
  120. package/src/tools/Map/components/MapMarker.tsx +102 -0
  121. package/src/tools/Map/components/MapPopup.tsx +46 -0
  122. package/src/tools/Map/components/MapSource.tsx +30 -0
  123. package/src/tools/Map/components/index.ts +20 -0
  124. package/src/tools/Map/context/MapContext.tsx +89 -0
  125. package/src/tools/Map/context/index.ts +2 -0
  126. package/src/tools/Map/hooks/index.ts +9 -0
  127. package/src/tools/Map/hooks/useMap.ts +11 -0
  128. package/src/tools/Map/hooks/useMapControl.ts +99 -0
  129. package/src/tools/Map/hooks/useMapEvents.ts +147 -0
  130. package/src/tools/Map/hooks/useMapLayers.ts +83 -0
  131. package/src/tools/Map/hooks/useMapViewport.ts +62 -0
  132. package/src/tools/Map/hooks/useMarkers.ts +85 -0
  133. package/src/tools/Map/index.ts +116 -0
  134. package/src/tools/Map/layers/cluster.ts +94 -0
  135. package/src/tools/Map/layers/index.ts +15 -0
  136. package/src/tools/Map/layers/line.ts +93 -0
  137. package/src/tools/Map/layers/point.ts +61 -0
  138. package/src/tools/Map/layers/polygon.ts +73 -0
  139. package/src/tools/Map/lazy.tsx +56 -0
  140. package/src/tools/Map/styles/index.ts +15 -0
  141. package/src/tools/Map/types.ts +259 -0
  142. package/src/tools/Map/utils/geo.ts +88 -0
  143. package/src/tools/Map/utils/index.ts +16 -0
  144. package/src/tools/Map/utils/transform.ts +107 -0
  145. package/src/tools/Mermaid/Mermaid.client.tsx +12 -4
  146. package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +6 -2
  147. package/src/tools/Mermaid/lazy.tsx +46 -0
  148. package/src/tools/OpenapiViewer/lazy.tsx +72 -0
  149. package/src/tools/PrettyCode/PrettyCode.client.tsx +10 -3
  150. package/src/tools/PrettyCode/lazy.tsx +64 -0
  151. package/src/tools/VideoPlayer/lazy.tsx +63 -0
  152. package/dist/Mermaid.client-4OCKJ6QD.mjs.map +0 -1
  153. package/dist/Mermaid.client-ZP6OE46Z.cjs.map +0 -1
  154. package/dist/PlaygroundLayout-LMQTVXSP.mjs.map +0 -1
  155. package/dist/PlaygroundLayout-XXVBU4WZ.cjs.map +0 -1
  156. package/dist/PrettyCode.client-2CLSV2VD.cjs.map +0 -1
  157. package/dist/PrettyCode.client-Y2BVON7R.mjs.map +0 -1
  158. package/dist/chunk-FB5QBSI3.cjs.map +0 -1
  159. package/dist/chunk-L6UHASYQ.mjs.map +0 -1
@@ -0,0 +1,316 @@
1
+ 'use client'
2
+
3
+ import { useCallback, useRef, useState } from 'react'
4
+
5
+ export interface ZoomState {
6
+ scale: number
7
+ x: number
8
+ y: number
9
+ }
10
+
11
+ export interface UseZoomOptions {
12
+ minScale?: number
13
+ maxScale?: number
14
+ desktopScale?: number
15
+ onZoomChange?: (zoomed: boolean) => void
16
+ }
17
+
18
+ export interface UseZoomReturn {
19
+ state: ZoomState
20
+ isZoomed: boolean
21
+ isDragging: boolean
22
+ handlers: {
23
+ onTouchStart: (e: React.TouchEvent) => void
24
+ onTouchMove: (e: React.TouchEvent) => void
25
+ onTouchEnd: (e: React.TouchEvent) => void
26
+ onClick: (e: React.MouseEvent) => void
27
+ onMouseDown: (e: React.MouseEvent) => void
28
+ onMouseMove: (e: React.MouseEvent) => void
29
+ onMouseUp: () => void
30
+ onMouseLeave: () => void
31
+ }
32
+ reset: () => void
33
+ zoomTo: (scale: number, x?: number, y?: number) => void
34
+ toggleZoom: () => void
35
+ }
36
+
37
+ const INITIAL_STATE: ZoomState = { scale: 1, x: 0, y: 0 }
38
+ const ZOOM_THRESHOLD = 1.05
39
+
40
+ /**
41
+ * Unified zoom hook for both desktop (click/drag) and mobile (pinch/tap)
42
+ */
43
+ export function useZoom(options: UseZoomOptions = {}): UseZoomReturn {
44
+ const { minScale = 1, maxScale = 3, desktopScale = 2, onZoomChange } = options
45
+
46
+ const [state, setState] = useState<ZoomState>(INITIAL_STATE)
47
+ const [isDragging, setIsDragging] = useState(false)
48
+
49
+ const touchRef = useRef<{
50
+ // Touch tracking
51
+ initialDistance: number
52
+ initialScale: number
53
+ initialX: number
54
+ initialY: number
55
+ centerX: number
56
+ centerY: number
57
+ isPinching: boolean
58
+ lastTap: number
59
+ // Container dimensions
60
+ containerWidth: number
61
+ containerHeight: number
62
+ // Mouse drag tracking
63
+ dragStartX: number
64
+ dragStartY: number
65
+ }>({
66
+ initialDistance: 0,
67
+ initialScale: 1,
68
+ initialX: 0,
69
+ initialY: 0,
70
+ centerX: 0,
71
+ centerY: 0,
72
+ isPinching: false,
73
+ lastTap: 0,
74
+ containerWidth: 0,
75
+ containerHeight: 0,
76
+ dragStartX: 0,
77
+ dragStartY: 0,
78
+ })
79
+
80
+ const isZoomed = state.scale > ZOOM_THRESHOLD
81
+
82
+ // Calculate distance between two touch points
83
+ const getDistance = useCallback((touches: React.TouchList): number => {
84
+ if (touches.length < 2) return 0
85
+ const dx = touches[0].clientX - touches[1].clientX
86
+ const dy = touches[0].clientY - touches[1].clientY
87
+ return Math.sqrt(dx * dx + dy * dy)
88
+ }, [])
89
+
90
+ // Get center point between two touches
91
+ const getCenter = useCallback((touches: React.TouchList): { x: number; y: number } => {
92
+ if (touches.length < 2) {
93
+ return { x: touches[0].clientX, y: touches[0].clientY }
94
+ }
95
+ return {
96
+ x: (touches[0].clientX + touches[1].clientX) / 2,
97
+ y: (touches[0].clientY + touches[1].clientY) / 2,
98
+ }
99
+ }, [])
100
+
101
+ // Calculate max pan based on container size and scale
102
+ const getMaxPan = useCallback((scale: number) => {
103
+ const { containerWidth, containerHeight } = touchRef.current
104
+ return {
105
+ x: (containerWidth * (scale - 1)) / 2,
106
+ y: (containerHeight * (scale - 1)) / 2,
107
+ }
108
+ }, [])
109
+
110
+ // Clamp pan position within bounds
111
+ const clampPan = useCallback((x: number, y: number, scale: number) => {
112
+ const maxPan = getMaxPan(scale)
113
+ return {
114
+ x: Math.max(-maxPan.x, Math.min(maxPan.x, x)),
115
+ y: Math.max(-maxPan.y, Math.min(maxPan.y, y)),
116
+ }
117
+ }, [getMaxPan])
118
+
119
+ // ========== TOUCH HANDLERS (MOBILE) ==========
120
+
121
+ const handleTouchStart = useCallback((e: React.TouchEvent) => {
122
+ const touches = e.touches
123
+ const rect = e.currentTarget.getBoundingClientRect()
124
+ touchRef.current.containerWidth = rect.width
125
+ touchRef.current.containerHeight = rect.height
126
+
127
+ // Double tap to zoom
128
+ if (touches.length === 1) {
129
+ const now = Date.now()
130
+ const timeSinceLastTap = now - touchRef.current.lastTap
131
+
132
+ if (timeSinceLastTap < 300) {
133
+ e.preventDefault()
134
+ if (isZoomed) {
135
+ setState(INITIAL_STATE)
136
+ onZoomChange?.(false)
137
+ } else {
138
+ const x = touches[0].clientX - rect.left - rect.width / 2
139
+ const y = touches[0].clientY - rect.top - rect.height / 2
140
+ setState({ scale: desktopScale, x: -x * 0.5, y: -y * 0.5 })
141
+ onZoomChange?.(true)
142
+ }
143
+ touchRef.current.lastTap = 0
144
+ return
145
+ }
146
+
147
+ touchRef.current.lastTap = now
148
+
149
+ // Single finger - start pan (if zoomed)
150
+ if (isZoomed) {
151
+ touchRef.current.initialX = touches[0].clientX - state.x
152
+ touchRef.current.initialY = touches[0].clientY - state.y
153
+ }
154
+ }
155
+
156
+ // Two finger pinch
157
+ if (touches.length === 2) {
158
+ e.preventDefault()
159
+ touchRef.current.isPinching = true
160
+ touchRef.current.initialDistance = getDistance(touches)
161
+ touchRef.current.initialScale = state.scale
162
+
163
+ const center = getCenter(touches)
164
+ touchRef.current.centerX = center.x
165
+ touchRef.current.centerY = center.y
166
+ touchRef.current.initialX = state.x
167
+ touchRef.current.initialY = state.y
168
+ }
169
+ }, [isZoomed, state, getDistance, getCenter, desktopScale, onZoomChange])
170
+
171
+ const handleTouchMove = useCallback((e: React.TouchEvent) => {
172
+ const touches = e.touches
173
+
174
+ // Pinch zoom
175
+ if (touches.length === 2 && touchRef.current.isPinching) {
176
+ e.preventDefault()
177
+
178
+ const currentDistance = getDistance(touches)
179
+ const scaleDelta = currentDistance / touchRef.current.initialDistance
180
+ let newScale = touchRef.current.initialScale * scaleDelta
181
+ newScale = Math.max(minScale, Math.min(maxScale, newScale))
182
+
183
+ const center = getCenter(touches)
184
+ const dx = center.x - touchRef.current.centerX
185
+ const dy = center.y - touchRef.current.centerY
186
+
187
+ const { x: clampedX, y: clampedY } = clampPan(
188
+ touchRef.current.initialX + dx,
189
+ touchRef.current.initialY + dy,
190
+ newScale
191
+ )
192
+
193
+ setState({ scale: newScale, x: clampedX, y: clampedY })
194
+
195
+ if (newScale > ZOOM_THRESHOLD !== isZoomed) {
196
+ onZoomChange?.(newScale > ZOOM_THRESHOLD)
197
+ }
198
+ }
199
+
200
+ // Pan when zoomed (single finger)
201
+ if (touches.length === 1 && isZoomed && !touchRef.current.isPinching) {
202
+ e.preventDefault()
203
+
204
+ const newX = touches[0].clientX - touchRef.current.initialX
205
+ const newY = touches[0].clientY - touchRef.current.initialY
206
+ const { x: clampedX, y: clampedY } = clampPan(newX, newY, state.scale)
207
+
208
+ setState((prev) => ({ ...prev, x: clampedX, y: clampedY }))
209
+ }
210
+ }, [isZoomed, state.scale, minScale, maxScale, getDistance, getCenter, clampPan, onZoomChange])
211
+
212
+ const handleTouchEnd = useCallback((e: React.TouchEvent) => {
213
+ if (e.touches.length < 2) {
214
+ touchRef.current.isPinching = false
215
+ }
216
+
217
+ // Snap to 1 if close
218
+ if (state.scale < 1.1 && state.scale > 0.9) {
219
+ setState(INITIAL_STATE)
220
+ onZoomChange?.(false)
221
+ }
222
+ }, [state.scale, onZoomChange])
223
+
224
+ // ========== MOUSE HANDLERS (DESKTOP) ==========
225
+
226
+ const handleClick = useCallback((e: React.MouseEvent) => {
227
+ // Skip if we were dragging
228
+ if (isDragging) return
229
+
230
+ const rect = e.currentTarget.getBoundingClientRect()
231
+ touchRef.current.containerWidth = rect.width
232
+ touchRef.current.containerHeight = rect.height
233
+
234
+ if (isZoomed) {
235
+ setState(INITIAL_STATE)
236
+ onZoomChange?.(false)
237
+ } else {
238
+ // Zoom to click position
239
+ const x = ((e.clientX - rect.left) / rect.width - 0.5) * -100
240
+ const y = ((e.clientY - rect.top) / rect.height - 0.5) * -100
241
+ setState({ scale: desktopScale, x, y })
242
+ onZoomChange?.(true)
243
+ }
244
+ }, [isZoomed, isDragging, desktopScale, onZoomChange])
245
+
246
+ const handleMouseDown = useCallback((e: React.MouseEvent) => {
247
+ if (!isZoomed) return
248
+ e.preventDefault()
249
+ setIsDragging(true)
250
+ touchRef.current.dragStartX = e.clientX - state.x
251
+ touchRef.current.dragStartY = e.clientY - state.y
252
+
253
+ const rect = e.currentTarget.getBoundingClientRect()
254
+ touchRef.current.containerWidth = rect.width
255
+ touchRef.current.containerHeight = rect.height
256
+ }, [isZoomed, state.x, state.y])
257
+
258
+ const handleMouseMove = useCallback((e: React.MouseEvent) => {
259
+ if (!isDragging) return
260
+
261
+ const newX = e.clientX - touchRef.current.dragStartX
262
+ const newY = e.clientY - touchRef.current.dragStartY
263
+ const { x: clampedX, y: clampedY } = clampPan(newX, newY, state.scale)
264
+
265
+ setState((prev) => ({ ...prev, x: clampedX, y: clampedY }))
266
+ }, [isDragging, state.scale, clampPan])
267
+
268
+ const handleMouseUp = useCallback(() => {
269
+ // Small delay to prevent click from triggering after drag
270
+ setTimeout(() => setIsDragging(false), 10)
271
+ }, [])
272
+
273
+ // ========== COMMON ACTIONS ==========
274
+
275
+ const reset = useCallback(() => {
276
+ setState(INITIAL_STATE)
277
+ setIsDragging(false)
278
+ onZoomChange?.(false)
279
+ }, [onZoomChange])
280
+
281
+ const zoomTo = useCallback((scale: number, x = 0, y = 0) => {
282
+ const clampedScale = Math.max(minScale, Math.min(maxScale, scale))
283
+ const { x: clampedX, y: clampedY } = clampPan(x, y, clampedScale)
284
+ setState({ scale: clampedScale, x: clampedX, y: clampedY })
285
+ onZoomChange?.(clampedScale > ZOOM_THRESHOLD)
286
+ }, [minScale, maxScale, clampPan, onZoomChange])
287
+
288
+ const toggleZoom = useCallback(() => {
289
+ if (isZoomed) {
290
+ setState(INITIAL_STATE)
291
+ onZoomChange?.(false)
292
+ } else {
293
+ setState({ scale: desktopScale, x: 0, y: 0 })
294
+ onZoomChange?.(true)
295
+ }
296
+ }, [isZoomed, desktopScale, onZoomChange])
297
+
298
+ return {
299
+ state,
300
+ isZoomed,
301
+ isDragging,
302
+ handlers: {
303
+ onTouchStart: handleTouchStart,
304
+ onTouchMove: handleTouchMove,
305
+ onTouchEnd: handleTouchEnd,
306
+ onClick: handleClick,
307
+ onMouseDown: handleMouseDown,
308
+ onMouseMove: handleMouseMove,
309
+ onMouseUp: handleMouseUp,
310
+ onMouseLeave: handleMouseUp,
311
+ },
312
+ reset,
313
+ zoomTo,
314
+ toggleZoom,
315
+ }
316
+ }
@@ -0,0 +1,66 @@
1
+ // Components
2
+ export {
3
+ Gallery,
4
+ GalleryCompact,
5
+ GalleryGrid,
6
+ GalleryImage,
7
+ GalleryVideo,
8
+ GalleryMedia,
9
+ GalleryThumbnails,
10
+ GalleryThumbnailsVirtual,
11
+ GalleryLightbox,
12
+ } from './components'
13
+ export type { GalleryCompactProps, GalleryGridProps, GalleryGridLayout } from './components'
14
+
15
+ // Hooks
16
+ export {
17
+ useGallery,
18
+ useSwipe,
19
+ usePreloadImages,
20
+ preloadImage,
21
+ preloadImages,
22
+ usePinchZoom,
23
+ useVirtualList,
24
+ useImageDimensions,
25
+ clearDimensionsCache,
26
+ getCachedDimensions,
27
+ } from './hooks'
28
+ export type {
29
+ UseGalleryOptions,
30
+ UseSwipeOptions,
31
+ PinchZoomState,
32
+ UsePinchZoomOptions,
33
+ UsePinchZoomReturn,
34
+ VirtualListOptions,
35
+ VirtualListReturn,
36
+ VirtualItem,
37
+ ImageWithDimensions,
38
+ UseImageDimensionsResult,
39
+ UseImageDimensionsOptions,
40
+ } from './hooks'
41
+
42
+ // Utils
43
+ export {
44
+ getOrientation,
45
+ getAspectRatio,
46
+ analyzeImage,
47
+ analyzeImages,
48
+ } from './utils'
49
+ export type {
50
+ ImageOrientation,
51
+ AnalyzedImage,
52
+ } from './utils'
53
+
54
+ // Types
55
+ export type {
56
+ GalleryMediaType,
57
+ GalleryPreviewMode,
58
+ GalleryMediaItem,
59
+ GalleryState,
60
+ GalleryActions,
61
+ GalleryContextValue,
62
+ GalleryProps,
63
+ GalleryImageProps,
64
+ GalleryThumbnailsProps,
65
+ GalleryLightboxProps,
66
+ } from './types'
@@ -0,0 +1,183 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ /**
4
+ * Media type for gallery items
5
+ */
6
+ export type GalleryMediaType = 'image' | 'video'
7
+
8
+ /**
9
+ * Single item in the gallery (image or video)
10
+ */
11
+ export interface GalleryMediaItem {
12
+ /** Unique identifier */
13
+ id: string
14
+ /** Full-size image/poster URL */
15
+ src: string
16
+ /** Thumbnail URL (optional, falls back to src) */
17
+ thumbnail?: string
18
+ /** Alt text for accessibility */
19
+ alt?: string
20
+ /** Image width (optional, for aspect ratio) */
21
+ width?: number
22
+ /** Image height (optional, for aspect ratio) */
23
+ height?: number
24
+ /** Media type (default: 'image') */
25
+ type?: GalleryMediaType
26
+ /** Video URL (required if type is 'video') */
27
+ videoSrc?: string
28
+ /** Video MIME type */
29
+ videoType?: string
30
+ }
31
+
32
+ /**
33
+ * Gallery state
34
+ */
35
+ export interface GalleryState {
36
+ /** Currently selected image index */
37
+ currentIndex: number
38
+ /** Whether lightbox is open */
39
+ isLightboxOpen: boolean
40
+ /** Whether image is zoomed in lightbox */
41
+ isZoomed: boolean
42
+ /** Whether current image is loading */
43
+ isLoading: boolean
44
+ }
45
+
46
+ /**
47
+ * Gallery actions
48
+ */
49
+ export interface GalleryActions {
50
+ /** Go to specific image by index */
51
+ goTo: (index: number) => void
52
+ /** Go to next image */
53
+ next: () => void
54
+ /** Go to previous image */
55
+ prev: () => void
56
+ /** Open lightbox at specific index */
57
+ openLightbox: (index?: number) => void
58
+ /** Close lightbox */
59
+ closeLightbox: () => void
60
+ /** Toggle zoom in lightbox */
61
+ toggleZoom: () => void
62
+ /** Set loading state */
63
+ setLoading: (loading: boolean) => void
64
+ }
65
+
66
+ /**
67
+ * Gallery context value
68
+ */
69
+ export interface GalleryContextValue extends GalleryState, GalleryActions {
70
+ /** All images in gallery */
71
+ images: GalleryMediaItem[]
72
+ /** Current image */
73
+ currentImage: GalleryMediaItem | null
74
+ /** Total number of images */
75
+ total: number
76
+ /** Whether there are multiple images */
77
+ hasMultiple: boolean
78
+ }
79
+
80
+ /**
81
+ * Preview mode for gallery
82
+ */
83
+ export type GalleryPreviewMode = 'carousel' | 'grid'
84
+
85
+ /**
86
+ * Props for Gallery component
87
+ */
88
+ export interface GalleryProps {
89
+ /** Array of images to display */
90
+ images: GalleryMediaItem[]
91
+ /** Initial image index */
92
+ initialIndex?: number
93
+ /** Preview mode: carousel (default) or grid */
94
+ previewMode?: GalleryPreviewMode
95
+ /** Number of images to show in grid preview (default: 5) */
96
+ previewCount?: number
97
+ /** Show thumbnail strip (carousel mode only) */
98
+ showThumbnails?: boolean
99
+ /** Show navigation controls (carousel mode only) */
100
+ showControls?: boolean
101
+ /** Show image counter */
102
+ showCounter?: boolean
103
+ /** Aspect ratio for main image (e.g., 16/9, 4/3) */
104
+ aspectRatio?: number
105
+ /** Enable lightbox on click */
106
+ enableLightbox?: boolean
107
+ /** Enable keyboard navigation */
108
+ enableKeyboard?: boolean
109
+ /** Callback when image changes */
110
+ onImageChange?: (index: number, image: GalleryMediaItem) => void
111
+ /** Callback when lightbox opens */
112
+ onLightboxOpen?: () => void
113
+ /** Callback when lightbox closes */
114
+ onLightboxClose?: () => void
115
+ /** Custom empty state */
116
+ emptyState?: ReactNode
117
+ /** Custom loading placeholder */
118
+ loadingPlaceholder?: ReactNode
119
+ /** Additional CSS class */
120
+ className?: string
121
+ }
122
+
123
+ /**
124
+ * Props for GalleryImage component
125
+ */
126
+ export interface GalleryImageProps {
127
+ /** Image data */
128
+ image: GalleryMediaItem
129
+ /** Whether to show loading skeleton */
130
+ showLoading?: boolean
131
+ /** On image load callback */
132
+ onLoad?: () => void
133
+ /** On image error callback */
134
+ onError?: () => void
135
+ /** On click callback */
136
+ onClick?: () => void
137
+ /** Priority loading */
138
+ priority?: boolean
139
+ /** Additional CSS class */
140
+ className?: string
141
+ }
142
+
143
+ /**
144
+ * Props for GalleryThumbnails component
145
+ */
146
+ export interface GalleryThumbnailsProps {
147
+ /** All images */
148
+ images: GalleryMediaItem[]
149
+ /** Currently selected index */
150
+ currentIndex: number
151
+ /** On thumbnail click */
152
+ onSelect: (index: number) => void
153
+ /** Thumbnail size */
154
+ size?: 'sm' | 'md' | 'lg'
155
+ /** Additional CSS class */
156
+ className?: string
157
+ }
158
+
159
+ /**
160
+ * Props for GalleryLightbox component
161
+ */
162
+ export interface GalleryLightboxProps {
163
+ /** Whether lightbox is open */
164
+ open: boolean
165
+ /** On close callback */
166
+ onClose: () => void
167
+ /** All images */
168
+ images: GalleryMediaItem[]
169
+ /** Current image index */
170
+ currentIndex: number
171
+ /** On index change */
172
+ onIndexChange: (index: number) => void
173
+ /** Show thumbnails in lightbox */
174
+ showThumbnails?: boolean
175
+ /** Enable zoom */
176
+ enableZoom?: boolean
177
+ /** Enable download button */
178
+ enableDownload?: boolean
179
+ /** Enable share button */
180
+ enableShare?: boolean
181
+ /** Title to display */
182
+ title?: string
183
+ }
@@ -0,0 +1,52 @@
1
+ import type { GalleryMediaItem } from '../types'
2
+
3
+ /**
4
+ * Image orientation types
5
+ */
6
+ export type ImageOrientation = 'landscape' | 'portrait' | 'square'
7
+
8
+ /**
9
+ * Analyzed image with orientation info
10
+ */
11
+ export interface AnalyzedImage extends GalleryMediaItem {
12
+ orientation: ImageOrientation
13
+ aspectRatio: number
14
+ }
15
+
16
+ /**
17
+ * Get orientation from width/height
18
+ */
19
+ export function getOrientation(width?: number, height?: number): ImageOrientation {
20
+ if (!width || !height) return 'landscape' // default assumption
21
+
22
+ const ratio = width / height
23
+ if (ratio > 1.2) return 'landscape'
24
+ if (ratio < 0.8) return 'portrait'
25
+ return 'square'
26
+ }
27
+
28
+ /**
29
+ * Calculate aspect ratio from width/height
30
+ */
31
+ export function getAspectRatio(width?: number, height?: number): number {
32
+ if (!width || !height) return 16 / 9 // default
33
+ return width / height
34
+ }
35
+
36
+ /**
37
+ * Analyze single image
38
+ */
39
+ export function analyzeImage(image: GalleryMediaItem): AnalyzedImage {
40
+ return {
41
+ ...image,
42
+ orientation: getOrientation(image.width, image.height),
43
+ aspectRatio: getAspectRatio(image.width, image.height),
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Analyze array of images
49
+ */
50
+ export function analyzeImages(images: GalleryMediaItem[]): AnalyzedImage[] {
51
+ return images.map(analyzeImage)
52
+ }
@@ -0,0 +1,11 @@
1
+ export {
2
+ getOrientation,
3
+ getAspectRatio,
4
+ analyzeImage,
5
+ analyzeImages,
6
+ } from './imageAnalysis'
7
+
8
+ export type {
9
+ ImageOrientation,
10
+ AnalyzedImage,
11
+ } from './imageAnalysis'