@thewhateverapp/tile-sdk 0.13.13 → 0.13.15

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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"}
@@ -75,12 +75,21 @@ class SlideshowSingletonClass {
75
75
  }
76
76
  // Navigation controls
77
77
  next() {
78
- if (this.currentState.isTransitioning || this.currentState.totalSlides <= 1)
78
+ console.log('[SlideshowSingleton] next() called:', {
79
+ isTransitioning: this.currentState.isTransitioning,
80
+ totalSlides: this.currentState.totalSlides,
81
+ currentIndex: this.currentState.currentIndex,
82
+ });
83
+ if (this.currentState.isTransitioning || this.currentState.totalSlides <= 1) {
84
+ console.log('[SlideshowSingleton] next() blocked:', this.currentState.isTransitioning ? 'transitioning' : 'single slide');
79
85
  return;
86
+ }
87
+ const newIndex = (this.currentState.currentIndex + 1) % this.currentState.totalSlides;
88
+ console.log('[SlideshowSingleton] next() advancing to index:', newIndex);
80
89
  this.currentState = {
81
90
  ...this.currentState,
82
91
  isTransitioning: true,
83
- currentIndex: (this.currentState.currentIndex + 1) % this.currentState.totalSlides,
92
+ currentIndex: newIndex,
84
93
  };
85
94
  this.notifyListeners();
86
95
  setTimeout(() => {
@@ -89,12 +98,21 @@ class SlideshowSingletonClass {
89
98
  }, this.transitionDuration);
90
99
  }
91
100
  prev() {
92
- if (this.currentState.isTransitioning || this.currentState.totalSlides <= 1)
101
+ console.log('[SlideshowSingleton] prev() called:', {
102
+ isTransitioning: this.currentState.isTransitioning,
103
+ totalSlides: this.currentState.totalSlides,
104
+ currentIndex: this.currentState.currentIndex,
105
+ });
106
+ if (this.currentState.isTransitioning || this.currentState.totalSlides <= 1) {
107
+ console.log('[SlideshowSingleton] prev() blocked:', this.currentState.isTransitioning ? 'transitioning' : 'single slide');
93
108
  return;
109
+ }
110
+ const newIndex = (this.currentState.currentIndex - 1 + this.currentState.totalSlides) % this.currentState.totalSlides;
111
+ console.log('[SlideshowSingleton] prev() going back to index:', newIndex);
94
112
  this.currentState = {
95
113
  ...this.currentState,
96
114
  isTransitioning: true,
97
- currentIndex: (this.currentState.currentIndex - 1 + this.currentState.totalSlides) % this.currentState.totalSlides,
115
+ currentIndex: newIndex,
98
116
  };
99
117
  this.notifyListeners();
100
118
  setTimeout(() => {
@@ -217,10 +235,36 @@ export function Slideshow({ images, intervalMs = 5000, autoAdvance = true, trans
217
235
  const touchCurrentRef = useRef(null);
218
236
  useEffect(() => {
219
237
  const container = containerRef.current;
220
- if (!container || !swipeable || state.totalSlides <= 1)
238
+ console.log('[Slideshow Swipe] Effect running:', {
239
+ hasContainer: !!container,
240
+ swipeable,
241
+ totalSlides: state.totalSlides,
242
+ isTransitioning: state.isTransitioning,
243
+ });
244
+ if (!container) {
245
+ console.log('[Slideshow Swipe] ❌ No container ref');
221
246
  return;
247
+ }
248
+ if (!swipeable) {
249
+ console.log('[Slideshow Swipe] ❌ Swipeable is false');
250
+ return;
251
+ }
252
+ if (state.totalSlides <= 1) {
253
+ console.log('[Slideshow Swipe] ❌ Only', state.totalSlides, 'slide(s)');
254
+ return;
255
+ }
256
+ console.log('[Slideshow Swipe] ✅ Attaching touch listeners to container');
222
257
  const handleTouchStart = (e) => {
223
258
  const touch = e.touches[0];
259
+ console.log('[Slideshow Swipe] touchstart:', {
260
+ hasTouch: !!touch,
261
+ clientX: touch?.clientX,
262
+ clientY: touch?.clientY,
263
+ target: e.target?.tagName,
264
+ currentTarget: e.currentTarget?.tagName,
265
+ });
266
+ if (!touch)
267
+ return;
224
268
  touchStartRef.current = {
225
269
  x: touch.clientX,
226
270
  y: touch.clientY,
@@ -229,19 +273,40 @@ export function Slideshow({ images, intervalMs = 5000, autoAdvance = true, trans
229
273
  touchCurrentRef.current = { x: touch.clientX, y: touch.clientY };
230
274
  };
231
275
  const handleTouchMove = (e) => {
232
- if (!touchStartRef.current)
276
+ if (!touchStartRef.current) {
277
+ console.log('[Slideshow Swipe] touchmove: no touchStart ref');
233
278
  return;
279
+ }
234
280
  const touch = e.touches[0];
281
+ if (!touch) {
282
+ console.log('[Slideshow Swipe] touchmove: no touch');
283
+ return;
284
+ }
235
285
  touchCurrentRef.current = { x: touch.clientX, y: touch.clientY };
236
- // Prevent vertical scroll if horizontal swipe is detected
286
+ // Prevent default and stop propagation for horizontal swipes
237
287
  const deltaX = touch.clientX - touchStartRef.current.x;
238
288
  const deltaY = touch.clientY - touchStartRef.current.y;
239
289
  if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 10) {
290
+ console.log('[Slideshow Swipe] touchmove: horizontal swipe detected, preventing default', { deltaX, deltaY });
240
291
  e.preventDefault();
292
+ e.stopPropagation();
241
293
  }
242
294
  };
243
- const handleTouchEnd = () => {
244
- if (!touchStartRef.current || !touchCurrentRef.current || state.isTransitioning) {
295
+ const handleTouchEnd = (e) => {
296
+ console.log('[Slideshow Swipe] touchend:', {
297
+ hasTouchStart: !!touchStartRef.current,
298
+ hasTouchCurrent: !!touchCurrentRef.current,
299
+ isTransitioning: state.isTransitioning,
300
+ });
301
+ if (!touchStartRef.current || !touchCurrentRef.current) {
302
+ console.log('[Slideshow Swipe] touchend: missing refs, aborting');
303
+ touchStartRef.current = null;
304
+ touchCurrentRef.current = null;
305
+ return;
306
+ }
307
+ // Don't process if transitioning
308
+ if (state.isTransitioning) {
309
+ console.log('[Slideshow Swipe] touchend: transitioning, aborting');
245
310
  touchStartRef.current = null;
246
311
  touchCurrentRef.current = null;
247
312
  return;
@@ -249,27 +314,49 @@ export function Slideshow({ images, intervalMs = 5000, autoAdvance = true, trans
249
314
  const deltaX = touchCurrentRef.current.x - touchStartRef.current.x;
250
315
  const deltaY = touchCurrentRef.current.y - touchStartRef.current.y;
251
316
  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;
317
+ // Lower threshold for smaller tiles - 30px minimum swipe distance
318
+ const minSwipeDistance = 30;
254
319
  const isHorizontalSwipe = Math.abs(deltaX) > Math.abs(deltaY);
255
320
  const isValidSwipe = Math.abs(deltaX) > minSwipeDistance && isHorizontalSwipe;
256
- const isQuickSwipe = deltaTime < 300 || Math.abs(deltaX) > 100;
321
+ const isQuickSwipe = deltaTime < 500 || Math.abs(deltaX) > 60;
322
+ console.log('[Slideshow Swipe] touchend analysis:', {
323
+ deltaX,
324
+ deltaY,
325
+ deltaTime,
326
+ minSwipeDistance,
327
+ isHorizontalSwipe,
328
+ isValidSwipe,
329
+ isQuickSwipe,
330
+ willTrigger: isValidSwipe && isQuickSwipe,
331
+ direction: deltaX > 0 ? 'prev' : 'next',
332
+ });
257
333
  if (isValidSwipe && isQuickSwipe) {
334
+ e.preventDefault();
335
+ e.stopPropagation();
258
336
  if (deltaX > 0) {
337
+ console.log('[Slideshow Swipe] 🎯 Triggering PREV');
259
338
  singleton.prev();
260
339
  }
261
340
  else {
341
+ console.log('[Slideshow Swipe] 🎯 Triggering NEXT');
262
342
  singleton.next();
263
343
  }
264
344
  }
345
+ else {
346
+ console.log('[Slideshow Swipe] ❌ Swipe not valid:', {
347
+ reason: !isHorizontalSwipe ? 'not horizontal' : !isValidSwipe ? 'too short' : 'too slow',
348
+ });
349
+ }
265
350
  touchStartRef.current = null;
266
351
  touchCurrentRef.current = null;
267
352
  };
268
- // Use passive: false to allow preventDefault() on touchmove
269
- container.addEventListener('touchstart', handleTouchStart, { passive: true });
353
+ // Use passive: false on all to allow preventDefault()
354
+ container.addEventListener('touchstart', handleTouchStart, { passive: false });
270
355
  container.addEventListener('touchmove', handleTouchMove, { passive: false });
271
- container.addEventListener('touchend', handleTouchEnd, { passive: true });
356
+ container.addEventListener('touchend', handleTouchEnd, { passive: false });
357
+ console.log('[Slideshow Swipe] ✅ Listeners attached successfully');
272
358
  return () => {
359
+ console.log('[Slideshow Swipe] Cleaning up listeners');
273
360
  container.removeEventListener('touchstart', handleTouchStart);
274
361
  container.removeEventListener('touchmove', handleTouchMove);
275
362
  container.removeEventListener('touchend', handleTouchEnd);
@@ -312,7 +399,7 @@ export function Slideshow({ images, intervalMs = 5000, autoAdvance = true, trans
312
399
  React.createElement("p", { className: "text-white/60" }, "No images")));
313
400
  }
314
401
  return (React.createElement(SlideshowContext.Provider, { value: contextValue },
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' } },
402
+ React.createElement("div", { ref: containerRef, className: `relative w-full h-full bg-black overflow-hidden ${className}`, style: { touchAction: swipeable && state.totalSlides > 1 ? 'none' : 'auto' } },
316
403
  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}` },
317
404
  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 }))))),
318
405
  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 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";
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 if (!touch) return;\n\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 if (!touch) return;\n\n touchCurrentRef.current = { x: touch.clientX, y: touch.clientY };\n\n // Prevent default and stop propagation for horizontal swipes\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 e.stopPropagation();\n }\n };\n\n const handleTouchEnd = (e: TouchEvent) => {\n if (!touchStartRef.current || !touchCurrentRef.current) {\n touchStartRef.current = null;\n touchCurrentRef.current = null;\n return;\n }\n\n // Don't process if transitioning\n if (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 // Lower threshold for smaller tiles - 30px minimum swipe distance\n const minSwipeDistance = 30;\n const isHorizontalSwipe = Math.abs(deltaX) > Math.abs(deltaY);\n const isValidSwipe = Math.abs(deltaX) > minSwipeDistance && isHorizontalSwipe;\n const isQuickSwipe = deltaTime < 500 || Math.abs(deltaX) > 60;\n\n if (isValidSwipe && isQuickSwipe) {\n e.preventDefault();\n e.stopPropagation();\n\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 on all to allow preventDefault()\n container.addEventListener('touchstart', handleTouchStart, { passive: false });\n container.addEventListener('touchmove', handleTouchMove, { passive: false });\n container.addEventListener('touchend', handleTouchEnd, { passive: false });\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 ? 'none' : '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,25TA4RvC,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,8qUA2SvC,CAAC"}
@@ -81,6 +81,8 @@ export function PersistentSlideshow({
81
81
 
82
82
  const handleTouchStart = (e: TouchEvent) => {
83
83
  const touch = e.touches[0];
84
+ if (!touch) return;
85
+
84
86
  touchStartRef.current = {
85
87
  x: touch.clientX,
86
88
  y: touch.clientY,
@@ -92,18 +94,28 @@ export function PersistentSlideshow({
92
94
  const handleTouchMove = (e: TouchEvent) => {
93
95
  if (!touchStartRef.current) return;
94
96
  const touch = e.touches[0];
97
+ if (!touch) return;
98
+
95
99
  touchCurrentRef.current = { x: touch.clientX, y: touch.clientY };
96
100
 
97
- // Prevent vertical scroll if horizontal swipe is detected
101
+ // Prevent default and stop propagation for horizontal swipes
98
102
  const deltaX = touch.clientX - touchStartRef.current.x;
99
103
  const deltaY = touch.clientY - touchStartRef.current.y;
100
104
  if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 10) {
101
105
  e.preventDefault();
106
+ e.stopPropagation();
102
107
  }
103
108
  };
104
109
 
105
- const handleTouchEnd = () => {
106
- if (!touchStartRef.current || !touchCurrentRef.current || state.isTransitioning) {
110
+ const handleTouchEnd = (e: TouchEvent) => {
111
+ if (!touchStartRef.current || !touchCurrentRef.current) {
112
+ touchStartRef.current = null;
113
+ touchCurrentRef.current = null;
114
+ return;
115
+ }
116
+
117
+ // Don't process if transitioning
118
+ if (state.isTransitioning) {
107
119
  touchStartRef.current = null;
108
120
  touchCurrentRef.current = null;
109
121
  return;
@@ -113,13 +125,16 @@ export function PersistentSlideshow({
113
125
  const deltaY = touchCurrentRef.current.y - touchStartRef.current.y;
114
126
  const deltaTime = Date.now() - touchStartRef.current.time;
115
127
 
116
- // Minimum swipe distance (50px) and max time (300ms for quick swipe, or slower with more distance)
117
- const minSwipeDistance = 50;
128
+ // Lower threshold for smaller tiles - 30px minimum swipe distance
129
+ const minSwipeDistance = 30;
118
130
  const isHorizontalSwipe = Math.abs(deltaX) > Math.abs(deltaY);
119
131
  const isValidSwipe = Math.abs(deltaX) > minSwipeDistance && isHorizontalSwipe;
120
- const isQuickSwipe = deltaTime < 300 || Math.abs(deltaX) > 100;
132
+ const isQuickSwipe = deltaTime < 500 || Math.abs(deltaX) > 60;
121
133
 
122
134
  if (isValidSwipe && isQuickSwipe) {
135
+ e.preventDefault();
136
+ e.stopPropagation();
137
+
123
138
  if (deltaX > 0) {
124
139
  SlideshowManager.prev();
125
140
  } else {
@@ -131,10 +146,10 @@ export function PersistentSlideshow({
131
146
  touchCurrentRef.current = null;
132
147
  };
133
148
 
134
- // Use passive: false to allow preventDefault() on touchmove
135
- container.addEventListener('touchstart', handleTouchStart, { passive: true });
149
+ // Use passive: false on all to allow preventDefault()
150
+ container.addEventListener('touchstart', handleTouchStart, { passive: false });
136
151
  container.addEventListener('touchmove', handleTouchMove, { passive: false });
137
- container.addEventListener('touchend', handleTouchEnd, { passive: true });
152
+ container.addEventListener('touchend', handleTouchEnd, { passive: false });
138
153
 
139
154
  return () => {
140
155
  container.removeEventListener('touchstart', handleTouchStart);
@@ -215,7 +230,7 @@ export function PersistentSlideshow({
215
230
  <div
216
231
  ref={containerRef}
217
232
  className={\`relative w-full h-full bg-black overflow-hidden \${className}\`}
218
- style={{ touchAction: swipeable && state.totalSlides > 1 ? 'pan-y pinch-zoom' : 'auto' }}
233
+ style={{ touchAction: swipeable && state.totalSlides > 1 ? 'none' : 'auto' }}
219
234
  >
220
235
  {/* Image container */}
221
236
  <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.13",
3
+ "version": "0.13.15",
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",