@xhub-short/ui 0.1.0-beta.8 → 1.0.0-beta.18

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 (75) hide show
  1. package/dist/CommentSheet.css-DuBy01rU.d.ts +219 -0
  2. package/dist/VideoSlotPlayIndicator-DPs8Xt5C.d.ts +51 -0
  3. package/dist/chunk-3OB3OVYR.js +349 -0
  4. package/dist/chunk-4RIMQOBR.js +58 -0
  5. package/dist/chunk-5MKYDI4X.js +1 -0
  6. package/dist/chunk-5Y43HNRG.js +296 -0
  7. package/dist/chunk-7WXAQHJI.js +350 -0
  8. package/dist/chunk-BADA7OLG.js +564 -0
  9. package/dist/chunk-BEJAJFV6.js +191 -0
  10. package/dist/chunk-BNI7CYRI.js +334 -0
  11. package/dist/{chunk-UPCVSGWH.js → chunk-CAWE42LH.js} +5 -2
  12. package/dist/chunk-DGKMO3AE.js +717 -0
  13. package/dist/chunk-DR7KR7OT.js +103 -0
  14. package/dist/chunk-GSNIZ6DF.js +605 -0
  15. package/dist/chunk-HXQPEZRG.js +105 -0
  16. package/dist/chunk-IC2KUU4V.js +1264 -0
  17. package/dist/chunk-IWSBYOSS.js +91 -0
  18. package/dist/chunk-MFJS65C5.js +368 -0
  19. package/dist/{chunk-CXPNPSF7.js → chunk-NJXIYSDZ.js} +12 -1
  20. package/dist/{chunk-NRQXKZO3.js → chunk-OM4L7RE5.js} +12 -2
  21. package/dist/chunk-QCRRF76W.js +75 -0
  22. package/dist/chunk-QUEJHA24.js +508 -0
  23. package/dist/chunk-UYBQTE4M.js +337 -0
  24. package/dist/chunk-VJ744W5N.js +603 -0
  25. package/dist/chunk-VXW7AOGM.js +285 -0
  26. package/dist/{chunk-OQ7P5XC7.js → chunk-YB7AXTX7.js} +1 -1
  27. package/dist/components/ActionBar/index.d.ts +7 -2
  28. package/dist/components/ActionBar/index.js +1 -1
  29. package/dist/components/AdvanceMenu/index.d.ts +80 -0
  30. package/dist/components/AdvanceMenu/index.js +1 -0
  31. package/dist/components/ArticleSlot/index.d.ts +213 -0
  32. package/dist/components/ArticleSlot/index.js +1 -0
  33. package/dist/components/AuthorInfo/index.js +1 -1
  34. package/dist/components/BottomSheet/index.d.ts +87 -0
  35. package/dist/components/BottomSheet/index.js +1 -0
  36. package/dist/components/CleanModeOverlay/index.d.ts +60 -0
  37. package/dist/components/CleanModeOverlay/index.js +1 -0
  38. package/dist/components/CommentSheet/index.d.ts +155 -3
  39. package/dist/components/CommentSheet/index.js +1 -1
  40. package/dist/components/DetailView/index.d.ts +314 -0
  41. package/dist/components/DetailView/index.js +1 -0
  42. package/dist/components/ErrorBoundary/index.js +1 -1
  43. package/dist/components/{VideoFeed → Feed}/index.d.ts +64 -45
  44. package/dist/components/Feed/index.js +1 -0
  45. package/dist/components/ImageCarousel/index.d.ts +50 -0
  46. package/dist/components/ImageCarousel/index.js +1 -0
  47. package/dist/components/Playlist/index.d.ts +117 -0
  48. package/dist/components/Playlist/index.js +1 -0
  49. package/dist/components/ProgressBar/index.js +1 -1
  50. package/dist/components/QualityPicker/index.d.ts +35 -0
  51. package/dist/components/QualityPicker/index.js +1 -0
  52. package/dist/components/ReportSheet/index.d.ts +74 -0
  53. package/dist/components/ReportSheet/index.js +1 -0
  54. package/dist/components/Skeleton/index.js +1 -1
  55. package/dist/components/SpeedPicker/index.d.ts +32 -0
  56. package/dist/components/SpeedPicker/index.js +1 -0
  57. package/dist/components/VideoInfo/index.d.ts +7 -3
  58. package/dist/components/VideoInfo/index.js +1 -1
  59. package/dist/components/VideoPlayer/index.js +1 -1
  60. package/dist/components/VideoSlot/index.d.ts +31 -68
  61. package/dist/components/VideoSlot/index.js +2 -1
  62. package/dist/components/VirtualSlider/index.js +1 -1
  63. package/dist/index.d.ts +74 -11
  64. package/dist/index.js +39 -15
  65. package/package.json +8 -4
  66. package/dist/CommentSheet.css-BD6XbpU2.d.ts +0 -207
  67. package/dist/chunk-EBAMBI3O.js +0 -571
  68. package/dist/chunk-K3CETRCY.js +0 -737
  69. package/dist/chunk-PGHLVKXS.js +0 -148
  70. package/dist/chunk-QF7O26KZ.js +0 -357
  71. package/dist/chunk-RKS7YA7Z.js +0 -562
  72. package/dist/chunk-T4RQWGRY.js +0 -1519
  73. package/dist/chunk-UXNIXHII.js +0 -2040
  74. package/dist/chunk-YWAPI7JO.js +0 -204
  75. package/dist/components/VideoFeed/index.js +0 -1
