@thewhateverapp/tile-sdk 0.13.10 → 0.13.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/react/overlay/Slideshow.d.ts.map +1 -1
- package/dist/react/overlay/Slideshow.js +22 -5
- package/dist/templates/slideshow/PersistentSlideshow.tsx.template.d.ts +1 -1
- package/dist/templates/slideshow/PersistentSlideshow.tsx.template.d.ts.map +1 -1
- package/dist/templates/slideshow/PersistentSlideshow.tsx.template.js +69 -4
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Slideshow.d.ts","sourceRoot":"","sources":["../../../src/react/overlay/Slideshow.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"Slideshow.d.ts","sourceRoot":"","sources":["../../../src/react/overlay/Slideshow.tsx"],"names":[],"mappings":"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"}
|
|
@@ -213,7 +213,8 @@ export function Slideshow({ images, intervalMs = 5000, autoAdvance = true, trans
|
|
|
213
213
|
resume: useCallback(() => singleton.resume(), []),
|
|
214
214
|
toggle: useCallback(() => singleton.toggle(), []),
|
|
215
215
|
};
|
|
216
|
-
// Touch/swipe handlers
|
|
216
|
+
// Touch/swipe handlers with tracking for more reliable detection
|
|
217
|
+
const touchCurrentRef = useRef(null);
|
|
217
218
|
const handleTouchStart = useCallback((e) => {
|
|
218
219
|
if (!swipeable || state.totalSlides <= 1)
|
|
219
220
|
return;
|
|
@@ -223,13 +224,28 @@ export function Slideshow({ images, intervalMs = 5000, autoAdvance = true, trans
|
|
|
223
224
|
y: touch.clientY,
|
|
224
225
|
time: Date.now(),
|
|
225
226
|
};
|
|
227
|
+
touchCurrentRef.current = { x: touch.clientX, y: touch.clientY };
|
|
226
228
|
}, [swipeable, state.totalSlides]);
|
|
227
|
-
const
|
|
228
|
-
if (!swipeable || !touchStartRef.current
|
|
229
|
+
const handleTouchMove = useCallback((e) => {
|
|
230
|
+
if (!swipeable || !touchStartRef.current)
|
|
229
231
|
return;
|
|
230
|
-
const touch = e.
|
|
232
|
+
const touch = e.touches[0];
|
|
233
|
+
touchCurrentRef.current = { x: touch.clientX, y: touch.clientY };
|
|
234
|
+
// Prevent vertical scroll if horizontal swipe is detected
|
|
231
235
|
const deltaX = touch.clientX - touchStartRef.current.x;
|
|
232
236
|
const deltaY = touch.clientY - touchStartRef.current.y;
|
|
237
|
+
if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 10) {
|
|
238
|
+
e.preventDefault();
|
|
239
|
+
}
|
|
240
|
+
}, [swipeable]);
|
|
241
|
+
const handleTouchEnd = useCallback(() => {
|
|
242
|
+
if (!swipeable || !touchStartRef.current || !touchCurrentRef.current || state.isTransitioning) {
|
|
243
|
+
touchStartRef.current = null;
|
|
244
|
+
touchCurrentRef.current = null;
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const deltaX = touchCurrentRef.current.x - touchStartRef.current.x;
|
|
248
|
+
const deltaY = touchCurrentRef.current.y - touchStartRef.current.y;
|
|
233
249
|
const deltaTime = Date.now() - touchStartRef.current.time;
|
|
234
250
|
// Minimum swipe distance (50px) and max time (300ms for quick swipe, or slower with more distance)
|
|
235
251
|
const minSwipeDistance = 50;
|
|
@@ -245,6 +261,7 @@ export function Slideshow({ images, intervalMs = 5000, autoAdvance = true, trans
|
|
|
245
261
|
}
|
|
246
262
|
}
|
|
247
263
|
touchStartRef.current = null;
|
|
264
|
+
touchCurrentRef.current = null;
|
|
248
265
|
}, [swipeable, state.isTransitioning, controls]);
|
|
249
266
|
// Get transition styles
|
|
250
267
|
const getTransitionStyles = (index) => {
|
|
@@ -283,7 +300,7 @@ export function Slideshow({ images, intervalMs = 5000, autoAdvance = true, trans
|
|
|
283
300
|
React.createElement("p", { className: "text-white/60" }, "No images")));
|
|
284
301
|
}
|
|
285
302
|
return (React.createElement(SlideshowContext.Provider, { value: contextValue },
|
|
286
|
-
React.createElement("div", { ref: containerRef, className: `relative w-full h-full bg-black overflow-hidden ${className}`, onTouchStart: handleTouchStart, onTouchEnd: handleTouchEnd },
|
|
303
|
+
React.createElement("div", { ref: containerRef, className: `relative w-full h-full bg-black overflow-hidden ${className}`, style: { touchAction: swipeable && state.totalSlides > 1 ? 'pan-y pinch-zoom' : 'auto' }, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd },
|
|
287
304
|
React.createElement("div", { className: "relative w-full h-full" }, images.map((image, index) => (React.createElement("div", { key: `${image.url}-${index}`, style: getTransitionStyles(index), className: `flex items-center justify-center ${imageClassName}` },
|
|
288
305
|
React.createElement("img", { src: image.url, alt: image.alt || `Slide ${index + 1}`, className: `w-full h-full ${objectFit === 'cover' ? 'object-cover' : 'object-contain'}`, style: { objectPosition: 'center' }, loading: index === 0 ? 'eager' : 'lazy', draggable: false }))))),
|
|
289
306
|
showArrows && state.totalSlides > 1 && (React.createElement(React.Fragment, null,
|
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* React component that attaches to the SlideshowManager singleton.
|
|
5
5
|
* The slideshow state persists across route changes.
|
|
6
6
|
*/
|
|
7
|
-
export declare const persistentSlideshowTemplate = "'use client';\n\nimport { useEffect, useState, createContext, useContext, ReactNode } from 'react';\nimport { SlideshowManager, SlideshowState, SlideImage } from '../lib/SlideshowManager';\n\n// Context to share slideshow state with overlays\ninterface SlideshowContextValue {\n state: SlideshowState;\n controls: {\n next: () => void;\n prev: () => void;\n goTo: (index: number) => void;\n pause: () => void;\n resume: () => void;\n toggle: () => void;\n };\n}\n\nconst SlideshowContext = createContext<SlideshowContextValue | null>(null);\n\nexport function useSlideshow(): SlideshowContextValue {\n const context = useContext(SlideshowContext);\n if (!context) {\n throw new Error('useSlideshow must be used within PersistentSlideshow');\n }\n return context;\n}\n\ninterface PersistentSlideshowProps {\n images: SlideImage[];\n /** Auto-advance interval in milliseconds (default: 5000) */\n intervalMs?: number;\n /** Auto-advance slides (default: true) */\n autoAdvance?: boolean;\n /** Transition type (default: 'fade') */\n transition?: 'fade' | 'slide' | 'none';\n /** Transition duration in ms (default: 500) */\n transitionDuration?: number;\n /** Show navigation dots (default: true) */\n showDots?: boolean;\n /** Show navigation arrows (default: true) */\n showArrows?: boolean;\n /** Additional class names */\n className?: string;\n /** Image container class names */\n imageClassName?: string;\n /** Children rendered as overlay */\n children?: ReactNode;\n}\n\nexport function PersistentSlideshow({\n images,\n intervalMs = 5000,\n autoAdvance = true,\n transition = 'fade',\n transitionDuration = 500,\n showDots = true,\n showArrows = true,\n className = '',\n imageClassName = '',\n children\n}: PersistentSlideshowProps) {\n const [state, setState] = useState<SlideshowState>(SlideshowManager.getState());\n\n useEffect(() => {\n // Initialize slideshow with images\n SlideshowManager.initialize(images, {\n intervalMs,\n transitionDuration,\n autoAdvance,\n });\n\n // Subscribe to state changes\n const unsubscribe = SlideshowManager.onStateChange(setState);\n\n return () => {\n unsubscribe();\n // Don't destroy - let slideshow persist for route changes\n };\n }, [images, intervalMs, transitionDuration, autoAdvance]);\n\n const contextValue: SlideshowContextValue = {\n state,\n controls: {\n next: () => SlideshowManager.next(),\n prev: () => SlideshowManager.prev(),\n goTo: (index) => SlideshowManager.goTo(index),\n pause: () => SlideshowManager.pause(),\n resume: () => SlideshowManager.resume(),\n toggle: () => SlideshowManager.toggle(),\n },\n };\n\n // Get transition styles\n const getTransitionStyles = (index: number): React.CSSProperties => {\n const isActive = index === state.currentIndex;\n\n switch (transition) {\n case 'fade':\n return {\n opacity: isActive ? 1 : 0,\n transition: `opacity ${transitionDuration}ms ease-in-out`,\n position: 'absolute',\n inset: 0,\n };\n case 'slide':\n const offset = (index - state.currentIndex) * 100;\n return {\n transform: `translateX(${offset}%)`,\n transition: `transform ${transitionDuration}ms ease-in-out`,\n position: 'absolute',\n inset: 0,\n };\n case 'none':\n default:\n return {\n opacity: isActive ? 1 : 0,\n position: 'absolute',\n inset: 0,\n };\n }\n };\n\n if (images.length === 0) {\n return (\n <div className={`relative w-full h-full bg-black flex items-center justify-center ${className}`}>\n <p className=\"text-white/60\">No images</p>\n </div>\n );\n }\n\n return (\n <SlideshowContext.Provider value={contextValue}>\n <div
|
|
7
|
+
export declare const persistentSlideshowTemplate = "'use client';\n\nimport { useEffect, useState, useRef, useCallback, createContext, useContext, ReactNode } from 'react';\nimport { SlideshowManager, SlideshowState, SlideImage } from '../lib/SlideshowManager';\n\n// Context to share slideshow state with overlays\ninterface SlideshowContextValue {\n state: SlideshowState;\n controls: {\n next: () => void;\n prev: () => void;\n goTo: (index: number) => void;\n pause: () => void;\n resume: () => void;\n toggle: () => void;\n };\n}\n\nconst SlideshowContext = createContext<SlideshowContextValue | null>(null);\n\nexport function useSlideshow(): SlideshowContextValue {\n const context = useContext(SlideshowContext);\n if (!context) {\n throw new Error('useSlideshow must be used within PersistentSlideshow');\n }\n return context;\n}\n\ninterface PersistentSlideshowProps {\n images: SlideImage[];\n /** Auto-advance interval in milliseconds (default: 5000) */\n intervalMs?: number;\n /** Auto-advance slides (default: true) */\n autoAdvance?: boolean;\n /** Transition type (default: 'fade') */\n transition?: 'fade' | 'slide' | 'none';\n /** Transition duration in ms (default: 500) */\n transitionDuration?: number;\n /** Show navigation dots (default: true) */\n showDots?: boolean;\n /** Show navigation arrows (default: true) */\n showArrows?: boolean;\n /** Enable swipe gestures on touch devices (default: true) */\n swipeable?: boolean;\n /** Additional class names */\n className?: string;\n /** Image container class names */\n imageClassName?: string;\n /** Children rendered as overlay */\n children?: ReactNode;\n}\n\nexport function PersistentSlideshow({\n images,\n intervalMs = 5000,\n autoAdvance = true,\n transition = 'fade',\n transitionDuration = 500,\n showDots = true,\n showArrows = true,\n swipeable = true,\n className = '',\n imageClassName = '',\n children\n}: PersistentSlideshowProps) {\n const [state, setState] = useState<SlideshowState>(SlideshowManager.getState());\n const touchStartRef = useRef<{ x: number; y: number; time: number } | null>(null);\n const touchCurrentRef = useRef<{ x: number; y: number } | null>(null);\n\n // Touch/swipe handlers with tracking for more reliable detection\n const handleTouchStart = useCallback((e: React.TouchEvent) => {\n if (!swipeable || state.totalSlides <= 1) return;\n const touch = e.touches[0];\n touchStartRef.current = {\n x: touch.clientX,\n y: touch.clientY,\n time: Date.now(),\n };\n touchCurrentRef.current = { x: touch.clientX, y: touch.clientY };\n }, [swipeable, state.totalSlides]);\n\n const handleTouchMove = useCallback((e: React.TouchEvent) => {\n if (!swipeable || !touchStartRef.current) return;\n const touch = e.touches[0];\n touchCurrentRef.current = { x: touch.clientX, y: touch.clientY };\n\n // Prevent vertical scroll if horizontal swipe is detected\n const deltaX = touch.clientX - touchStartRef.current.x;\n const deltaY = touch.clientY - touchStartRef.current.y;\n if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 10) {\n e.preventDefault();\n }\n }, [swipeable]);\n\n const handleTouchEnd = useCallback(() => {\n if (!swipeable || !touchStartRef.current || !touchCurrentRef.current || state.isTransitioning) {\n touchStartRef.current = null;\n touchCurrentRef.current = null;\n return;\n }\n\n const deltaX = touchCurrentRef.current.x - touchStartRef.current.x;\n const deltaY = touchCurrentRef.current.y - touchStartRef.current.y;\n const deltaTime = Date.now() - touchStartRef.current.time;\n\n // Minimum swipe distance (50px) and max time (300ms for quick swipe, or slower with more distance)\n const minSwipeDistance = 50;\n const isHorizontalSwipe = Math.abs(deltaX) > Math.abs(deltaY);\n const isValidSwipe = Math.abs(deltaX) > minSwipeDistance && isHorizontalSwipe;\n const isQuickSwipe = deltaTime < 300 || Math.abs(deltaX) > 100;\n\n if (isValidSwipe && isQuickSwipe) {\n if (deltaX > 0) {\n SlideshowManager.prev();\n } else {\n SlideshowManager.next();\n }\n }\n\n touchStartRef.current = null;\n touchCurrentRef.current = null;\n }, [swipeable, state.isTransitioning]);\n\n useEffect(() => {\n // Initialize slideshow with images\n SlideshowManager.initialize(images, {\n intervalMs,\n transitionDuration,\n autoAdvance,\n });\n\n // Subscribe to state changes\n const unsubscribe = SlideshowManager.onStateChange(setState);\n\n return () => {\n unsubscribe();\n // Don't destroy - let slideshow persist for route changes\n };\n }, [images, intervalMs, transitionDuration, autoAdvance]);\n\n const contextValue: SlideshowContextValue = {\n state,\n controls: {\n next: () => SlideshowManager.next(),\n prev: () => SlideshowManager.prev(),\n goTo: (index) => SlideshowManager.goTo(index),\n pause: () => SlideshowManager.pause(),\n resume: () => SlideshowManager.resume(),\n toggle: () => SlideshowManager.toggle(),\n },\n };\n\n // Get transition styles\n const getTransitionStyles = (index: number): React.CSSProperties => {\n const isActive = index === state.currentIndex;\n\n switch (transition) {\n case 'fade':\n return {\n opacity: isActive ? 1 : 0,\n transition: `opacity ${transitionDuration}ms ease-in-out`,\n position: 'absolute',\n inset: 0,\n };\n case 'slide':\n const offset = (index - state.currentIndex) * 100;\n return {\n transform: `translateX(${offset}%)`,\n transition: `transform ${transitionDuration}ms ease-in-out`,\n position: 'absolute',\n inset: 0,\n };\n case 'none':\n default:\n return {\n opacity: isActive ? 1 : 0,\n position: 'absolute',\n inset: 0,\n };\n }\n };\n\n if (images.length === 0) {\n return (\n <div className={`relative w-full h-full bg-black flex items-center justify-center ${className}`}>\n <p className=\"text-white/60\">No images</p>\n </div>\n );\n }\n\n return (\n <SlideshowContext.Provider value={contextValue}>\n <div\n className={`relative w-full h-full bg-black overflow-hidden ${className}`}\n style={{ touchAction: swipeable && state.totalSlides > 1 ? 'pan-y pinch-zoom' : 'auto' }}\n onTouchStart={handleTouchStart}\n onTouchMove={handleTouchMove}\n onTouchEnd={handleTouchEnd}\n >\n {/* Image container */}\n <div className=\"relative w-full h-full\">\n {state.images.map((image, index) => (\n <div\n key={`${image.url}-${index}`}\n style={getTransitionStyles(index)}\n className={imageClassName}\n >\n <img\n src={image.url}\n alt={image.alt || `Slide ${index + 1}`}\n className=\"w-full h-full object-contain\"\n loading={index === 0 ? 'eager' : 'lazy'}\n />\n </div>\n ))}\n </div>\n\n {/* Navigation arrows */}\n {showArrows && state.totalSlides > 1 && (\n <>\n <button\n onClick={() => SlideshowManager.prev()}\n disabled={state.isTransitioning}\n className=\"absolute left-2 top-1/2 -translate-y-1/2 p-2 bg-black/40 hover:bg-black/60 rounded-full text-white transition-colors disabled:opacity-50\"\n aria-label=\"Previous slide\"\n >\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M15 19l-7-7 7-7\" />\n </svg>\n </button>\n <button\n onClick={() => SlideshowManager.next()}\n disabled={state.isTransitioning}\n className=\"absolute right-2 top-1/2 -translate-y-1/2 p-2 bg-black/40 hover:bg-black/60 rounded-full text-white transition-colors disabled:opacity-50\"\n aria-label=\"Next slide\"\n >\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9 5l7 7-7 7\" />\n </svg>\n </button>\n </>\n )}\n\n {/* Navigation dots */}\n {showDots && state.totalSlides > 1 && (\n <div className=\"absolute bottom-3 left-1/2 -translate-x-1/2 flex gap-1.5\">\n {state.images.map((_, index) => (\n <button\n key={index}\n onClick={() => SlideshowManager.goTo(index)}\n disabled={state.isTransitioning}\n className={`w-2 h-2 rounded-full transition-colors ${\n index === state.currentIndex\n ? 'bg-white'\n : 'bg-white/40 hover:bg-white/60'\n }`}\n aria-label={`Go to slide ${index + 1}`}\n />\n ))}\n </div>\n )}\n\n {/* Overlay children - pointer-events managed by individual OverlaySlot components */}\n <div className=\"absolute inset-0 z-10 pointer-events-none\">\n {children}\n </div>\n </div>\n </SlideshowContext.Provider>\n );\n}\n";
|
|
8
8
|
//# sourceMappingURL=PersistentSlideshow.tsx.template.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PersistentSlideshow.tsx.template.d.ts","sourceRoot":"","sources":["../../../src/templates/slideshow/PersistentSlideshow.tsx.template.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,
|
|
1
|
+
{"version":3,"file":"PersistentSlideshow.tsx.template.d.ts","sourceRoot":"","sources":["../../../src/templates/slideshow/PersistentSlideshow.tsx.template.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,s0SA8QvC,CAAC"}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export const persistentSlideshowTemplate = `'use client';
|
|
8
8
|
|
|
9
|
-
import { useEffect, useState, createContext, useContext, ReactNode } from 'react';
|
|
9
|
+
import { useEffect, useState, useRef, useCallback, createContext, useContext, ReactNode } from 'react';
|
|
10
10
|
import { SlideshowManager, SlideshowState, SlideImage } from '../lib/SlideshowManager';
|
|
11
11
|
|
|
12
12
|
// Context to share slideshow state with overlays
|
|
@@ -46,6 +46,8 @@ interface PersistentSlideshowProps {
|
|
|
46
46
|
showDots?: boolean;
|
|
47
47
|
/** Show navigation arrows (default: true) */
|
|
48
48
|
showArrows?: boolean;
|
|
49
|
+
/** Enable swipe gestures on touch devices (default: true) */
|
|
50
|
+
swipeable?: boolean;
|
|
49
51
|
/** Additional class names */
|
|
50
52
|
className?: string;
|
|
51
53
|
/** Image container class names */
|
|
@@ -62,11 +64,68 @@ export function PersistentSlideshow({
|
|
|
62
64
|
transitionDuration = 500,
|
|
63
65
|
showDots = true,
|
|
64
66
|
showArrows = true,
|
|
67
|
+
swipeable = true,
|
|
65
68
|
className = '',
|
|
66
69
|
imageClassName = '',
|
|
67
70
|
children
|
|
68
71
|
}: PersistentSlideshowProps) {
|
|
69
72
|
const [state, setState] = useState<SlideshowState>(SlideshowManager.getState());
|
|
73
|
+
const touchStartRef = useRef<{ x: number; y: number; time: number } | null>(null);
|
|
74
|
+
const touchCurrentRef = useRef<{ x: number; y: number } | null>(null);
|
|
75
|
+
|
|
76
|
+
// Touch/swipe handlers with tracking for more reliable detection
|
|
77
|
+
const handleTouchStart = useCallback((e: React.TouchEvent) => {
|
|
78
|
+
if (!swipeable || state.totalSlides <= 1) return;
|
|
79
|
+
const touch = e.touches[0];
|
|
80
|
+
touchStartRef.current = {
|
|
81
|
+
x: touch.clientX,
|
|
82
|
+
y: touch.clientY,
|
|
83
|
+
time: Date.now(),
|
|
84
|
+
};
|
|
85
|
+
touchCurrentRef.current = { x: touch.clientX, y: touch.clientY };
|
|
86
|
+
}, [swipeable, state.totalSlides]);
|
|
87
|
+
|
|
88
|
+
const handleTouchMove = useCallback((e: React.TouchEvent) => {
|
|
89
|
+
if (!swipeable || !touchStartRef.current) return;
|
|
90
|
+
const touch = e.touches[0];
|
|
91
|
+
touchCurrentRef.current = { x: touch.clientX, y: touch.clientY };
|
|
92
|
+
|
|
93
|
+
// Prevent vertical scroll if horizontal swipe is detected
|
|
94
|
+
const deltaX = touch.clientX - touchStartRef.current.x;
|
|
95
|
+
const deltaY = touch.clientY - touchStartRef.current.y;
|
|
96
|
+
if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 10) {
|
|
97
|
+
e.preventDefault();
|
|
98
|
+
}
|
|
99
|
+
}, [swipeable]);
|
|
100
|
+
|
|
101
|
+
const handleTouchEnd = useCallback(() => {
|
|
102
|
+
if (!swipeable || !touchStartRef.current || !touchCurrentRef.current || state.isTransitioning) {
|
|
103
|
+
touchStartRef.current = null;
|
|
104
|
+
touchCurrentRef.current = null;
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const deltaX = touchCurrentRef.current.x - touchStartRef.current.x;
|
|
109
|
+
const deltaY = touchCurrentRef.current.y - touchStartRef.current.y;
|
|
110
|
+
const deltaTime = Date.now() - touchStartRef.current.time;
|
|
111
|
+
|
|
112
|
+
// Minimum swipe distance (50px) and max time (300ms for quick swipe, or slower with more distance)
|
|
113
|
+
const minSwipeDistance = 50;
|
|
114
|
+
const isHorizontalSwipe = Math.abs(deltaX) > Math.abs(deltaY);
|
|
115
|
+
const isValidSwipe = Math.abs(deltaX) > minSwipeDistance && isHorizontalSwipe;
|
|
116
|
+
const isQuickSwipe = deltaTime < 300 || Math.abs(deltaX) > 100;
|
|
117
|
+
|
|
118
|
+
if (isValidSwipe && isQuickSwipe) {
|
|
119
|
+
if (deltaX > 0) {
|
|
120
|
+
SlideshowManager.prev();
|
|
121
|
+
} else {
|
|
122
|
+
SlideshowManager.next();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
touchStartRef.current = null;
|
|
127
|
+
touchCurrentRef.current = null;
|
|
128
|
+
}, [swipeable, state.isTransitioning]);
|
|
70
129
|
|
|
71
130
|
useEffect(() => {
|
|
72
131
|
// Initialize slideshow with images
|
|
@@ -137,7 +196,13 @@ export function PersistentSlideshow({
|
|
|
137
196
|
|
|
138
197
|
return (
|
|
139
198
|
<SlideshowContext.Provider value={contextValue}>
|
|
140
|
-
<div
|
|
199
|
+
<div
|
|
200
|
+
className={\`relative w-full h-full bg-black overflow-hidden \${className}\`}
|
|
201
|
+
style={{ touchAction: swipeable && state.totalSlides > 1 ? 'pan-y pinch-zoom' : 'auto' }}
|
|
202
|
+
onTouchStart={handleTouchStart}
|
|
203
|
+
onTouchMove={handleTouchMove}
|
|
204
|
+
onTouchEnd={handleTouchEnd}
|
|
205
|
+
>
|
|
141
206
|
{/* Image container */}
|
|
142
207
|
<div className="relative w-full h-full">
|
|
143
208
|
{state.images.map((image, index) => (
|
|
@@ -201,8 +266,8 @@ export function PersistentSlideshow({
|
|
|
201
266
|
</div>
|
|
202
267
|
)}
|
|
203
268
|
|
|
204
|
-
{/* Overlay children */}
|
|
205
|
-
<div className="absolute inset-0 z-10">
|
|
269
|
+
{/* Overlay children - pointer-events managed by individual OverlaySlot components */}
|
|
270
|
+
<div className="absolute inset-0 z-10 pointer-events-none">
|
|
206
271
|
{children}
|
|
207
272
|
</div>
|
|
208
273
|
</div>
|