@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.
- package/dist/react/overlay/Slideshow.d.ts.map +1 -1
- package/dist/react/overlay/Slideshow.js +103 -16
- package/dist/templates/slideshow/PersistentSlideshow.tsx.template.d.ts +1 -1
- package/dist/templates/slideshow/PersistentSlideshow.tsx.template.d.ts.map +1 -1
- package/dist/templates/slideshow/PersistentSlideshow.tsx.template.js +25 -10
- package/package.json +1 -1
|
@@ -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;;OAEG;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;IAgCR;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAYxB;;OAEG;IACH,OAAO,CAAC,eAAe;IAQvB,IAAI,IAAI,IAAI;
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
253
|
-
const minSwipeDistance =
|
|
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 <
|
|
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()
|
|
269
|
-
container.addEventListener('touchstart', handleTouchStart, { passive:
|
|
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:
|
|
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 ? '
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
//
|
|
117
|
-
const minSwipeDistance =
|
|
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 <
|
|
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()
|
|
135
|
-
container.addEventListener('touchstart', handleTouchStart, { passive:
|
|
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:
|
|
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 ? '
|
|
233
|
+
style={{ touchAction: swipeable && state.totalSlides > 1 ? 'none' : 'auto' }}
|
|
219
234
|
>
|
|
220
235
|
{/* Image container */}
|
|
221
236
|
<div className="relative w-full h-full">
|