@@ -0,0 +1,605 @@
1
+ import { clsx2 } from './chunk-EDWS2IPH.js';
2
+ import { injectComponentCSS } from './chunk-CAWE42LH.js';
3
+ import { memo, useInsertionEffect, useState, useRef, useCallback, useEffect, useMemo } from 'react';
4
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
+
6
+ // src/components/ImageCarousel/ImageCarousel.css.ts
7
+ var IMAGE_CAROUSEL_CSS = `.sv-image-carousel{position:relative;width:100%;height:100%;overflow:hidden;touch-action:pan-y pinch-zoom;user-select:none;-webkit-user-select:none;background:var(--sv-carousel-bg,#000)}.sv-image-carousel--dragging{-webkit-user-drag:none}.sv-image-carousel--swiping-horizontal{touch-action:none !important;overscroll-behavior:contain}.sv-image-carousel--swiping-vertical{touch-action:pan-y}.sv-image-carousel-track{display:flex;height:100%;will-change:transform;transition:transform .3s cubic-bezier(.25,.46,.45,.94)}.sv-image-carousel-track--dragging{transition:none}.sv-image-carousel-slide{flex:0 0 100%;width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;position:relative}.sv-image-carousel-image{max-width:100%;max-height:100%;width:100%;height:100%;object-fit:contain;pointer-events:none;transition:transform .2s ease-out}.sv-image-carousel-image--loading{opacity:0}.sv-image-carousel-image--loaded{opacity:1;transition:opacity .3s ease}.sv-image-carousel-image--zoomed{cursor:grab;pointer-events:auto}.sv-image-carousel-image--zoomed:active{cursor:grabbing}.sv-image-carousel-placeholder{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:var(--sv-carousel-placeholder-bg,rgba(255,255,255,.1))}.sv-image-carousel-skeleton{width:60%;height:60%;background:linear-gradient(90deg,rgba(255,255,255,.05)25%,rgba(255,255,255,.1)50%,rgba(255,255,255,.05)75%);background-size:200% 100%;animation:sv-carousel-shimmer 1.5s infinite;border-radius:8px}@keyframes sv-carousel-shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}.sv-image-carousel-indicators{position:absolute;bottom:var(--sv-carousel-indicators-bottom,16px);left:50%;transform:translateX(-50%);display:flex;gap:var(--sv-carousel-indicators-gap,6px);padding:6px 12px;border-radius:20px;z-index:10}.sv-image-carousel-dot{width:var(--sv-carousel-dot-size,6px);height:var(--sv-carousel-dot-size,6px);border-radius:50%;background:var(--sv-carousel-dot-color,rgba(255,255,255,.5));transition:all .2s ease;cursor:pointer;border:0;padding:0}.sv-image-carousel-dot:hover{background:var(--sv-carousel-dot-hover,rgba(255,255,255,.7))}.sv-image-carousel-dot--active{background:var(--sv-carousel-dot-active,#fff);transform:scale(1.2)}.sv-image-carousel-indicators--compact .sv-image-carousel-dot{width:4px;height:4px}.sv-image-carousel-counter{position:absolute;top:var(--sv-carousel-counter-top,16px);right:var(--sv-carousel-counter-right,16px);padding:4px 10px;background:var(--sv-carousel-counter-bg,rgba(0,0,0,.6));border-radius:12px;font-size:var(--sv-carousel-counter-font-size,12px);font-weight:500;color:var(--sv-carousel-counter-color,#fff);z-index:10}.sv-image-carousel-nav{position:absolute;top:50%;transform:translateY(-50%);width:40px;height:40px;display:flex;align-items:center;justify-content:center;background:var(--sv-carousel-nav-bg,rgba(0,0,0,.4));border:0;border-radius:50%;color:var(--sv-carousel-nav-color,#fff);cursor:pointer;z-index:10;opacity:0;transition:opacity .2s ease}.sv-image-carousel:hover .sv-image-carousel-nav{opacity:1}.sv-image-carousel-nav:hover{background:var(--sv-carousel-nav-hover-bg,rgba(0,0,0,.6))}.sv-image-carousel-nav:disabled{opacity:.3;cursor:not-allowed}.sv-image-carousel-nav--prev{left:12px}.sv-image-carousel-nav--next{right:12px}.sv-image-carousel-nav svg{width:20px;height:20px}.sv-image-carousel-zoom-container{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;overflow:hidden;touch-action:none}.sv-image-carousel:focus-visible{outline:2px solid var(--sv-carousel-focus-color,#4dabff);outline-offset:2px}.sv-image-carousel-dot:focus-visible{outline:2px solid var(--sv-carousel-focus-color,#4dabff);outline-offset:2px}.sv-image-carousel-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}`;
8
+ var cssInjected = false;
9
+ function injectImageCarouselCSS() {
10
+ if (cssInjected) return void 0;
11
+ cssInjected = true;
12
+ return injectComponentCSS("image-carousel", IMAGE_CAROUSEL_CSS);
13
+ }
14
+ var DEFAULT_SWIPE_THRESHOLD = 0.2;
15
+ var COMPACT_DOTS_THRESHOLD = 5;
16
+ function ChevronLeftIcon() {
17
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: /* @__PURE__ */ jsx("polyline", { points: "15 18 9 12 15 6" }) });
18
+ }
19
+ function ChevronRightIcon() {
20
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: /* @__PURE__ */ jsx("polyline", { points: "9 6 15 12 9 18" }) });
21
+ }
22
+ function CarouselSlide({
23
+ url,
24
+ index,
25
+ shouldLoad,
26
+ isActive,
27
+ isZoomed,
28
+ zoomScale,
29
+ zoomTranslate,
30
+ renderImage,
31
+ renderPlaceholder
32
+ }) {
33
+ const [loaded, setLoaded] = useState(false);
34
+ const imageStyle = isActive && isZoomed ? {
35
+ transform: `scale(${zoomScale}) translate(${zoomTranslate.x}px, ${zoomTranslate.y}px)`
36
+ } : void 0;
37
+ return /* @__PURE__ */ jsx("div", { className: "sv-image-carousel-slide", "aria-hidden": !isActive, children: shouldLoad ? /* @__PURE__ */ jsxs(Fragment, { children: [
38
+ !loaded && /* @__PURE__ */ jsx("div", { className: "sv-image-carousel-placeholder", children: renderPlaceholder?.(index) ?? /* @__PURE__ */ jsx("div", { className: "sv-image-carousel-skeleton" }) }),
39
+ renderImage ? renderImage(url, index, loaded) : /* @__PURE__ */ jsx(
40
+ "img",
41
+ {
42
+ src: url,
43
+ alt: "",
44
+ className: clsx2(
45
+ "sv-image-carousel-image",
46
+ loaded ? "sv-image-carousel-image--loaded" : "sv-image-carousel-image--loading",
47
+ isActive && isZoomed && "sv-image-carousel-image--zoomed"
48
+ ),
49
+ style: imageStyle,
50
+ onLoad: () => setLoaded(true),
51
+ draggable: false
52
+ }
53
+ )
54
+ ] }) : /* @__PURE__ */ jsx("div", { className: "sv-image-carousel-placeholder", children: renderPlaceholder?.(index) ?? /* @__PURE__ */ jsx("div", { className: "sv-image-carousel-skeleton" }) }) });
55
+ }
56
+ function ImageCarouselHeadlessBase({
57
+ images,
58
+ currentIndex: controlledIndex,
59
+ onIndexChange,
60
+ showIndicators = true,
61
+ indicatorStyle = "dots",
62
+ showArrows = false,
63
+ enableZoom = false,
64
+ maxZoomScale = 3,
65
+ lazyLoad = true,
66
+ renderImage,
67
+ renderPlaceholder,
68
+ swipeThreshold = DEFAULT_SWIPE_THRESHOLD,
69
+ onZoomChange,
70
+ onSwipeDirectionChange,
71
+ className
72
+ }) {
73
+ useInsertionEffect(() => {
74
+ return injectImageCarouselCSS();
75
+ }, []);
76
+ const [internalIndex, setInternalIndex] = useState(0);
77
+ const activeIndex = controlledIndex ?? internalIndex;
78
+ const [loadedImages, setLoadedImages] = useState(() => /* @__PURE__ */ new Set([0]));
79
+ const [isDragging, setIsDragging] = useState(false);
80
+ const [dragOffset, setDragOffset] = useState(0);
81
+ const [swipeDirection, setSwipeDirection] = useState(null);
82
+ const [zoomScale, setZoomScale] = useState(1);
83
+ const [zoomTranslate, setZoomTranslate] = useState({ x: 0, y: 0 });
84
+ const isZoomed = zoomScale > 1;
85
+ const containerRef = useRef(null);
86
+ const dragStartRef = useRef(null);
87
+ const touchesRef = useRef(null);
88
+ const wasHorizontalSwipingRef = useRef(false);
89
+ const totalImages = images.length;
90
+ const canGoPrev = activeIndex > 0;
91
+ const canGoNext = activeIndex < totalImages - 1;
92
+ const useCompactDots = totalImages > COMPACT_DOTS_THRESHOLD;
93
+ const goToIndex = useCallback(
94
+ (index) => {
95
+ const clampedIndex = Math.max(0, Math.min(totalImages - 1, index));
96
+ if (clampedIndex !== activeIndex) {
97
+ setInternalIndex(clampedIndex);
98
+ onIndexChange?.(clampedIndex);
99
+ if (isZoomed) {
100
+ setZoomScale(1);
101
+ setZoomTranslate({ x: 0, y: 0 });
102
+ onZoomChange?.(false);
103
+ }
104
+ }
105
+ },
106
+ [activeIndex, totalImages, onIndexChange, isZoomed, onZoomChange]
107
+ );
108
+ const goToPrev = useCallback(() => {
109
+ if (canGoPrev) goToIndex(activeIndex - 1);
110
+ }, [canGoPrev, activeIndex, goToIndex]);
111
+ const goToNext = useCallback(() => {
112
+ if (canGoNext) goToIndex(activeIndex + 1);
113
+ }, [canGoNext, activeIndex, goToIndex]);
114
+ useEffect(() => {
115
+ if (!lazyLoad) return;
116
+ const toLoad = /* @__PURE__ */ new Set();
117
+ toLoad.add(activeIndex);
118
+ if (activeIndex > 0) toLoad.add(activeIndex - 1);
119
+ if (activeIndex < totalImages - 1) toLoad.add(activeIndex + 1);
120
+ setLoadedImages((prev) => {
121
+ const next = new Set(prev);
122
+ for (const idx of toLoad) {
123
+ next.add(idx);
124
+ }
125
+ return next;
126
+ });
127
+ }, [activeIndex, totalImages, lazyLoad]);
128
+ useEffect(() => {
129
+ wasHorizontalSwipingRef.current = swipeDirection === "horizontal";
130
+ }, [swipeDirection]);
131
+ const swipeDirectionRef = useRef(null);
132
+ useEffect(() => {
133
+ const container = containerRef.current;
134
+ if (!container) return;
135
+ const handleSwipeDirectionDetection = (dx, dy) => {
136
+ const adx = Math.abs(dx);
137
+ const ady = Math.abs(dy);
138
+ const lockRatio = 1.3;
139
+ if (adx > ady * lockRatio) {
140
+ swipeDirectionRef.current = "horizontal";
141
+ setSwipeDirection("horizontal");
142
+ setIsDragging(true);
143
+ onSwipeDirectionChange?.("horizontal");
144
+ wasHorizontalSwipingRef.current = true;
145
+ } else if (ady > adx * lockRatio) {
146
+ swipeDirectionRef.current = "vertical";
147
+ setSwipeDirection("vertical");
148
+ setIsDragging(false);
149
+ dragStartRef.current = null;
150
+ }
151
+ };
152
+ const shouldHandleNativeTouch = (e, dragStart) => {
153
+ return e.touches.length === 1 && dragStart && swipeDirectionRef.current !== "vertical";
154
+ };
155
+ const processNativeTouch = (e, dx, dy) => {
156
+ if (swipeDirectionRef.current === null && (Math.abs(dx) >= 5 || Math.abs(dy) >= 5)) {
157
+ handleSwipeDirectionDetection(dx, dy);
158
+ }
159
+ if (swipeDirectionRef.current === "horizontal") {
160
+ setDragOffset(dx);
161
+ if (e.cancelable) e.preventDefault();
162
+ }
163
+ };
164
+ const handleNativeTouchMove = (e) => {
165
+ const dragStart = dragStartRef.current;
166
+ const isHorizontal = swipeDirectionRef.current === "horizontal";
167
+ if (!shouldHandleNativeTouch(e, dragStart)) {
168
+ if (isHorizontal && e.cancelable) e.preventDefault();
169
+ return;
170
+ }
171
+ const touch = e.touches[0];
172
+ if (!touch || !dragStart) return;
173
+ const dx = touch.clientX - dragStart.x;
174
+ const dy = touch.clientY - dragStart.y;
175
+ processNativeTouch(e, dx, dy);
176
+ };
177
+ const handleNativeTouchEnd = (e) => {
178
+ if (swipeDirectionRef.current === "horizontal") {
179
+ if (e.cancelable) {
180
+ e.preventDefault();
181
+ }
182
+ }
183
+ };
184
+ container.addEventListener("touchmove", handleNativeTouchMove, { passive: false });
185
+ container.addEventListener("touchend", handleNativeTouchEnd, { passive: false });
186
+ container.addEventListener("touchcancel", handleNativeTouchEnd, { passive: false });
187
+ return () => {
188
+ container.removeEventListener("touchmove", handleNativeTouchMove);
189
+ container.removeEventListener("touchend", handleNativeTouchEnd);
190
+ container.removeEventListener("touchcancel", handleNativeTouchEnd);
191
+ };
192
+ }, [onSwipeDirectionChange]);
193
+ const handlePointerDown = useCallback(
194
+ (e) => {
195
+ if (isZoomed) return;
196
+ dragStartRef.current = {
197
+ x: e.clientX,
198
+ y: e.clientY,
199
+ time: Date.now()
200
+ };
201
+ setIsDragging(true);
202
+ setSwipeDirection(null);
203
+ swipeDirectionRef.current = null;
204
+ onSwipeDirectionChange?.(null);
205
+ wasHorizontalSwipingRef.current = false;
206
+ e.currentTarget.setPointerCapture(e.pointerId);
207
+ },
208
+ [isZoomed, onSwipeDirectionChange]
209
+ );
210
+ const detectSwipeDirection = useCallback(
211
+ (dx, dy) => {
212
+ const LOCK_THRESHOLD = 5;
213
+ const adx = Math.abs(dx);
214
+ const ady = Math.abs(dy);
215
+ if (adx < LOCK_THRESHOLD && ady < LOCK_THRESHOLD) {
216
+ return "undecided";
217
+ }
218
+ const lockRatio = 1.3;
219
+ if (adx > ady * lockRatio) {
220
+ return "horizontal";
221
+ }
222
+ if (ady > adx * lockRatio) {
223
+ return "vertical";
224
+ }
225
+ return "undecided";
226
+ },
227
+ []
228
+ );
229
+ const releasePointer = useCallback((e) => {
230
+ if (e.currentTarget.hasPointerCapture(e.pointerId)) {
231
+ e.currentTarget.releasePointerCapture(e.pointerId);
232
+ }
233
+ }, []);
234
+ const cancelDragGesture = useCallback(
235
+ (e) => {
236
+ releasePointer(e);
237
+ setIsDragging(false);
238
+ setDragOffset(0);
239
+ setSwipeDirection(null);
240
+ swipeDirectionRef.current = null;
241
+ wasHorizontalSwipingRef.current = false;
242
+ dragStartRef.current = null;
243
+ },
244
+ [releasePointer]
245
+ );
246
+ const processHorizontalSwipe = useCallback((_e, dx) => {
247
+ setDragOffset(dx);
248
+ }, []);
249
+ const handlePointerMove = useCallback(
250
+ (e) => {
251
+ if (!isDragging || !dragStartRef.current) {
252
+ return;
253
+ }
254
+ const dx = e.clientX - dragStartRef.current.x;
255
+ const dy = e.clientY - dragStartRef.current.y;
256
+ if (swipeDirection === "vertical") return;
257
+ const detectedDirection = detectSwipeDirection(dx, dy);
258
+ if (detectedDirection === "undecided") return;
259
+ if (detectedDirection === "vertical") {
260
+ setSwipeDirection("vertical");
261
+ swipeDirectionRef.current = "vertical";
262
+ cancelDragGesture(e);
263
+ return;
264
+ }
265
+ if (detectedDirection === "horizontal") {
266
+ if (swipeDirection !== "horizontal") {
267
+ setSwipeDirection("horizontal");
268
+ swipeDirectionRef.current = "horizontal";
269
+ onSwipeDirectionChange?.("horizontal");
270
+ wasHorizontalSwipingRef.current = true;
271
+ }
272
+ processHorizontalSwipe(e, dx);
273
+ }
274
+ },
275
+ [
276
+ isDragging,
277
+ swipeDirection,
278
+ detectSwipeDirection,
279
+ cancelDragGesture,
280
+ processHorizontalSwipe,
281
+ onSwipeDirectionChange
282
+ ]
283
+ );
284
+ const resetDragState = useCallback(() => {
285
+ setIsDragging(false);
286
+ setDragOffset(0);
287
+ setSwipeDirection(null);
288
+ swipeDirectionRef.current = null;
289
+ dragStartRef.current = null;
290
+ }, []);
291
+ const executeSwipeNavigation = useCallback(
292
+ (offset, velocity, threshold) => {
293
+ const shouldSwipe = Math.abs(offset) > threshold || Math.abs(velocity) > 0.5;
294
+ if (!shouldSwipe) return;
295
+ if (offset > 0 && canGoPrev) {
296
+ goToPrev();
297
+ } else if (offset < 0 && canGoNext) {
298
+ goToNext();
299
+ }
300
+ },
301
+ [canGoPrev, canGoNext, goToPrev, goToNext]
302
+ );
303
+ const handlePointerUp = useCallback(
304
+ (e) => {
305
+ if (!isDragging || !dragStartRef.current) return;
306
+ releasePointer(e);
307
+ const container = containerRef.current;
308
+ if (!container) {
309
+ resetDragState();
310
+ onSwipeDirectionChange?.(null);
311
+ return;
312
+ }
313
+ const containerWidth = container.offsetWidth;
314
+ const threshold = containerWidth * swipeThreshold;
315
+ const velocity = dragOffset / (Date.now() - dragStartRef.current.time);
316
+ executeSwipeNavigation(dragOffset, velocity, threshold);
317
+ const wasHorizontal = swipeDirection === "horizontal";
318
+ setIsDragging(false);
319
+ setDragOffset(0);
320
+ dragStartRef.current = null;
321
+ if (wasHorizontal) {
322
+ setTimeout(() => {
323
+ setSwipeDirection(null);
324
+ swipeDirectionRef.current = null;
325
+ onSwipeDirectionChange?.(null);
326
+ wasHorizontalSwipingRef.current = false;
327
+ }, 300);
328
+ } else {
329
+ setSwipeDirection(null);
330
+ swipeDirectionRef.current = null;
331
+ onSwipeDirectionChange?.(null);
332
+ }
333
+ },
334
+ [
335
+ isDragging,
336
+ dragOffset,
337
+ swipeThreshold,
338
+ executeSwipeNavigation,
339
+ releasePointer,
340
+ swipeDirection,
341
+ resetDragState,
342
+ onSwipeDirectionChange
343
+ ]
344
+ );
345
+ const handlePointerCancel = useCallback(
346
+ (e) => {
347
+ if (!isDragging) return;
348
+ releasePointer(e);
349
+ },
350
+ [isDragging, releasePointer]
351
+ );
352
+ const handleSingleTouchStart = useCallback(
353
+ (touch) => {
354
+ if (isDragging || isZoomed) return;
355
+ dragStartRef.current = {
356
+ x: touch.clientX,
357
+ y: touch.clientY,
358
+ time: Date.now()
359
+ };
360
+ setIsDragging(true);
361
+ setSwipeDirection(null);
362
+ swipeDirectionRef.current = null;
363
+ onSwipeDirectionChange?.(null);
364
+ wasHorizontalSwipingRef.current = false;
365
+ },
366
+ [isDragging, isZoomed, onSwipeDirectionChange]
367
+ );
368
+ const handleMultiTouchStart = useCallback(
369
+ (e) => {
370
+ if (!enableZoom || e.touches.length !== 2) return;
371
+ const touch1 = e.touches[0];
372
+ const touch2 = e.touches[1];
373
+ if (!touch1 || !touch2) return;
374
+ const distance = Math.hypot(touch2.clientX - touch1.clientX, touch2.clientY - touch1.clientY);
375
+ const center = {
376
+ x: (touch1.clientX + touch2.clientX) / 2,
377
+ y: (touch1.clientY + touch2.clientY) / 2
378
+ };
379
+ touchesRef.current = { distance, center };
380
+ if (isDragging) {
381
+ resetDragState();
382
+ }
383
+ },
384
+ [enableZoom, isDragging, resetDragState]
385
+ );
386
+ const handleTouchStart = useCallback(
387
+ (e) => {
388
+ if (e.touches.length === 1) {
389
+ const touch = e.touches[0];
390
+ if (touch) handleSingleTouchStart(touch);
391
+ } else if (e.touches.length === 2) {
392
+ handleMultiTouchStart(e);
393
+ }
394
+ },
395
+ [handleSingleTouchStart, handleMultiTouchStart]
396
+ );
397
+ const handleMultiTouchMove = useCallback(
398
+ (e) => {
399
+ if (!enableZoom || !touchesRef.current || e.touches.length !== 2) return;
400
+ const touch1 = e.touches[0];
401
+ const touch2 = e.touches[1];
402
+ if (!touch1 || !touch2) return;
403
+ e.preventDefault();
404
+ const newDistance = Math.hypot(
405
+ touch2.clientX - touch1.clientX,
406
+ touch2.clientY - touch1.clientY
407
+ );
408
+ const scale = newDistance / (touchesRef.current.distance || 1);
409
+ const newScale = Math.max(1, Math.min(maxZoomScale, zoomScale * scale));
410
+ setZoomScale(newScale);
411
+ if (newScale > 1) {
412
+ onZoomChange?.(true);
413
+ if (newScale > 1.1 && !wasHorizontalSwipingRef.current) {
414
+ onSwipeDirectionChange?.("horizontal");
415
+ }
416
+ }
417
+ touchesRef.current.distance = newDistance;
418
+ },
419
+ [enableZoom, maxZoomScale, zoomScale, onZoomChange, onSwipeDirectionChange]
420
+ );
421
+ const handleTouchMove = useCallback(
422
+ (e) => {
423
+ if (e.touches.length === 2) {
424
+ handleMultiTouchMove(e);
425
+ }
426
+ },
427
+ [handleMultiTouchMove]
428
+ );
429
+ const handleTouchEnd = useCallback(
430
+ (e) => {
431
+ if (swipeDirection === "horizontal" || wasHorizontalSwipingRef.current) {
432
+ e.preventDefault();
433
+ }
434
+ touchesRef.current = null;
435
+ if (zoomScale < 1.1) {
436
+ setZoomScale(1);
437
+ setZoomTranslate({ x: 0, y: 0 });
438
+ onZoomChange?.(false);
439
+ onSwipeDirectionChange?.(null);
440
+ }
441
+ },
442
+ [zoomScale, onZoomChange, swipeDirection, onSwipeDirectionChange]
443
+ );
444
+ const handleTouchCancel = useCallback(
445
+ (e) => {
446
+ if (swipeDirection === "horizontal" || wasHorizontalSwipingRef.current) {
447
+ e.preventDefault();
448
+ }
449
+ touchesRef.current = null;
450
+ },
451
+ [swipeDirection]
452
+ );
453
+ const handleKeyDown = useCallback(
454
+ (e) => {
455
+ if (isZoomed) {
456
+ if (e.key === "Escape") {
457
+ setZoomScale(1);
458
+ setZoomTranslate({ x: 0, y: 0 });
459
+ onZoomChange?.(false);
460
+ }
461
+ return;
462
+ }
463
+ switch (e.key) {
464
+ case "ArrowLeft":
465
+ e.preventDefault();
466
+ goToPrev();
467
+ break;
468
+ case "ArrowRight":
469
+ e.preventDefault();
470
+ goToNext();
471
+ break;
472
+ case "Home":
473
+ e.preventDefault();
474
+ goToIndex(0);
475
+ break;
476
+ case "End":
477
+ e.preventDefault();
478
+ goToIndex(totalImages - 1);
479
+ break;
480
+ }
481
+ },
482
+ [isZoomed, goToPrev, goToNext, goToIndex, totalImages, onZoomChange]
483
+ );
484
+ const trackTransform = useMemo(() => {
485
+ const baseOffset = -activeIndex * 100;
486
+ const dragPercent = containerRef.current ? dragOffset / containerRef.current.offsetWidth * 100 : 0;
487
+ return `translateX(${baseOffset + dragPercent}%)`;
488
+ }, [activeIndex, dragOffset]);
489
+ if (images.length === 0) {
490
+ return /* @__PURE__ */ jsx("div", { className: clsx2("sv-image-carousel", className) });
491
+ }
492
+ return /* @__PURE__ */ jsxs(
493
+ "div",
494
+ {
495
+ ref: containerRef,
496
+ className: clsx2(
497
+ "sv-image-carousel",
498
+ isDragging && "sv-image-carousel--dragging",
499
+ swipeDirection === "horizontal" && "sv-image-carousel--swiping-horizontal",
500
+ swipeDirection === "vertical" && "sv-image-carousel--swiping-vertical",
501
+ className
502
+ ),
503
+ onPointerDown: handlePointerDown,
504
+ onPointerMove: handlePointerMove,
505
+ onPointerUp: handlePointerUp,
506
+ onPointerCancel: handlePointerCancel,
507
+ onPointerLeave: handlePointerCancel,
508
+ onTouchStart: handleTouchStart,
509
+ onTouchMove: handleTouchMove,
510
+ onTouchEnd: handleTouchEnd,
511
+ onTouchCancel: handleTouchCancel,
512
+ onKeyDown: handleKeyDown,
513
+ "aria-roledescription": "carousel",
514
+ "aria-label": `Image carousel, ${activeIndex + 1} of ${totalImages}`,
515
+ children: [
516
+ /* @__PURE__ */ jsxs("div", { className: "sv-image-carousel-sr-only", "aria-live": "polite", children: [
517
+ "Image ",
518
+ activeIndex + 1,
519
+ " of ",
520
+ totalImages
521
+ ] }),
522
+ /* @__PURE__ */ jsx(
523
+ "div",
524
+ {
525
+ className: clsx2("sv-image-carousel-track", isDragging && "sv-image-carousel-track--dragging"),
526
+ style: { transform: trackTransform },
527
+ children: images.map((url, index) => /* @__PURE__ */ jsx(
528
+ CarouselSlide,
529
+ {
530
+ url,
531
+ index,
532
+ shouldLoad: !lazyLoad || loadedImages.has(index),
533
+ isActive: index === activeIndex,
534
+ isZoomed,
535
+ zoomScale,
536
+ zoomTranslate,
537
+ renderImage,
538
+ renderPlaceholder
539
+ },
540
+ url
541
+ ))
542
+ }
543
+ ),
544
+ showIndicators && totalImages > 1 && (indicatorStyle === "dots" ? /* @__PURE__ */ jsx(
545
+ "div",
546
+ {
547
+ className: clsx2(
548
+ "sv-image-carousel-indicators",
549
+ useCompactDots && "sv-image-carousel-indicators--compact"
550
+ ),
551
+ role: "tablist",
552
+ "aria-label": "Image pagination",
553
+ children: images.map((_, index) => /* @__PURE__ */ jsx(
554
+ "button",
555
+ {
556
+ type: "button",
557
+ className: clsx2(
558
+ "sv-image-carousel-dot",
559
+ index === activeIndex && "sv-image-carousel-dot--active"
560
+ ),
561
+ onClick: () => goToIndex(index),
562
+ role: "tab",
563
+ "aria-selected": index === activeIndex,
564
+ "aria-label": `Go to image ${index + 1}`
565
+ },
566
+ _
567
+ ))
568
+ }
569
+ ) : /* @__PURE__ */ jsxs("div", { className: "sv-image-carousel-counter", children: [
570
+ activeIndex + 1,
571
+ "/",
572
+ totalImages
573
+ ] })),
574
+ showArrows && totalImages > 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
575
+ /* @__PURE__ */ jsx(
576
+ "button",
577
+ {
578
+ type: "button",
579
+ className: "sv-image-carousel-nav sv-image-carousel-nav--prev",
580
+ onClick: goToPrev,
581
+ disabled: !canGoPrev,
582
+ "aria-label": "Previous image",
583
+ children: /* @__PURE__ */ jsx(ChevronLeftIcon, {})
584
+ }
585
+ ),
586
+ /* @__PURE__ */ jsx(
587
+ "button",
588
+ {
589
+ type: "button",
590
+ className: "sv-image-carousel-nav sv-image-carousel-nav--next",
591
+ onClick: goToNext,
592
+ disabled: !canGoNext,
593
+ "aria-label": "Next image",
594
+ children: /* @__PURE__ */ jsx(ChevronRightIcon, {})
595
+ }
596
+ )
597
+ ] })
598
+ ]
599
+ }
600
+ );
601
+ }
602
+ var ImageCarouselHeadless = memo(ImageCarouselHeadlessBase);
603
+ ImageCarouselHeadless.displayName = "ImageCarouselHeadless";
604
+
605
+ export { IMAGE_CAROUSEL_CSS, ImageCarouselHeadless };