@thewhateverapp/tile-sdk 0.13.12 → 0.13.13

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.
@@ -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,56 +213,68 @@ 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 with tracking for more reliable detection
216
+ // Touch/swipe handlers using native events (React touch events are passive)
217
217
  const touchCurrentRef = useRef(null);
218
- const handleTouchStart = useCallback((e) => {
219
- if (!swipeable || state.totalSlides <= 1)
218
+ useEffect(() => {
219
+ const container = containerRef.current;
220
+ if (!container || !swipeable || state.totalSlides <= 1)
220
221
  return;
221
- const touch = e.touches[0];
222
- touchStartRef.current = {
223
- x: touch.clientX,
224
- y: touch.clientY,
225
- time: Date.now(),
222
+ const handleTouchStart = (e) => {
223
+ const touch = e.touches[0];
224
+ touchStartRef.current = {
225
+ x: touch.clientX,
226
+ y: touch.clientY,
227
+ time: Date.now(),
228
+ };
229
+ touchCurrentRef.current = { x: touch.clientX, y: touch.clientY };
226
230
  };
227
- touchCurrentRef.current = { x: touch.clientX, y: touch.clientY };
228
- }, [swipeable, state.totalSlides]);
229
- const handleTouchMove = useCallback((e) => {
230
- if (!swipeable || !touchStartRef.current)
231
- return;
232
- const touch = e.touches[0];
233
- touchCurrentRef.current = { x: touch.clientX, y: touch.clientY };
234
- // Prevent vertical scroll if horizontal swipe is detected
235
- const deltaX = touch.clientX - touchStartRef.current.x;
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;
249
- const deltaTime = Date.now() - touchStartRef.current.time;
250
- // Minimum swipe distance (50px) and max time (300ms for quick swipe, or slower with more distance)
251
- const minSwipeDistance = 50;
252
- const isHorizontalSwipe = Math.abs(deltaX) > Math.abs(deltaY);
253
- const isValidSwipe = Math.abs(deltaX) > minSwipeDistance && isHorizontalSwipe;
254
- const isQuickSwipe = deltaTime < 300 || Math.abs(deltaX) > 100;
255
- if (isValidSwipe && isQuickSwipe) {
256
- if (deltaX > 0) {
257
- controls.prev();
231
+ const handleTouchMove = (e) => {
232
+ if (!touchStartRef.current)
233
+ return;
234
+ const touch = e.touches[0];
235
+ touchCurrentRef.current = { x: touch.clientX, y: touch.clientY };
236
+ // Prevent vertical scroll if horizontal swipe is detected
237
+ const deltaX = touch.clientX - touchStartRef.current.x;
238
+ const deltaY = touch.clientY - touchStartRef.current.y;
239
+ if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 10) {
240
+ e.preventDefault();
258
241
  }
259
- else {
260
- controls.next();
242
+ };
243
+ const handleTouchEnd = () => {
244
+ if (!touchStartRef.current || !touchCurrentRef.current || state.isTransitioning) {
245
+ touchStartRef.current = null;
246
+ touchCurrentRef.current = null;
247
+ return;
261
248
  }
262
- }
263
- touchStartRef.current = null;
264
- touchCurrentRef.current = null;
265
- }, [swipeable, state.isTransitioning, controls]);
249
+ const deltaX = touchCurrentRef.current.x - touchStartRef.current.x;
250
+ const deltaY = touchCurrentRef.current.y - touchStartRef.current.y;
251
+ const deltaTime = Date.now() - touchStartRef.current.time;
252
+ // Minimum swipe distance (50px) and max time (300ms for quick swipe, or slower with more distance)
253
+ const minSwipeDistance = 50;
254
+ const isHorizontalSwipe = Math.abs(deltaX) > Math.abs(deltaY);
255
+ const isValidSwipe = Math.abs(deltaX) > minSwipeDistance && isHorizontalSwipe;
256
+ const isQuickSwipe = deltaTime < 300 || Math.abs(deltaX) > 100;
257
+ if (isValidSwipe && isQuickSwipe) {
258
+ if (deltaX > 0) {
259
+ singleton.prev();
260
+ }
261
+ else {
262
+ singleton.next();
263
+ }
264
+ }
265
+ touchStartRef.current = null;
266
+ touchCurrentRef.current = null;
267
+ };
268
+ // Use passive: false to allow preventDefault() on touchmove
269
+ container.addEventListener('touchstart', handleTouchStart, { passive: true });
270
+ container.addEventListener('touchmove', handleTouchMove, { passive: false });
271
+ container.addEventListener('touchend', handleTouchEnd, { passive: true });
272
+ return () => {
273
+ container.removeEventListener('touchstart', handleTouchStart);
274
+ container.removeEventListener('touchmove', handleTouchMove);
275
+ container.removeEventListener('touchend', handleTouchEnd);
276
+ };
277
+ }, [swipeable, state.totalSlides, state.isTransitioning, singleton]);
266
278
  // Get transition styles
267
279
  const getTransitionStyles = (index) => {
268
280
  const isActive = index === state.currentIndex;
@@ -300,7 +312,7 @@ export function Slideshow({ images, intervalMs = 5000, autoAdvance = true, trans
300
312
  React.createElement("p", { className: "text-white/60" }, "No images")));
301
313
  }
302
314
  return (React.createElement(SlideshowContext.Provider, { value: contextValue },
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 },
315
+ 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' } },
304
316
  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}` },
305
317
  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 }))))),
306
318
  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, 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";
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 containerRef = useRef<HTMLDivElement>(null);\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 using native events (React touch events are passive)\n useEffect(() => {\n const container = containerRef.current;\n if (!container || !swipeable || state.totalSlides <= 1) return;\n\n const handleTouchStart = (e: TouchEvent) => {\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 };\n\n const handleTouchMove = (e: TouchEvent) => {\n if (!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 };\n\n const handleTouchEnd = () => {\n if (!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 };\n\n // Use passive: false to allow preventDefault() on touchmove\n container.addEventListener('touchstart', handleTouchStart, { passive: true });\n container.addEventListener('touchmove', handleTouchMove, { passive: false });\n container.addEventListener('touchend', handleTouchEnd, { passive: true });\n\n return () => {\n container.removeEventListener('touchstart', handleTouchStart);\n container.removeEventListener('touchmove', handleTouchMove);\n container.removeEventListener('touchend', handleTouchEnd);\n };\n }, [swipeable, state.totalSlides, 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 ref={containerRef}\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 >\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,s0SA8QvC,CAAC"}
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,25TA4RvC,CAAC"}
@@ -70,62 +70,78 @@ export function PersistentSlideshow({
70
70
  children
71
71
  }: PersistentSlideshowProps) {
72
72
  const [state, setState] = useState<SlideshowState>(SlideshowManager.getState());
73
+ const containerRef = useRef<HTMLDivElement>(null);
73
74
  const touchStartRef = useRef<{ x: number; y: number; time: number } | null>(null);
74
75
  const touchCurrentRef = useRef<{ x: number; y: number } | null>(null);
75
76
 
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(),
77
+ // Touch/swipe handlers using native events (React touch events are passive)
78
+ useEffect(() => {
79
+ const container = containerRef.current;
80
+ if (!container || !swipeable || state.totalSlides <= 1) return;
81
+
82
+ const handleTouchStart = (e: TouchEvent) => {
83
+ const touch = e.touches[0];
84
+ touchStartRef.current = {
85
+ x: touch.clientX,
86
+ y: touch.clientY,
87
+ time: Date.now(),
88
+ };
89
+ touchCurrentRef.current = { x: touch.clientX, y: touch.clientY };
84
90
  };
85
- touchCurrentRef.current = { x: touch.clientX, y: touch.clientY };
86
- }, [swipeable, state.totalSlides]);
87
91
 
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
+ const handleTouchMove = (e: TouchEvent) => {
93
+ if (!touchStartRef.current) return;
94
+ const touch = e.touches[0];
95
+ touchCurrentRef.current = { x: touch.clientX, y: touch.clientY };
92
96
 
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]);
97
+ // Prevent vertical scroll if horizontal swipe is detected
98
+ const deltaX = touch.clientX - touchStartRef.current.x;
99
+ const deltaY = touch.clientY - touchStartRef.current.y;
100
+ if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 10) {
101
+ e.preventDefault();
102
+ }
103
+ };
100
104
 
101
- const handleTouchEnd = useCallback(() => {
102
- if (!swipeable || !touchStartRef.current || !touchCurrentRef.current || state.isTransitioning) {
103
- touchStartRef.current = null;
104
- touchCurrentRef.current = null;
105
- return;
106
- }
105
+ const handleTouchEnd = () => {
106
+ if (!touchStartRef.current || !touchCurrentRef.current || state.isTransitioning) {
107
+ touchStartRef.current = null;
108
+ touchCurrentRef.current = null;
109
+ return;
110
+ }
107
111
 
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;
112
+ const deltaX = touchCurrentRef.current.x - touchStartRef.current.x;
113
+ const deltaY = touchCurrentRef.current.y - touchStartRef.current.y;
114
+ const deltaTime = Date.now() - touchStartRef.current.time;
111
115
 
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;
116
+ // Minimum swipe distance (50px) and max time (300ms for quick swipe, or slower with more distance)
117
+ const minSwipeDistance = 50;
118
+ const isHorizontalSwipe = Math.abs(deltaX) > Math.abs(deltaY);
119
+ const isValidSwipe = Math.abs(deltaX) > minSwipeDistance && isHorizontalSwipe;
120
+ const isQuickSwipe = deltaTime < 300 || Math.abs(deltaX) > 100;
117
121
 
118
- if (isValidSwipe && isQuickSwipe) {
119
- if (deltaX > 0) {
120
- SlideshowManager.prev();
121
- } else {
122
- SlideshowManager.next();
122
+ if (isValidSwipe && isQuickSwipe) {
123
+ if (deltaX > 0) {
124
+ SlideshowManager.prev();
125
+ } else {
126
+ SlideshowManager.next();
127
+ }
123
128
  }
124
- }
125
129
 
126
- touchStartRef.current = null;
127
- touchCurrentRef.current = null;
128
- }, [swipeable, state.isTransitioning]);
130
+ touchStartRef.current = null;
131
+ touchCurrentRef.current = null;
132
+ };
133
+
134
+ // Use passive: false to allow preventDefault() on touchmove
135
+ container.addEventListener('touchstart', handleTouchStart, { passive: true });
136
+ container.addEventListener('touchmove', handleTouchMove, { passive: false });
137
+ container.addEventListener('touchend', handleTouchEnd, { passive: true });
138
+
139
+ return () => {
140
+ container.removeEventListener('touchstart', handleTouchStart);
141
+ container.removeEventListener('touchmove', handleTouchMove);
142
+ container.removeEventListener('touchend', handleTouchEnd);
143
+ };
144
+ }, [swipeable, state.totalSlides, state.isTransitioning]);
129
145
 
130
146
  useEffect(() => {
131
147
  // Initialize slideshow with images
@@ -197,11 +213,9 @@ export function PersistentSlideshow({
197
213
  return (
198
214
  <SlideshowContext.Provider value={contextValue}>
199
215
  <div
216
+ ref={containerRef}
200
217
  className={\`relative w-full h-full bg-black overflow-hidden \${className}\`}
201
218
  style={{ touchAction: swipeable && state.totalSlides > 1 ? 'pan-y pinch-zoom' : 'auto' }}
202
- onTouchStart={handleTouchStart}
203
- onTouchMove={handleTouchMove}
204
- onTouchEnd={handleTouchEnd}
205
219
  >
206
220
  {/* Image container */}
207
221
  <div className="relative w-full h-full">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thewhateverapp/tile-sdk",
3
- "version": "0.13.12",
3
+ "version": "0.13.13",
4
4
  "description": "SDK for building interactive tiles on The Whatever App platform",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",