@thewhateverapp/tile-sdk 0.13.0 → 0.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -5,8 +5,9 @@ export { useKeyboard } from './react/useKeyboard';
5
5
  export { TileContainer } from './react/TileContainer';
6
6
  export { withTile } from './react/withTile';
7
7
  export { VideoPlayer, useVideoState, useVideo, // Alias for useVideoState
8
- Slideshow, useSlideshowState, OverlaySlot, FullOverlay, GradientOverlay, } from './react/overlay';
9
- export type { VideoState, VideoControls, VideoContextValue, VideoPlayerProps, SlideImage, SlideshowState, SlideshowControls, SlideshowContextValue, SlideshowProps, SlotPosition, OverlaySlotProps, FullOverlayProps, GradientOverlayProps, } from './react/overlay';
8
+ useCuePoint, useCuePoints, useVideoProgress, Slideshow, useSlideshowState, useSlideshow, // Alias for useSlideshowState
9
+ OverlaySlot, FullOverlay, GradientOverlay, } from './react/overlay';
10
+ export type { VideoState, VideoControls, VideoContextValue, VideoPlayerProps, CuePoint, SlideImage, SlideshowState, SlideshowControls, SlideshowContextValue, SlideshowProps, SlotPosition, OverlaySlotProps, FullOverlayProps, GradientOverlayProps, } from './react/overlay';
10
11
  export { getTileBridge, TileBridge } from './bridge/TileBridge';
11
12
  export type { TileMessage, TileConfig, TileTokenData, KeyboardState, VisibilityState } from './bridge/TileBridge';
12
13
  export { StateClient } from './state/StateClient';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAG5C,OAAO,EAEL,WAAW,EACX,aAAa,EACb,QAAQ,EAAE,0BAA0B;AAEpC,SAAS,EACT,iBAAiB,EAEjB,WAAW,EACX,WAAW,EACX,eAAe,GAChB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAEV,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,gBAAgB,EAEhB,UAAU,EACV,cAAc,EACd,iBAAiB,EACjB,qBAAqB,EACrB,cAAc,EAEd,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAChE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAGlH,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGnE,cAAc,SAAS,CAAC;AAGxB,cAAc,SAAS,CAAC;AAGxB,cAAc,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAG5C,OAAO,EAEL,WAAW,EACX,aAAa,EACb,QAAQ,EAAE,0BAA0B;AACpC,WAAW,EACX,YAAY,EACZ,gBAAgB,EAEhB,SAAS,EACT,iBAAiB,EACjB,YAAY,EAAE,8BAA8B;AAE5C,WAAW,EACX,WAAW,EACX,eAAe,GAChB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAEV,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,gBAAgB,EAChB,QAAQ,EAER,UAAU,EACV,cAAc,EACd,iBAAiB,EACjB,qBAAqB,EACrB,cAAc,EAEd,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAChE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAGlH,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGnE,cAAc,SAAS,CAAC;AAGxB,cAAc,SAAS,CAAC;AAGxB,cAAc,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -9,8 +9,9 @@ export { withTile } from './react/withTile';
9
9
  export {
10
10
  // Video player (with route persistence)
11
11
  VideoPlayer, useVideoState, useVideo, // Alias for useVideoState
12
- // Slideshow
13
- Slideshow, useSlideshowState,
12
+ useCuePoint, useCuePoints, useVideoProgress,
13
+ // Slideshow (with route persistence)
14
+ Slideshow, useSlideshowState, useSlideshow, // Alias for useSlideshowState
14
15
  // Positioning components
15
16
  OverlaySlot, FullOverlay, GradientOverlay, } from './react/overlay';
16
17
  // Bridge for secure communication
@@ -1,4 +1,9 @@
1
1
  import React, { type ReactNode } from 'react';
