@thewhateverapp/tile-sdk 0.15.8 → 0.16.0

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,9 +1,5 @@
1
1
  import React, { type ReactNode } from 'react';
2
- declare global {
3
- interface Window {
4
- __slideshowSingleton?: SlideshowSingletonClass;
5
- }
6
- }
2
+ import useEmblaCarousel from 'embla-carousel-react';
7
3
  export interface SlideImage {
8
4
  url: string;
9
5
  alt?: string;
@@ -28,11 +24,14 @@ export interface SlideshowContextValue {
28
24
  state: SlideshowState;
29
25
  controls: SlideshowControls;
30
26
  }
27
+ declare global {
28
+ interface Window {
29
+ __slideshowSingleton?: SlideshowSingletonClass;
30
+ }
31
+ }
31
32
  type StateCallback = (state: SlideshowState) => void;
32
33
  /**
33
34
  * SlideshowSingleton - Manages slideshow state across route changes.
34
- *
35
- * The current slide index and pause state persist across Next.js soft navigations.
36
35
  * When navigating from /tile to /page, the slideshow maintains its position.
37
36
  */
38
37
  declare class SlideshowSingletonClass {
@@ -40,27 +39,18 @@ declare class SlideshowSingletonClass {
40
39
  private currentState;
41
40
  private intervalRef;
42
41
  private intervalMs;
43
- private transitionDuration;
42
+ private emblaApi;
43
+ setEmblaApi(api: ReturnType<typeof useEmblaCarousel>[1] | null): void;
44
44
  /**
45
45
  * Initialize slideshow with images
46
- *
47
- * Auto-advance state is ALWAYS updated based on the autoAdvance option,
48
- * allowing tile mode (no auto-advance) and page mode (auto-advance) to
49
- * work correctly when navigating between routes with the same images.
50
46
  */
51
47
  initialize(images: SlideImage[], options?: {
52
48
  intervalMs?: number;
53
49
  autoAdvance?: boolean;
54
- transitionDuration?: number;
55
50
  }): void;
56
- /**
57
- * Start auto-advance timer
58
- */
59
51
  private startAutoAdvance;
60
- /**
61
- * Stop auto-advance timer
62
- */
63
52
  private stopAutoAdvance;
53
+ updateFromEmbla(index: number): void;
64
54
  next(): void;
65
55
  prev(): void;
66
56
  goTo(index: number): void;
@@ -70,9 +60,6 @@ declare class SlideshowSingletonClass {
70
60
  getState(): SlideshowState;
71
61
  onStateChange(callback: StateCallback): () => void;
72
62
  private notifyListeners;
73
- /**
74
- * Clean up (call when slideshow is removed)
75
- */
76
63
  destroy(): void;
77
64
  }
78
65
  export interface SlideshowProps {
@@ -82,9 +69,9 @@ export interface SlideshowProps {
82
69
  intervalMs?: number;
83
70
  /** Auto-advance slides (default: true) */
84
71
  autoAdvance?: boolean;
85
- /** Transition type (default: 'fade') */
72
+ /** Transition type - note: Embla always uses slide, this is kept for API compat */
86
73
  transition?: 'fade' | 'slide' | 'none';
87
- /** Transition duration in ms (default: 500) */
74
+ /** Transition duration in ms (default: 300) */
88
75
  transitionDuration?: number;
89
76
  /** Show navigation dots (default: true) */
90
77
  showDots?: boolean;
@@ -92,8 +79,10 @@ export interface SlideshowProps {
92
79
  showArrows?: boolean;
93
80
  /** Enable swipe gestures on touch devices (default: true) */
94
81
  swipeable?: boolean;
95
- /** Image fit mode: 'cover' fills container (may crop), 'contain' shows full image (may letterbox) */
82
+ /** Image fit mode: 'cover' fills container (may crop), 'contain' shows full image */
96
83
  objectFit?: 'cover' | 'contain';
84
+ /** Enable looping (default: true) */
85
+ loop?: boolean;
97
86
  /** Children rendered as overlay */
98
87
  children?: ReactNode;
99
88
  /** Additional class names */
@@ -102,22 +91,17 @@ export interface SlideshowProps {
102
91
  imageClassName?: string;
103
92
  }
104
93
  /**
105
- * Slideshow component with auto-advancing image carousel and route persistence.
106
- *
107
- * The current slide index persists across Next.js route changes (tile ↔ page).
108
- * When navigating between routes, the slideshow maintains its position.
94
+ * Slideshow component using Embla Carousel for smooth, native-like swiping.
109
95
  *
110
- * Usage:
111
- * ```tsx
112
- * <Slideshow images={images} className="w-full h-full">
113
- * <YourOverlay />
114
- * </Slideshow>
115
- * ```
96
+ * Features:
97
+ * - Smooth touch/swipe with momentum physics
98
+ * - State persistence across route changes
99
+ * - Auto-advance with configurable interval
100
+ * - Overlay support for custom UI
116
101
  */
117
- export declare function Slideshow({ images, intervalMs, autoAdvance, transition, transitionDuration, showDots, showArrows, swipeable, objectFit, children, className, imageClassName, }: SlideshowProps): React.JSX.Element;
102
+ export declare function Slideshow({ images, intervalMs, autoAdvance, transition, transitionDuration, showDots, showArrows, swipeable, objectFit, loop, children, className, imageClassName, }: SlideshowProps): React.JSX.Element;
118
103
  /**
119
104
  * Hook to access slideshow state and controls from within Slideshow children.
120
- * Also aliased as useSlideshow for consistency with VideoPlayer.
121
105
  */
122
106
  export declare function useSlideshowState(): SlideshowContextValue;
123
107
  export declare const useSlideshow: typeof useSlideshowState;
@@ -1 +1 @@
1
- {"version":3,"file":"Slideshow.d.ts","sourceRoot":"","sources":["../../../src/react/overlay/Slideshow.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAOZ,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAMf,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,oBAAoB,CAAC,EAAE,uBAAuB,CAAC;KAChD;CACF;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,OAAO,CAAC;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,cAAc,CAAC;IACtB,QAAQ,EAAE,iBAAiB,CAAC;CAC7B;AAED,KAAK,aAAa,GAAG,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;AAErD;;;;;GAKG;AACH,cAAM,uBAAuB;IAC3B,OAAO,CAAC,cAAc,CAAiC;IACvD,OAAO,CAAC,YAAY,CAMlB;IACF,OAAO,CAAC,WAAW,CAA+B;IAClD,OAAO,CAAC,UAAU,CAAgB;IAClC,OAAO,CAAC,kBAAkB,CAAe;IAEzC;;;;;;OAMG;IACH,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE,OAAO,CAAC,EAAE;QACzC,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;KAC7B,GAAG,IAAI;IAqCR;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAYxB;;OAEG;IACH,OAAO,CAAC,eAAe;IAQvB,IAAI,IAAI,IAAI;IA4BZ,IAAI,IAAI,IAAI;IA4BZ,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAqBzB,KAAK,IAAI,IAAI;IAMb,MAAM,IAAI,IAAI;IAMd,MAAM,IAAI,IAAI;IASd,QAAQ,IAAI,cAAc;IAI1B,aAAa,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,IAAI;IAMlD,OAAO,CAAC,eAAe;IAIvB;;OAEG;IACH,OAAO,IAAI,IAAI;CAIhB;AAqBD,MAAM,WAAW,cAAc;IAC7B,iCAAiC;IACjC,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,wCAAwC;IACxC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IACvC,+CAA+C;IAC/C,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6CAA6C;IAC7C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,6DAA6D;IAC7D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,qGAAqG;IACrG,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAChC,mCAAmC;IACnC,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kCAAkC;IAClC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CAAC,EACxB,MAAM,EACN,UAAiB,EACjB,WAAkB,EAClB,UAAmB,EACnB,kBAAwB,EACxB,QAAe,EACf,UAAiB,EACjB,SAAgB,EAChB,SAAmB,EACnB,QAAQ,EACR,SAAc,EACd,cAAmB,GACpB,EAAE,cAAc,qBA8XhB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,qBAAqB,CAMzD;AAGD,eAAO,MAAM,YAAY,0BAAoB,CAAC"}
1
+ {"version":3,"file":"Slideshow.d.ts","sourceRoot":"","sources":["../../../src/react/overlay/Slideshow.tsx"],"names":[],"mappings":"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"}
@@ -1,9 +1,8 @@
1
1
  'use client';
2
2
  import React, { createContext, useContext, useEffect, useRef, useState, useCallback, } from 'react';
3
+ import useEmblaCarousel from 'embla-carousel-react';
3
4
  /**
4
5
  * SlideshowSingleton - Manages slideshow state across route changes.
5
- *
6
- * The current slide index and pause state persist across Next.js soft navigations.
7
6
  * When navigating from /tile to /page, the slideshow maintains its position.
8
7
  */
9
8
  class SlideshowSingletonClass {
@@ -18,21 +17,19 @@ class SlideshowSingletonClass {
18
17
  };
19
18
  this.intervalRef = null;
20
19
  this.intervalMs = 5000;
21
- this.transitionDuration = 500;
20
+ // Embla API reference (set by component)
21
+ this.emblaApi = null;
22
+ }
23
+ setEmblaApi(api) {
24
+ this.emblaApi = api;
22
25
  }
23
26
  /**
24
27
  * Initialize slideshow with images
25
- *
26
- * Auto-advance state is ALWAYS updated based on the autoAdvance option,
27
- * allowing tile mode (no auto-advance) and page mode (auto-advance) to
28
- * work correctly when navigating between routes with the same images.
29
28
  */
30
29
  initialize(images, options) {
31
30
  const imagesChanged = JSON.stringify(images) !== JSON.stringify(this.currentState.images);
32
- // Determine pause state from autoAdvance option (default: true = not paused)
33
31
  const shouldBePaused = options?.autoAdvance === false;
34
32
  if (imagesChanged) {
35
- // Images changed - reset to first slide
36
33
  this.currentState = {
37
34
  ...this.currentState,
38
35
  currentIndex: 0,
@@ -42,8 +39,6 @@ class SlideshowSingletonClass {
42
39
  };
43
40
  }
44
41
  else {
45
- // Same images - preserve current slide but update auto-advance state
46
- // This allows tile→page navigation to start auto-advancing
47
42
  this.currentState = {
48
43
  ...this.currentState,
49
44
  totalSlides: images.length,
@@ -54,15 +49,9 @@ class SlideshowSingletonClass {
54
49
  if (options?.intervalMs) {
55
50
  this.intervalMs = options.intervalMs;
56
51
  }
57
- if (options?.transitionDuration) {
58
- this.transitionDuration = options.transitionDuration;
59
- }
60
52
  this.notifyListeners();
61
53
  this.startAutoAdvance();
62
54
  }
63
- /**
64
- * Start auto-advance timer
65
- */
66
55
  startAutoAdvance() {
67
56
  this.stopAutoAdvance();
68
57
  if (this.currentState.isPaused || this.currentState.totalSlides <= 1) {
@@ -72,78 +61,63 @@ class SlideshowSingletonClass {
72
61
  this.next();
73
62
  }, this.intervalMs);
74
63
  }
75
- /**
76
- * Stop auto-advance timer
77
- */
78
64
  stopAutoAdvance() {
79
65
  if (this.intervalRef) {
80
66
  clearInterval(this.intervalRef);
81
67
  this.intervalRef = null;
82
68
  }
83
69
  }
84
- // Navigation controls
70
+ // Update state from Embla (called on slide change)
71
+ updateFromEmbla(index) {
72
+ if (index !== this.currentState.currentIndex) {
73
+ this.currentState = {
74
+ ...this.currentState,
75
+ currentIndex: index,
76
+ isTransitioning: false,
77
+ };
78
+ this.notifyListeners();
79
+ // Restart auto-advance timer on manual navigation
80
+ if (!this.currentState.isPaused) {
81
+ this.startAutoAdvance();
82
+ }
83
+ }
84
+ }
85
85
  next() {
86
- console.log('[SlideshowSingleton] next() called:', {
87
- isTransitioning: this.currentState.isTransitioning,
88
- totalSlides: this.currentState.totalSlides,
89
- currentIndex: this.currentState.currentIndex,
90
- });
91
- if (this.currentState.isTransitioning || this.currentState.totalSlides <= 1) {
92
- console.log('[SlideshowSingleton] next() blocked:', this.currentState.isTransitioning ? 'transitioning' : 'single slide');
86
+ if (this.currentState.totalSlides <= 1)
93
87
  return;
88
+ if (this.emblaApi) {
89
+ this.emblaApi.scrollNext();
94
90
  }
95
- const newIndex = (this.currentState.currentIndex + 1) % this.currentState.totalSlides;
96
- console.log('[SlideshowSingleton] next() advancing to index:', newIndex);
97
- this.currentState = {
98
- ...this.currentState,
99
- isTransitioning: true,
100
- currentIndex: newIndex,
101
- };
102
- this.notifyListeners();
103
- setTimeout(() => {
104
- this.currentState = { ...this.currentState, isTransitioning: false };
91
+ else {
92
+ // Fallback without Embla
93
+ const newIndex = (this.currentState.currentIndex + 1) % this.currentState.totalSlides;
94
+ this.currentState = { ...this.currentState, currentIndex: newIndex };
105
95
  this.notifyListeners();
106
- }, this.transitionDuration);
96
+ }
107
97
  }
108
98
  prev() {
109
- console.log('[SlideshowSingleton] prev() called:', {
110
- isTransitioning: this.currentState.isTransitioning,
111
- totalSlides: this.currentState.totalSlides,
112
- currentIndex: this.currentState.currentIndex,
113
- });
114
- if (this.currentState.isTransitioning || this.currentState.totalSlides <= 1) {
115
- console.log('[SlideshowSingleton] prev() blocked:', this.currentState.isTransitioning ? 'transitioning' : 'single slide');
99
+ if (this.currentState.totalSlides <= 1)
116
100
  return;
101
+ if (this.emblaApi) {
102
+ this.emblaApi.scrollPrev();
117
103
  }
118
- const newIndex = (this.currentState.currentIndex - 1 + this.currentState.totalSlides) % this.currentState.totalSlides;
119
- console.log('[SlideshowSingleton] prev() going back to index:', newIndex);
120
- this.currentState = {
121
- ...this.currentState,
122
- isTransitioning: true,
123
- currentIndex: newIndex,
124
- };
125
- this.notifyListeners();
126
- setTimeout(() => {
127
- this.currentState = { ...this.currentState, isTransitioning: false };
104
+ else {
105
+ const newIndex = (this.currentState.currentIndex - 1 + this.currentState.totalSlides) % this.currentState.totalSlides;
106
+ this.currentState = { ...this.currentState, currentIndex: newIndex };
128
107
  this.notifyListeners();
129
- }, this.transitionDuration);
108
+ }
130
109
  }
131
110
  goTo(index) {
132
- if (this.currentState.isTransitioning ||
133
- index === this.currentState.currentIndex ||
134
- index < 0 ||
135
- index >= this.currentState.totalSlides)
111
+ if (index < 0 || index >= this.currentState.totalSlides || index === this.currentState.currentIndex) {
136
112
  return;
137
- this.currentState = {
138
- ...this.currentState,
139
- isTransitioning: true,
140
- currentIndex: index,
141
- };
142
- this.notifyListeners();
143
- setTimeout(() => {
144
- this.currentState = { ...this.currentState, isTransitioning: false };
113
+ }
114
+ if (this.emblaApi) {
115
+ this.emblaApi.scrollTo(index);
116
+ }
117
+ else {
118
+ this.currentState = { ...this.currentState, currentIndex: index };
145
119
  this.notifyListeners();
146
- }, this.transitionDuration);
120
+ }
147
121
  }
148
122
  pause() {
149
123
  this.currentState = { ...this.currentState, isPaused: true };
@@ -163,7 +137,6 @@ class SlideshowSingletonClass {
163
137
  this.pause();
164
138
  }
165
139
  }
166
- // State management
167
140
  getState() {
168
141
  return { ...this.currentState };
169
142
  }
@@ -175,17 +148,12 @@ class SlideshowSingletonClass {
175
148
  notifyListeners() {
176
149
  this.stateCallbacks.forEach(cb => cb(this.currentState));
177
150
  }
178
- /**
179
- * Clean up (call when slideshow is removed)
180
- */
181
151
  destroy() {
182
152
  this.stopAutoAdvance();
183
153
  this.stateCallbacks.clear();
154
+ this.emblaApi = null;
184
155
  }
185
156
  }
186
- /**
187
- * Get the SlideshowSingleton instance (creates if needed)
188
- */
189
157
  function getSlideshowSingleton() {
190
158
  if (typeof window === 'undefined') {
191
159
  return new SlideshowSingletonClass();
@@ -200,275 +168,68 @@ function getSlideshowSingleton() {
200
168
  // =============================================================================
201
169
  const SlideshowContext = createContext(null);
202
170
  /**
203
- * Slideshow component with auto-advancing image carousel and route persistence.
204
- *
205
- * The current slide index persists across Next.js route changes (tile ↔ page).
206
- * When navigating between routes, the slideshow maintains its position.
171
+ * Slideshow component using Embla Carousel for smooth, native-like swiping.
207
172
  *
208
- * Usage:
209
- * ```tsx
210
- * <Slideshow images={images} className="w-full h-full">
211
- * <YourOverlay />
212
- * </Slideshow>
213
- * ```
173
+ * Features:
174
+ * - Smooth touch/swipe with momentum physics
175
+ * - State persistence across route changes
176
+ * - Auto-advance with configurable interval
177
+ * - Overlay support for custom UI
214
178
  */
215
- export function Slideshow({ images, intervalMs = 5000, autoAdvance = true, transition = 'fade', transitionDuration = 500, showDots = true, showArrows = true, swipeable = true, objectFit = 'cover', children, className = '', imageClassName = '', }) {
179
+ export function Slideshow({ images, intervalMs = 5000, autoAdvance = true, transition = 'slide', transitionDuration = 300, showDots = true, showArrows = true, swipeable = true, objectFit = 'cover', loop = true, children, className = '', imageClassName = '', }) {
216
180
  const [state, setState] = useState(() => getSlideshowSingleton().getState());
217
- const containerRef = useRef(null);
218
- const touchStartRef = useRef(null);
219
- // Initialize and subscribe to singleton
181
+ const singleton = useRef(getSlideshowSingleton());
182
+ // Initialize Embla Carousel
183
+ const [emblaRef, emblaApi] = useEmblaCarousel({
184
+ loop: loop && images.length > 1,
185
+ dragFree: false,
186
+ containScroll: 'trimSnaps',
187
+ watchDrag: swipeable,
188
+ duration: transitionDuration,
189
+ startIndex: singleton.current.getState().currentIndex,
190
+ });
191
+ // Connect Embla API to singleton
220
192
  useEffect(() => {
221
- const singleton = getSlideshowSingleton();
222
- // Initialize with images and options
223
- singleton.initialize(images, {
224
- intervalMs,
225
- autoAdvance,
226
- transitionDuration,
227
- });
228
- // Subscribe to state changes
229
- const unsubscribe = singleton.onStateChange(setState);
230
- return unsubscribe;
231
- }, [images, intervalMs, autoAdvance, transitionDuration]);
232
- // Control functions bound to singleton
233
- const singleton = getSlideshowSingleton();
234
- const controls = {
235
- next: useCallback(() => singleton.next(), []),
236
- prev: useCallback(() => singleton.prev(), []),
237
- goTo: useCallback((index) => singleton.goTo(index), []),
238
- pause: useCallback(() => singleton.pause(), []),
239
- resume: useCallback(() => singleton.resume(), []),
240
- toggle: useCallback(() => singleton.toggle(), []),
241
- };
242
- // Touch/swipe handlers with visual drag feedback
243
- // NOTE: We attach listeners once and check singleton state directly to avoid
244
- // stale closure issues and listener teardown during swipe sequences.
245
- const touchCurrentRef = useRef(null);
246
- const [dragOffset, setDragOffset] = useState(0); // Pixels dragged horizontally
247
- const [isDragging, setIsDragging] = useState(false);
248
- const containerWidthRef = useRef(0);
193
+ singleton.current.setEmblaApi(emblaApi);
194
+ return () => {
195
+ singleton.current.setEmblaApi(null);
196
+ };
197
+ }, [emblaApi]);
198
+ // Listen for Embla slide changes
249
199
  useEffect(() => {
250
- const container = containerRef.current;
251
- if (!container || !swipeable) {
200
+ if (!emblaApi)
252
201
  return;
253
- }
254
- // Get singleton reference once - we'll check its state directly in handlers
255
- const swipeSingleton = getSlideshowSingleton();
256
- const handleTouchStart = (e) => {
257
- // Check totalSlides from singleton (not stale closure)
258
- if (swipeSingleton.getState().totalSlides <= 1)
259
- return;
260
- const touch = e.touches[0];
261
- if (!touch)
262
- return;
263
- // Store container width for percentage calculations
264
- containerWidthRef.current = container.offsetWidth;
265
- touchStartRef.current = {
266
- x: touch.clientX,
267
- y: touch.clientY,
268
- time: Date.now(),
269
- };
270
- touchCurrentRef.current = { x: touch.clientX, y: touch.clientY };
271
- setIsDragging(true);
272
- setDragOffset(0);
202
+ const onSelect = () => {
203
+ const index = emblaApi.selectedScrollSnap();
204
+ singleton.current.updateFromEmbla(index);
273
205
  };
274
- const handleTouchMove = (e) => {
275
- if (!touchStartRef.current)
276
- return;
277
- const touch = e.touches[0];
278
- if (!touch)
279
- return;
280
- touchCurrentRef.current = { x: touch.clientX, y: touch.clientY };
281
- const deltaX = touch.clientX - touchStartRef.current.x;
282
- const deltaY = touch.clientY - touchStartRef.current.y;
283
- // Only start visual drag if horizontal movement dominates (3px threshold for responsiveness)
284
- if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 3) {
285
- e.preventDefault();
286
- e.stopPropagation();
287
- // Apply resistance at edges (can't drag past first/last)
288
- const currentIndex = swipeSingleton.getState().currentIndex;
289
- const slideshowTotalSlides = swipeSingleton.getState().totalSlides;
290
- const isAtStart = currentIndex === 0 && deltaX > 0;
291
- const isAtEnd = currentIndex === slideshowTotalSlides - 1 && deltaX < 0;
292
- if (isAtStart || isAtEnd) {
293
- // Rubber band effect - reduced movement at edges
294
- setDragOffset(deltaX * 0.3);
295
- }
296
- else {
297
- setDragOffset(deltaX);
298
- }
299
- }
300
- };
301
- const handleTouchEnd = (e) => {
302
- const wasDragging = !!touchStartRef.current;
303
- if (!touchStartRef.current || !touchCurrentRef.current) {
304
- touchStartRef.current = null;
305
- touchCurrentRef.current = null;
306
- setIsDragging(false);
307
- setDragOffset(0);
308
- return;
309
- }
310
- // Check transitioning from singleton directly (not stale closure)
311
- if (swipeSingleton.getState().isTransitioning) {
312
- touchStartRef.current = null;
313
- touchCurrentRef.current = null;
314
- setIsDragging(false);
315
- setDragOffset(0);
316
- return;
317
- }
318
- const deltaX = touchCurrentRef.current.x - touchStartRef.current.x;
319
- const deltaY = touchCurrentRef.current.y - touchStartRef.current.y;
320
- const deltaTime = Date.now() - touchStartRef.current.time;
321
- // 15px minimum swipe distance, must be horizontal (lowered for better sensitivity)
322
- const minSwipeDistance = 15;
323
- const isHorizontalSwipe = Math.abs(deltaX) > Math.abs(deltaY);
324
- const isValidSwipe = Math.abs(deltaX) > minSwipeDistance && isHorizontalSwipe;
325
- const isQuickSwipe = deltaTime < 600 || Math.abs(deltaX) > 35;
326
- // Reset drag state before triggering transition
327
- setIsDragging(false);
328
- setDragOffset(0);
329
- if (isValidSwipe && isQuickSwipe) {
330
- e.preventDefault();
331
- e.stopPropagation();
332
- if (deltaX > 0) {
333
- swipeSingleton.prev();
334
- }
335
- else {
336
- swipeSingleton.next();
337
- }
338
- }
339
- touchStartRef.current = null;
340
- touchCurrentRef.current = null;
341
- };
342
- // Also handle touch cancel (e.g., incoming call)
343
- const handleTouchCancel = () => {
344
- touchStartRef.current = null;
345
- touchCurrentRef.current = null;
346
- setIsDragging(false);
347
- setDragOffset(0);
348
- };
349
- // Use passive: false to allow preventDefault()
350
- container.addEventListener('touchstart', handleTouchStart, { passive: false });
351
- container.addEventListener('touchmove', handleTouchMove, { passive: false });
352
- container.addEventListener('touchend', handleTouchEnd, { passive: false });
353
- container.addEventListener('touchcancel', handleTouchCancel, { passive: false });
206
+ emblaApi.on('select', onSelect);
207
+ // Sync initial position if singleton has a different index
208
+ const currentState = singleton.current.getState();
209
+ if (currentState.currentIndex !== emblaApi.selectedScrollSnap() && currentState.currentIndex < images.length) {
210
+ emblaApi.scrollTo(currentState.currentIndex, true);
211
+ }
354
212
  return () => {
355
- container.removeEventListener('touchstart', handleTouchStart);
356
- container.removeEventListener('touchmove', handleTouchMove);
357
- container.removeEventListener('touchend', handleTouchEnd);
358
- container.removeEventListener('touchcancel', handleTouchCancel);
213
+ emblaApi.off('select', onSelect);
359
214
  };
360
- }, [swipeable]); // Only re-attach if swipeable changes - check singleton for dynamic state
361
- // Track if we just finished dragging (for smooth transition)
362
- const [justFinishedDrag, setJustFinishedDrag] = useState(false);
363
- // When isDragging changes to false, briefly keep slide mode for smooth finish
215
+ }, [emblaApi, images.length]);
216
+ // Initialize singleton and subscribe to state changes
364
217
  useEffect(() => {
365
- if (!isDragging && dragOffset === 0) {
366
- setJustFinishedDrag(true);
367
- const timer = setTimeout(() => setJustFinishedDrag(false), transitionDuration + 50);
368
- return () => clearTimeout(timer);
369
- }
370
- }, [isDragging, dragOffset, transitionDuration]);
371
- // Get transition styles with drag offset support
372
- // When swipeable, always use slide transitions for consistency
373
- const getTransitionStyles = (index) => {
374
- const isActive = index === state.currentIndex;
375
- // Use images.length as fallback since state.totalSlides may be 0 on first render
376
- const totalSlides = state.totalSlides || images.length;
377
- const isPrev = index === state.currentIndex - 1 ||
378
- (state.currentIndex === 0 && index === totalSlides - 1); // Wrap around
379
- const isNext = index === state.currentIndex + 1 ||
380
- (state.currentIndex === totalSlides - 1 && index === 0); // Wrap around
381
- // During drag, use slide-style movement for visual feedback
382
- if (isDragging && swipeable && totalSlides > 1) {
383
- const containerWidth = containerWidthRef.current || 256;
384
- const dragPercent = (dragOffset / containerWidth) * 100;
385
- // Check if at edges (no wrap-around during drag)
386
- const isAtStart = state.currentIndex === 0;
387
- const isAtEnd = state.currentIndex === totalSlides - 1;
388
- // Show current, prev, and next slides during drag
389
- if (isActive) {
390
- return {
391
- transform: `translateX(${dragPercent}%)`,
392
- transition: 'none', // Immediate response during drag
393
- position: 'absolute',
394
- inset: 0,
395
- zIndex: 2,
396
- opacity: 1,
397
- };
398
- }
399
- else if (isPrev && dragOffset > 0 && !isAtStart) {
400
- // Dragging right - show previous slide (but not at start - no wrap)
401
- return {
402
- transform: `translateX(${-100 + dragPercent}%)`,
403
- transition: 'none',
404
- position: 'absolute',
405
- inset: 0,
406
- zIndex: 1,
407
- opacity: 1,
408
- };
409
- }
410
- else if (isNext && dragOffset < 0 && !isAtEnd) {
411
- // Dragging left - show next slide (but not at end - no wrap)
412
- return {
413
- transform: `translateX(${100 + dragPercent}%)`,
414
- transition: 'none',
415
- position: 'absolute',
416
- inset: 0,
417
- zIndex: 1,
418
- opacity: 1,
419
- };
420
- }
421
- else {
422
- // Hide other slides during drag (positioned off-screen)
423
- return {
424
- transform: 'translateX(-200%)',
425
- position: 'absolute',
426
- inset: 0,
427
- zIndex: 0,
428
- opacity: 1,
429
- };
430
- }
431
- }
432
- // When swipeable is enabled, ALWAYS use slide/transform transitions
433
- // Never use opacity-based fade to avoid any visual glitches
434
- if (swipeable) {
435
- const slideOffset = totalSlides > 1 ? (index - state.currentIndex) * 100 : 0;
436
- return {
437
- transform: `translateX(${slideOffset}%)`,
438
- transition: (justFinishedDrag || state.isTransitioning) && totalSlides > 1
439
- ? `transform ${transitionDuration}ms ease-out`
440
- : 'none',
441
- position: 'absolute',
442
- inset: 0,
443
- opacity: 1, // Always fully visible - no fade
444
- };
445
- }
446
- // Non-swipeable mode - use the configured transition type
447
- switch (transition) {
448
- case 'fade':
449
- return {
450
- opacity: isActive ? 1 : 0,
451
- transition: `opacity ${transitionDuration}ms ease-in-out`,
452
- position: 'absolute',
453
- inset: 0,
454
- };
455
- case 'slide':
456
- const offset = (index - state.currentIndex) * 100;
457
- return {
458
- transform: `translateX(${offset}%)`,
459
- transition: `transform ${transitionDuration}ms ease-in-out`,
460
- position: 'absolute',
461
- inset: 0,
462
- opacity: 1,
463
- };
464
- case 'none':
465
- default:
466
- return {
467
- opacity: isActive ? 1 : 0,
468
- position: 'absolute',
469
- inset: 0,
470
- };
471
- }
218
+ singleton.current.initialize(images, {
219
+ intervalMs,
220
+ autoAdvance,
221
+ });
222
+ const unsubscribe = singleton.current.onStateChange(setState);
223
+ return unsubscribe;
224
+ }, [images, intervalMs, autoAdvance]);
225
+ // Control functions
226
+ const controls = {
227
+ next: useCallback(() => singleton.current.next(), []),
228
+ prev: useCallback(() => singleton.current.prev(), []),
229
+ goTo: useCallback((index) => singleton.current.goTo(index), []),
230
+ pause: useCallback(() => singleton.current.pause(), []),
231
+ resume: useCallback(() => singleton.current.resume(), []),
232
+ toggle: useCallback(() => singleton.current.toggle(), []),
472
233
  };
473
234
  const contextValue = {
474
235
  state,
@@ -479,24 +240,24 @@ export function Slideshow({ images, intervalMs = 5000, autoAdvance = true, trans
479
240
  React.createElement("p", { className: "text-white/60" }, "No images")));
480
241
  }
481
242
  return (React.createElement(SlideshowContext.Provider, { value: contextValue },
482
- React.createElement("div", { ref: containerRef, className: `relative w-full h-full bg-black overflow-hidden ${className}`, style: { touchAction: swipeable && state.totalSlides > 1 ? 'none' : 'auto' } },
483
- 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}` },
484
- 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 }))))),
485
- showArrows && state.totalSlides > 1 && (React.createElement(React.Fragment, null,
486
- React.createElement("button", { onClick: controls.prev, disabled: state.isTransitioning, 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", "aria-label": "Previous slide" },
243
+ React.createElement("div", { className: `relative w-full h-full bg-black overflow-hidden ${className}` },
244
+ React.createElement("div", { ref: emblaRef, className: "w-full h-full overflow-hidden" },
245
+ React.createElement("div", { className: "flex h-full", style: { touchAction: swipeable ? 'pan-y' : 'auto' } }, images.map((image, index) => (React.createElement("div", { key: `${image.url}-${index}`, className: `flex-none w-full h-full flex items-center justify-center ${imageClassName}`, style: { minWidth: 0 } },
246
+ 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 })))))),
247
+ showArrows && images.length > 1 && (React.createElement(React.Fragment, null,
248
+ React.createElement("button", { onClick: controls.prev, 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 z-10", "aria-label": "Previous slide" },
487
249
  React.createElement("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24" },
488
250
  React.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 19l-7-7 7-7" }))),
489
- React.createElement("button", { onClick: controls.next, disabled: state.isTransitioning, 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", "aria-label": "Next slide" },
251
+ React.createElement("button", { onClick: controls.next, 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 z-10", "aria-label": "Next slide" },
490
252
  React.createElement("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24" },
491
253
  React.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 5l7 7-7 7" }))))),
492
- showDots && state.totalSlides > 1 && (React.createElement("div", { className: "absolute bottom-3 left-1/2 -translate-x-1/2 flex gap-1.5" }, images.map((_, index) => (React.createElement("button", { key: index, onClick: () => controls.goTo(index), disabled: state.isTransitioning, className: `w-2 h-2 rounded-full transition-colors ${index === state.currentIndex
254
+ showDots && images.length > 1 && (React.createElement("div", { className: "absolute bottom-3 left-1/2 -translate-x-1/2 flex gap-1.5 z-10" }, images.map((_, index) => (React.createElement("button", { key: index, onClick: () => controls.goTo(index), className: `w-2 h-2 rounded-full transition-colors ${index === state.currentIndex
493
255
  ? 'bg-white'
494
256
  : 'bg-white/40 hover:bg-white/60'}`, "aria-label": `Go to slide ${index + 1}` }))))),
495
- React.createElement("div", { className: "absolute inset-0 z-10 pointer-events-none" }, children))));
257
+ React.createElement("div", { className: "absolute inset-0 z-20 pointer-events-none" }, children))));
496
258
  }
497
259
  /**
498
260
  * Hook to access slideshow state and controls from within Slideshow children.
499
- * Also aliased as useSlideshow for consistency with VideoPlayer.
500
261
  */
501
262
  export function useSlideshowState() {
502
263
  const context = useContext(SlideshowContext);
@@ -505,5 +266,5 @@ export function useSlideshowState() {
505
266
  }
506
267
  return context;
507
268
  }
508
- // Alias for consistency with useVideo/useVideoState
269
+ // Alias for consistency
509
270
  export const useSlideshow = useSlideshowState;
@@ -103,12 +103,12 @@ declare const PlacementSchema: z.ZodUnion<[z.ZodObject<{
103
103
  align: z.ZodOptional<z.ZodEnum<["left", "center", "right"]>>;
104
104
  }, "strip", z.ZodTypeAny, {
105
105
  lane: "center" | "bottom" | "top";
106
- inset?: number | undefined;
107
106
  align?: "center" | "left" | "right" | undefined;
107
+ inset?: number | undefined;
108
108
  }, {
109
109
  lane: "center" | "bottom" | "top";
110
- inset?: number | undefined;
111
110
  align?: "center" | "left" | "right" | undefined;
111
+ inset?: number | undefined;
112
112
  }>, z.ZodObject<{
113
113
  type: z.ZodEnum<["fullscreen", "inset", "positioned"]>;
114
114
  insets: z.ZodOptional<z.ZodObject<{
@@ -181,12 +181,12 @@ declare const OverlayCueSchema: z.ZodObject<{
181
181
  align: z.ZodOptional<z.ZodEnum<["left", "center", "right"]>>;
182
182
  }, "strip", z.ZodTypeAny, {
183
183
  lane: "center" | "bottom" | "top";
184
- inset?: number | undefined;
185
184
  align?: "center" | "left" | "right" | undefined;
185
+ inset?: number | undefined;
186
186
  }, {
187
187
  lane: "center" | "bottom" | "top";
188
- inset?: number | undefined;
189
188
  align?: "center" | "left" | "right" | undefined;
189
+ inset?: number | undefined;
190
190
  }>, z.ZodObject<{
191
191
  type: z.ZodEnum<["fullscreen", "inset", "positioned"]>;
192
192
  insets: z.ZodOptional<z.ZodObject<{
@@ -264,8 +264,8 @@ declare const OverlayCueSchema: z.ZodObject<{
264
264
  };
265
265
  placement: {
266
266
  lane: "center" | "bottom" | "top";
267
- inset?: number | undefined;
268
267
  align?: "center" | "left" | "right" | undefined;
268
+ inset?: number | undefined;
269
269
  } | {
270
270
  type: "inset" | "fullscreen" | "positioned";
271
271
  position?: {
@@ -295,8 +295,8 @@ declare const OverlayCueSchema: z.ZodObject<{
295
295
  };
296
296
  placement: {
297
297
  lane: "center" | "bottom" | "top";
298
- inset?: number | undefined;
299
298
  align?: "center" | "left" | "right" | undefined;
299
+ inset?: number | undefined;
300
300
  } | {
301
301
  type: "inset" | "fullscreen" | "positioned";
302
302
  position?: {
@@ -462,12 +462,12 @@ export declare const OverlaySpecSchema: z.ZodObject<{
462
462
  align: z.ZodOptional<z.ZodEnum<["left", "center", "right"]>>;
463
463
  }, "strip", z.ZodTypeAny, {
464
464
  lane: "center" | "bottom" | "top";
465
- inset?: number | undefined;
466
465
  align?: "center" | "left" | "right" | undefined;
466
+ inset?: number | undefined;
467
467
  }, {
468
468
  lane: "center" | "bottom" | "top";
469
- inset?: number | undefined;
470
469
  align?: "center" | "left" | "right" | undefined;
470
+ inset?: number | undefined;
471
471
  }>, z.ZodObject<{
472
472
  type: z.ZodEnum<["fullscreen", "inset", "positioned"]>;
473
473
  insets: z.ZodOptional<z.ZodObject<{
@@ -545,8 +545,8 @@ export declare const OverlaySpecSchema: z.ZodObject<{
545
545
  };
546
546
  placement: {
547
547
  lane: "center" | "bottom" | "top";
548
- inset?: number | undefined;
549
548
  align?: "center" | "left" | "right" | undefined;
549
+ inset?: number | undefined;
550
550
  } | {
551
551
  type: "inset" | "fullscreen" | "positioned";
552
552
  position?: {
@@ -576,8 +576,8 @@ export declare const OverlaySpecSchema: z.ZodObject<{
576
576
  };
577
577
  placement: {
578
578
  lane: "center" | "bottom" | "top";
579
- inset?: number | undefined;
580
579
  align?: "center" | "left" | "right" | undefined;
580
+ inset?: number | undefined;
581
581
  } | {
582
582
  type: "inset" | "fullscreen" | "positioned";
583
583
  position?: {
@@ -609,8 +609,8 @@ export declare const OverlaySpecSchema: z.ZodObject<{
609
609
  };
610
610
  placement: {
611
611
  lane: "center" | "bottom" | "top";
612
- inset?: number | undefined;
613
612
  align?: "center" | "left" | "right" | undefined;
613
+ inset?: number | undefined;
614
614
  } | {
615
615
  type: "inset" | "fullscreen" | "positioned";
616
616
  position?: {
@@ -656,8 +656,8 @@ export declare const OverlaySpecSchema: z.ZodObject<{
656
656
  };
657
657
  placement: {
658
658
  lane: "center" | "bottom" | "top";
659
- inset?: number | undefined;
660
659
  align?: "center" | "left" | "right" | undefined;
660
+ inset?: number | undefined;
661
661
  } | {
662
662
  type: "inset" | "fullscreen" | "positioned";
663
663
  position?: {
@@ -741,8 +741,8 @@ export declare const OverlaySpecSchema: z.ZodObject<{
741
741
  };
742
742
  placement: {
743
743
  lane: "center" | "bottom" | "top";
744
- inset?: number | undefined;
745
744
  align?: "center" | "left" | "right" | undefined;
745
+ inset?: number | undefined;
746
746
  } | {
747
747
  type: "inset" | "fullscreen" | "positioned";
748
748
  position?: {
@@ -820,8 +820,8 @@ export declare const OverlaySpecSchema: z.ZodObject<{
820
820
  };
821
821
  placement: {
822
822
  lane: "center" | "bottom" | "top";
823
- inset?: number | undefined;
824
823
  align?: "center" | "left" | "right" | undefined;
824
+ inset?: number | undefined;
825
825
  } | {
826
826
  type: "inset" | "fullscreen" | "positioned";
827
827
  position?: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thewhateverapp/tile-sdk",
3
- "version": "0.15.8",
3
+ "version": "0.16.0",
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",
@@ -65,6 +65,7 @@
65
65
  "license": "MIT",
66
66
  "dependencies": {
67
67
  "@thewhateverapp/scene-sdk": "^0.1.1",
68
+ "embla-carousel-react": "^8.5.1",
68
69
  "matter-js": "^0.19.0",
69
70
  "zod": "^3.22.0"
70
71
  },