2
+ declare global {
3
+ interface Window {
4
+ __slideshowSingleton?: SlideshowSingletonClass;
5
+ }
6
+ }
2
7
  export interface SlideImage {
3
8
  url: string;
4
9
  alt?: string;
@@ -23,6 +28,49 @@ export interface SlideshowContextValue {
23
28
  state: SlideshowState;
24
29
  controls: SlideshowControls;
25
30
  }
31
+ type StateCallback = (state: SlideshowState) => void;
32
+ /**
33
+ * SlideshowSingleton - Manages slideshow state across route changes.
34
+ *
35
+ * The current slide index and pause state persist across Next.js soft navigations.
36
+ * When navigating from /tile to /page, the slideshow maintains its position.
37
+ */
38
+ declare class SlideshowSingletonClass {
39
+ private stateCallbacks;
40
+ private currentState;
41
+ private intervalRef;
42
+ private intervalMs;
43
+ private transitionDuration;
44
+ /**
45
+ * Initialize slideshow with images
46
+ */
47
+ initialize(images: SlideImage[], options?: {
48
+ intervalMs?: number;
49
+ autoAdvance?: boolean;
50
+ transitionDuration?: number;
51
+ }): void;
52
+ /**
53
+ * Start auto-advance timer
54
+ */
55
+ private startAutoAdvance;
56
+ /**
57
+ * Stop auto-advance timer
58
+ */
59
+ private stopAutoAdvance;
60
+ next(): void;
61
+ prev(): void;
62
+ goTo(index: number): void;
63
+ pause(): void;
64
+ resume(): void;
65
+ toggle(): void;
66
+ getState(): SlideshowState;
67
+ onStateChange(callback: StateCallback): () => void;
68
+ private notifyListeners;
69
+ /**
70
+ * Clean up (call when slideshow is removed)
71
+ */
72
+ destroy(): void;
73
+ }
26
74
  export interface SlideshowProps {
27
75
  /** Array of images to display */
28
76
  images: SlideImage[];
@@ -38,6 +86,10 @@ export interface SlideshowProps {
38
86
  showDots?: boolean;
39
87
  /** Show navigation arrows (default: true) */
40
88
  showArrows?: boolean;
89
+ /** Enable swipe gestures on touch devices (default: true) */
90
+ swipeable?: boolean;
91
+ /** Image fit mode: 'cover' fills container (may crop), 'contain' shows full image (may letterbox) */
92
+ objectFit?: 'cover' | 'contain';
41
93
  /** Children rendered as overlay */
42
94
  children?: ReactNode;
43
95
  /** Additional class names */
@@ -46,12 +98,24 @@ export interface SlideshowProps {
46
98
  imageClassName?: string;
47
99
  }
48
100
  /**
49
- * Slideshow component with auto-advancing image carousel.
50
- * Provides slideshow state and controls to child overlays via context.
101
+ * Slideshow component with auto-advancing image carousel and route persistence.
102
+ *
103
+ * The current slide index persists across Next.js route changes (tile ↔ page).
104
+ * When navigating between routes, the slideshow maintains its position.
105
+ *
106
+ * Usage:
107
+ * ```tsx
108
+ * <Slideshow images={images} className="w-full h-full">
109
+ * <YourOverlay />
110
+ * </Slideshow>
111
+ * ```
51
112
  */
52
- export declare function Slideshow({ images, intervalMs, autoAdvance, transition, transitionDuration, showDots, showArrows, children, className, imageClassName, }: SlideshowProps): React.JSX.Element;
113
+ export declare function Slideshow({ images, intervalMs, autoAdvance, transition, transitionDuration, showDots, showArrows, swipeable, objectFit, children, className, imageClassName, }: SlideshowProps): React.JSX.Element;
53
114
  /**
54
115
  * Hook to access slideshow state and controls from within Slideshow children.
116
+ * Also aliased as useSlideshow for consistency with VideoPlayer.
55
117
  */
56
118
  export declare function useSlideshowState(): SlideshowContextValue;
119
+ export declare const useSlideshow: typeof useSlideshowState;
120
+ export {};
57
121
  //# sourceMappingURL=Slideshow.d.ts.map
@@ -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;AAEf,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;AAID,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,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;;;GAGG;AACH,wBAAgB,SAAS,CAAC,EACxB,MAAM,EACN,UAAiB,EACjB,WAAkB,EAClB,UAAmB,EACnB,kBAAwB,EACxB,QAAe,EACf,UAAiB,EACjB,QAAQ,EACR,SAAc,EACd,cAAmB,GACpB,EAAE,cAAc,qBAsLhB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,qBAAqB,CAMzD"}
1
+ {"version":3,"file":"Slideshow.d.ts","sourceRoot":"","sources":["../../../src/react/overlay/Slideshow.tsx"],"names":[],"mappings":"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"}
@@ -1,67 +1,254 @@
1
1
  'use client';
2
2
  import React, { createContext, useContext, useEffect, useRef, useState, useCallback, } from 'react';
3
- const SlideshowContext = createContext(null);
4
3
  /**
5
- * Slideshow component with auto-advancing image carousel.
6
- * Provides slideshow state and controls to child overlays via context.
4
+ * SlideshowSingleton - Manages slideshow state across route changes.
5
+ *
6
+ * The current slide index and pause state persist across Next.js soft navigations.
7
+ * When navigating from /tile to /page, the slideshow maintains its position.
7
8
  */
8
- export function Slideshow({ images, intervalMs = 5000, autoAdvance = true, transition = 'fade', transitionDuration = 500, showDots = true, showArrows = true, children, className = '', imageClassName = '', }) {
9
- const [currentIndex, setCurrentIndex] = useState(0);
10
- const [isTransitioning, setIsTransitioning] = useState(false);
11
- const [isPaused, setIsPaused] = useState(!autoAdvance);
12
- const intervalRef = useRef(null);
13
- const totalSlides = images.length;
14
- // Control functions
15
- const next = useCallback(() => {
16
- if (isTransitioning || totalSlides <= 1)
9
+ class SlideshowSingletonClass {
10
+ constructor() {
11
+ this.stateCallbacks = new Set();
12
+ this.currentState = {
13
+ currentIndex: 0,
14
+ totalSlides: 0,
15
+ isTransitioning: false,
16
+ isPaused: false,
17
+ images: [],
18
+ };
19
+ this.intervalRef = null;
20
+ this.intervalMs = 5000;
21
+ this.transitionDuration = 500;
22
+ }
23
+ /**
24
+ * Initialize slideshow with images
25
+ */
26
+ initialize(images, options) {
27
+ const imagesChanged = JSON.stringify(images) !== JSON.stringify(this.currentState.images);
28
+ // Only reset if images changed
29
+ if (imagesChanged) {
30
+ this.currentState = {
31
+ ...this.currentState,
32
+ currentIndex: 0,
33
+ totalSlides: images.length,
34
+ images,
35
+ isPaused: options?.autoAdvance === false,
36
+ };
37
+ }
38
+ else {
39
+ // Just update totalSlides in case it changed
40
+ this.currentState = {
41
+ ...this.currentState,
42
+ totalSlides: images.length,
43
+ images,
44
+ };
45
+ }
46
+ if (options?.intervalMs) {
47
+ this.intervalMs = options.intervalMs;
48
+ }
49
+ if (options?.transitionDuration) {
50
+ this.transitionDuration = options.transitionDuration;
51
+ }
52
+ this.notifyListeners();
53
+ this.startAutoAdvance();
54
+ }
55
+ /**
56
+ * Start auto-advance timer
57
+ */
58
+ startAutoAdvance() {
59
+ this.stopAutoAdvance();
60
+ if (this.currentState.isPaused || this.currentState.totalSlides <= 1) {
17
61
  return;
18
- setIsTransitioning(true);
19
- setCurrentIndex(prev => (prev + 1) % totalSlides);
20
- setTimeout(() => setIsTransitioning(false), transitionDuration);
21
- }, [isTransitioning, totalSlides, transitionDuration]);
22
- const prev = useCallback(() => {
23
- if (isTransitioning || totalSlides <= 1)
62
+ }
63
+ this.intervalRef = setInterval(() => {
64
+ this.next();
65
+ }, this.intervalMs);
66
+ }
67
+ /**
68
+ * Stop auto-advance timer
69
+ */
70
+ stopAutoAdvance() {
71
+ if (this.intervalRef) {
72
+ clearInterval(this.intervalRef);
73
+ this.intervalRef = null;
74
+ }
75
+ }
76
+ // Navigation controls
77
+ next() {
78
+ if (this.currentState.isTransitioning || this.currentState.totalSlides <= 1)
24
79
  return;
25
- setIsTransitioning(true);
26
- setCurrentIndex(prev => (prev - 1 + totalSlides) % totalSlides);
27
- setTimeout(() => setIsTransitioning(false), transitionDuration);
28
- }, [isTransitioning, totalSlides, transitionDuration]);
29
- const goTo = useCallback((index) => {
30
- if (isTransitioning || index === currentIndex || index < 0 || index >= totalSlides)
80
+ this.currentState = {
81
+ ...this.currentState,
82
+ isTransitioning: true,
83
+ currentIndex: (this.currentState.currentIndex + 1) % this.currentState.totalSlides,
84
+ };
85
+ this.notifyListeners();
86
+ setTimeout(() => {
87
+ this.currentState = { ...this.currentState, isTransitioning: false };
88
+ this.notifyListeners();
89
+ }, this.transitionDuration);
90
+ }
91
+ prev() {
92
+ if (this.currentState.isTransitioning || this.currentState.totalSlides <= 1)
31
93
  return;
32
- setIsTransitioning(true);
33
- setCurrentIndex(index);
34
- setTimeout(() => setIsTransitioning(false), transitionDuration);
35
- }, [isTransitioning, currentIndex, totalSlides, transitionDuration]);
36
- const pause = useCallback(() => {
37
- setIsPaused(true);
38
- }, []);
39
- const resume = useCallback(() => {
40
- setIsPaused(false);
41
- }, []);
42
- const toggle = useCallback(() => {
43
- setIsPaused(prev => !prev);
44
- }, []);
45
- // Auto-advance timer
46
- useEffect(() => {
47
- if (isPaused || totalSlides <= 1) {
48
- if (intervalRef.current) {
49
- clearInterval(intervalRef.current);
50
- intervalRef.current = null;
51
- }
94
+ this.currentState = {
95
+ ...this.currentState,
96
+ isTransitioning: true,
97
+ currentIndex: (this.currentState.currentIndex - 1 + this.currentState.totalSlides) % this.currentState.totalSlides,
98
+ };
99
+ this.notifyListeners();
100
+ setTimeout(() => {
101
+ this.currentState = { ...this.currentState, isTransitioning: false };
102
+ this.notifyListeners();
103
+ }, this.transitionDuration);
104
+ }
105
+ goTo(index) {
106
+ if (this.currentState.isTransitioning ||
107
+ index === this.currentState.currentIndex ||
108
+ index < 0 ||
109
+ index >= this.currentState.totalSlides)
52
110
  return;
111
+ this.currentState = {
112
+ ...this.currentState,
113
+ isTransitioning: true,
114
+ currentIndex: index,
115
+ };
116
+ this.notifyListeners();
117
+ setTimeout(() => {
118
+ this.currentState = { ...this.currentState, isTransitioning: false };
119
+ this.notifyListeners();
120
+ }, this.transitionDuration);
121
+ }
122
+ pause() {
123
+ this.currentState = { ...this.currentState, isPaused: true };
124
+ this.stopAutoAdvance();
125
+ this.notifyListeners();
126
+ }
127
+ resume() {
128
+ this.currentState = { ...this.currentState, isPaused: false };
129
+ this.startAutoAdvance();
130
+ this.notifyListeners();
131
+ }
132
+ toggle() {
133
+ if (this.currentState.isPaused) {
134
+ this.resume();
53
135
  }
54
- intervalRef.current = setInterval(next, intervalMs);
55
- return () => {
56
- if (intervalRef.current) {
57
- clearInterval(intervalRef.current);
58
- intervalRef.current = null;
59
- }
136
+ else {
137
+ this.pause();
138
+ }
139
+ }
140
+ // State management
141
+ getState() {
142
+ return { ...this.currentState };
143
+ }
144
+ onStateChange(callback) {
145
+ this.stateCallbacks.add(callback);
146
+ callback(this.currentState);
147
+ return () => this.stateCallbacks.delete(callback);
148
+ }
149
+ notifyListeners() {
150
+ this.stateCallbacks.forEach(cb => cb(this.currentState));
151
+ }
152
+ /**
153
+ * Clean up (call when slideshow is removed)
154
+ */
155
+ destroy() {
156
+ this.stopAutoAdvance();
157
+ this.stateCallbacks.clear();
158
+ }
159
+ }
160
+ /**
161
+ * Get the SlideshowSingleton instance (creates if needed)
162
+ */
163
+ function getSlideshowSingleton() {
164
+ if (typeof window === 'undefined') {
165
+ return new SlideshowSingletonClass();
166
+ }
167
+ if (!window.__slideshowSingleton) {
168
+ window.__slideshowSingleton = new SlideshowSingletonClass();
169
+ }
170
+ return window.__slideshowSingleton;
171
+ }
172
+ // =============================================================================
173
+ // React Context and Component
174
+ // =============================================================================
175
+ const SlideshowContext = createContext(null);
176
+ /**
177
+ * Slideshow component with auto-advancing image carousel and route persistence.
178
+ *
179
+ * The current slide index persists across Next.js route changes (tile ↔ page).
180
+ * When navigating between routes, the slideshow maintains its position.
181
+ *
182
+ * Usage:
183
+ * ```tsx
184
+ * <Slideshow images={images} className="w-full h-full">
185
+ * <YourOverlay />
186
+ * </Slideshow>
187
+ * ```
188
+ */
189
+ export function Slideshow({ images, intervalMs = 5000, autoAdvance = true, transition = 'fade', transitionDuration = 500, showDots = true, showArrows = true, swipeable = true, objectFit = 'cover', children, className = '', imageClassName = '', }) {
190
+ const [state, setState] = useState(() => getSlideshowSingleton().getState());
191
+ const containerRef = useRef(null);
192
+ const touchStartRef = useRef(null);
193
+ // Initialize and subscribe to singleton
194
+ useEffect(() => {
195
+ const singleton = getSlideshowSingleton();
196
+ // Initialize with images and options
197
+ singleton.initialize(images, {
198
+ intervalMs,
199
+ autoAdvance,
200
+ transitionDuration,
201
+ });
202
+ // Subscribe to state changes
203
+ const unsubscribe = singleton.onStateChange(setState);
204
+ return unsubscribe;
205
+ }, [images, intervalMs, autoAdvance, transitionDuration]);
206
+ // Control functions bound to singleton
207
+ const singleton = getSlideshowSingleton();
208
+ const controls = {
209
+ next: useCallback(() => singleton.next(), []),
210
+ prev: useCallback(() => singleton.prev(), []),
211
+ goTo: useCallback((index) => singleton.goTo(index), []),
212
+ pause: useCallback(() => singleton.pause(), []),
213
+ resume: useCallback(() => singleton.resume(), []),
214
+ toggle: useCallback(() => singleton.toggle(), []),
215
+ };
216
+ // Touch/swipe handlers
217
+ const handleTouchStart = useCallback((e) => {
218
+ if (!swipeable || state.totalSlides <= 1)
219
+ return;
220
+ const touch = e.touches[0];
221
+ touchStartRef.current = {
222
+ x: touch.clientX,
223
+ y: touch.clientY,
224
+ time: Date.now(),
60
225
  };
61
- }, [isPaused, intervalMs, next, totalSlides]);
226
+ }, [swipeable, state.totalSlides]);
227
+ const handleTouchEnd = useCallback((e) => {
228
+ if (!swipeable || !touchStartRef.current || state.isTransitioning)
229
+ return;
230
+ const touch = e.changedTouches[0];
231
+ const deltaX = touch.clientX - touchStartRef.current.x;
232
+ const deltaY = touch.clientY - touchStartRef.current.y;
233
+ const deltaTime = Date.now() - touchStartRef.current.time;
234
+ // Minimum swipe distance (50px) and max time (300ms for quick swipe, or slower with more distance)
235
+ const minSwipeDistance = 50;
236
+ const isHorizontalSwipe = Math.abs(deltaX) > Math.abs(deltaY);
237
+ const isValidSwipe = Math.abs(deltaX) > minSwipeDistance && isHorizontalSwipe;
238
+ const isQuickSwipe = deltaTime < 300 || Math.abs(deltaX) > 100;
239
+ if (isValidSwipe && isQuickSwipe) {
240
+ if (deltaX > 0) {
241
+ controls.prev();
242
+ }
243
+ else {
244
+ controls.next();
245
+ }
246
+ }
247
+ touchStartRef.current = null;
248
+ }, [swipeable, state.isTransitioning, controls]);
62
249
  // Get transition styles
63
250
  const getTransitionStyles = (index) => {
64
- const isActive = index === currentIndex;
251
+ const isActive = index === state.currentIndex;
65
252
  switch (transition) {
66
253
  case 'fade':
67
254
  return {
@@ -71,7 +258,7 @@ export function Slideshow({ images, intervalMs = 5000, autoAdvance = true, trans
71
258
  inset: 0,
72
259
  };
73
260
  case 'slide':
74
- const offset = (index - currentIndex) * 100;
261
+ const offset = (index - state.currentIndex) * 100;
75
262
  return {
76
263
  transform: `translateX(${offset}%)`,
77
264
  transition: `transform ${transitionDuration}ms ease-in-out`,
@@ -88,37 +275,33 @@ export function Slideshow({ images, intervalMs = 5000, autoAdvance = true, trans
88
275
  }
89
276
  };
90
277
  const contextValue = {
91
- state: {
92
- currentIndex,
93
- totalSlides,
94
- isTransitioning,
95
- isPaused,
96
- images,
97
- },
98
- controls: { next, prev, goTo, pause, resume, toggle },
278
+ state,
279
+ controls,
99
280
  };
100
281
  if (images.length === 0) {
101
282
  return (React.createElement("div", { className: `relative w-full h-full bg-black flex items-center justify-center ${className}` },
102
283
  React.createElement("p", { className: "text-white/60" }, "No images")));
103
284
  }
104
285
  return (React.createElement(SlideshowContext.Provider, { value: contextValue },
105
- React.createElement("div", { className: `relative w-full h-full bg-black overflow-hidden ${className}` },
106
- React.createElement("div", { className: "relative w-full h-full" }, images.map((image, index) => (React.createElement("div", { key: `${image.url}-${index}`, style: getTransitionStyles(index), className: imageClassName },
107
- React.createElement("img", { src: image.url, alt: image.alt || `Slide ${index + 1}`, className: "w-full h-full object-contain", loading: index === 0 ? 'eager' : 'lazy' }))))),
108
- showArrows && totalSlides > 1 && (React.createElement(React.Fragment, null,
109
- React.createElement("button", { onClick: prev, disabled: 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" },
286
+ React.createElement("div", { ref: containerRef, className: `relative w-full h-full bg-black overflow-hidden ${className}`, onTouchStart: handleTouchStart, onTouchEnd: handleTouchEnd },
287
+ React.createElement("div", { className: "relative w-full h-full" }, images.map((image, index) => (React.createElement("div", { key: `${image.url}-${index}`, style: getTransitionStyles(index), className: `flex items-center justify-center ${imageClassName}` },
288
+ React.createElement("img", { src: image.url, alt: image.alt || `Slide ${index + 1}`, className: `w-full h-full ${objectFit === 'cover' ? 'object-cover' : 'object-contain'}`, style: { objectPosition: 'center' }, loading: index === 0 ? 'eager' : 'lazy', draggable: false }))))),
289
+ showArrows && state.totalSlides > 1 && (React.createElement(React.Fragment, null,
290
+ 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" },
110
291
  React.createElement("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24" },
111
292
  React.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 19l-7-7 7-7" }))),
112
- React.createElement("button", { onClick: next, disabled: 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" },
293
+ 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" },
113
294
  React.createElement("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24" },
114
295
  React.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 5l7 7-7 7" }))))),
115
- showDots && 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: () => goTo(index), disabled: isTransitioning, className: `w-2 h-2 rounded-full transition-colors ${index === currentIndex
296
+ 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
116
297
  ? 'bg-white'
117
298
  : 'bg-white/40 hover:bg-white/60'}`, "aria-label": `Go to slide ${index + 1}` }))))),
118
- children)));
299
+ React.createElement("div", { className: "absolute inset-0 z-10 pointer-events-none" },
300
+ React.createElement("div", { className: "pointer-events-auto" }, children)))));
119
301
  }
120
302
  /**
121
303
  * Hook to access slideshow state and controls from within Slideshow children.
304
+ * Also aliased as useSlideshow for consistency with VideoPlayer.
122
305
  */
123
306
  export function useSlideshowState() {
124
307
  const context = useContext(SlideshowContext);
@@ -127,3 +310,5 @@ export function useSlideshowState() {
127
310
  }
128
311
  return context;
129
312
  }
313
+ // Alias for consistency with useVideo/useVideoState
314
+ export const useSlideshow = useSlideshowState;
@@ -1,7 +1,7 @@
1
1
  export { VideoPlayer, useVideoState, useVideo, // Alias for useVideoState (compatibility)
2
2
  useCuePoint, useCuePoints, useVideoProgress, } from './VideoPlayer';
3
3
  export type { VideoState, VideoControls, VideoContextValue, VideoPlayerProps, CuePoint, } from './VideoPlayer';
4
- export { Slideshow, useSlideshowState, } from './Slideshow';
4
+ export { Slideshow, useSlideshowState, useSlideshow, } from './Slideshow';
5
5
  export type { SlideImage, SlideshowState, SlideshowControls, SlideshowContextValue, SlideshowProps, } from './Slideshow';
6
6
  export { OverlaySlot, FullOverlay, GradientOverlay, } from './OverlaySlot';
7
7
  export type { SlotPosition, OverlaySlotProps, FullOverlayProps, GradientOverlayProps, } from './OverlaySlot';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/react/overlay/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,WAAW,EACX,aAAa,EACb,QAAQ,EAAE,0CAA0C;AACpD,WAAW,EACX,YAAY,EACZ,gBAAgB,GACjB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,gBAAgB,EAChB,QAAQ,GACT,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,SAAS,EACT,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,UAAU,EACV,cAAc,EACd,iBAAiB,EACjB,qBAAqB,EACrB,cAAc,GACf,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,WAAW,EACX,WAAW,EACX,eAAe,GAChB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/react/overlay/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,WAAW,EACX,aAAa,EACb,QAAQ,EAAE,0CAA0C;AACpD,WAAW,EACX,YAAY,EACZ,gBAAgB,GACjB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,gBAAgB,EAChB,QAAQ,GACT,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,SAAS,EACT,iBAAiB,EACjB,YAAY,GACb,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,UAAU,EACV,cAAc,EACd,iBAAiB,EACjB,qBAAqB,EACrB,cAAc,GACf,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,WAAW,EACX,WAAW,EACX,eAAe,GAChB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,eAAe,CAAC"}
@@ -1,7 +1,8 @@
1
1
  // Video player with HLS streaming support and route persistence
2
2
  export { VideoPlayer, useVideoState, useVideo, // Alias for useVideoState (compatibility)
3
3
  useCuePoint, useCuePoints, useVideoProgress, } from './VideoPlayer';
4
- // Image slideshow component
5
- export { Slideshow, useSlideshowState, } from './Slideshow';
4
+ // Image slideshow component with route persistence
5
+ export { Slideshow, useSlideshowState, useSlideshow, // Alias for useSlideshowState (consistency)
6
+ } from './Slideshow';
6
7
  // Overlay positioning components
7
8
  export { OverlaySlot, FullOverlay, GradientOverlay, } from './OverlaySlot';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thewhateverapp/tile-sdk",
3
- "version": "0.13.0",
3
+ "version": "0.13.2",
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",