@thewhateverapp/tile-sdk 0.10.1 → 0.11.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.
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +2 -0
- package/dist/react/overlay/OverlaySlot.d.ts +65 -0
- package/dist/react/overlay/OverlaySlot.d.ts.map +1 -0
- package/dist/react/overlay/OverlaySlot.js +93 -0
- package/dist/react/overlay/Slideshow.d.ts +57 -0
- package/dist/react/overlay/Slideshow.d.ts.map +1 -0
- package/dist/react/overlay/Slideshow.js +129 -0
- package/dist/react/overlay/VideoPlayer.d.ts +83 -0
- package/dist/react/overlay/VideoPlayer.d.ts.map +1 -0
- package/dist/react/overlay/VideoPlayer.js +181 -0
- package/dist/react/overlay/index.d.ts +7 -0
- package/dist/react/overlay/index.d.ts.map +1 -0
- package/dist/react/overlay/index.js +6 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export { useTileNavigation } from './react/useTileNavigation';
|
|
|
4
4
|
export { useKeyboard } from './react/useKeyboard';
|
|
5
5
|
export { TileContainer } from './react/TileContainer';
|
|
6
6
|
export { withTile } from './react/withTile';
|
|
7
|
+
export { VideoPlayer, useVideoState, Slideshow, useSlideshowState, OverlaySlot, FullOverlay, GradientOverlay, } from './react/overlay';
|
|
8
|
+
export type { VideoState, VideoControls, VideoContextValue, VideoPlayerProps, SlideImage, SlideshowState, SlideshowControls, SlideshowContextValue, SlideshowProps, SlotPosition, OverlaySlotProps, FullOverlayProps, GradientOverlayProps, } from './react/overlay';
|
|
7
9
|
export { getTileBridge, TileBridge } from './bridge/TileBridge';
|
|
8
10
|
export type { TileMessage, TileConfig, TileTokenData, KeyboardState } from './bridge/TileBridge';
|
|
9
11
|
export { StateClient } from './state/StateClient';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAChE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGjG,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"}
|
|
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,EAEb,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,MAAM,qBAAqB,CAAC;AAGjG,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"}
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,14 @@ export { useTileNavigation } from './react/useTileNavigation';
|
|
|
5
5
|
export { useKeyboard } from './react/useKeyboard';
|
|
6
6
|
export { TileContainer } from './react/TileContainer';
|
|
7
7
|
export { withTile } from './react/withTile';
|
|
8
|
+
// Overlay components for hybrid tiles (video/image with interactive overlays)
|
|
9
|
+
export {
|
|
10
|
+
// Video player
|
|
11
|
+
VideoPlayer, useVideoState,
|
|
12
|
+
// Slideshow
|
|
13
|
+
Slideshow, useSlideshowState,
|
|
14
|
+
// Positioning components
|
|
15
|
+
OverlaySlot, FullOverlay, GradientOverlay, } from './react/overlay';
|
|
8
16
|
// Bridge for secure communication
|
|
9
17
|
export { getTileBridge, TileBridge } from './bridge/TileBridge';
|
|
10
18
|
// State API client
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC3D,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC3D,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAItC,cAAc,WAAW,CAAC"}
|
package/dist/react/index.js
CHANGED
|
@@ -4,3 +4,5 @@ export { useKeyboard } from './useKeyboard';
|
|
|
4
4
|
export { TileContainer } from './TileContainer';
|
|
5
5
|
export { withTile } from './withTile';
|
|
6
6
|
// TileInitializer removed - router should be injected directly into TileProvider
|
|
7
|
+
// Overlay components for hybrid tiles (video/image with interactive overlays)
|
|
8
|
+
export * from './overlay';
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React, { type ReactNode } from 'react';
|
|
2
|
+
export type SlotPosition = 'top-left' | 'top-center' | 'top-right' | 'center-left' | 'center' | 'center-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
|
|
3
|
+
export interface OverlaySlotProps {
|
|
4
|
+
/** Position of the slot */
|
|
5
|
+
position: SlotPosition;
|
|
6
|
+
/** Children to render in the slot */
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
/** Additional class names */
|
|
9
|
+
className?: string;
|
|
10
|
+
/** Padding from edges (default: 12px) */
|
|
11
|
+
padding?: number | string;
|
|
12
|
+
/** Whether to use safe area insets (default: true) */
|
|
13
|
+
useSafeArea?: boolean;
|
|
14
|
+
/** Z-index for stacking (default: 10) */
|
|
15
|
+
zIndex?: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* OverlaySlot component for positioning overlay content.
|
|
19
|
+
* Use within VideoPlayer or Slideshow to position interactive elements.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```tsx
|
|
23
|
+
* <VideoPlayer src={hlsUrl}>
|
|
24
|
+
* <OverlaySlot position="top-right">
|
|
25
|
+
* <span className="bg-red-500 px-2 py-1 rounded text-xs">LIVE</span>
|
|
26
|
+
* </OverlaySlot>
|
|
27
|
+
* <OverlaySlot position="bottom-center">
|
|
28
|
+
* <PlayPauseButton />
|
|
29
|
+
* </OverlaySlot>
|
|
30
|
+
* </VideoPlayer>
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare function OverlaySlot({ position, children, className, padding, useSafeArea, zIndex, }: OverlaySlotProps): React.JSX.Element;
|
|
34
|
+
/**
|
|
35
|
+
* Full-screen overlay container that covers the entire media area.
|
|
36
|
+
* Use for modals, overlays that need to capture all interactions, etc.
|
|
37
|
+
*/
|
|
38
|
+
export interface FullOverlayProps {
|
|
39
|
+
children: ReactNode;
|
|
40
|
+
className?: string;
|
|
41
|
+
/** Background color/style (default: transparent) */
|
|
42
|
+
background?: string;
|
|
43
|
+
/** Z-index for stacking (default: 20) */
|
|
44
|
+
zIndex?: number;
|
|
45
|
+
/** Click handler for the overlay background */
|
|
46
|
+
onClick?: () => void;
|
|
47
|
+
}
|
|
48
|
+
export declare function FullOverlay({ children, className, background, zIndex, onClick, }: FullOverlayProps): React.JSX.Element;
|
|
49
|
+
/**
|
|
50
|
+
* Gradient overlay for text readability.
|
|
51
|
+
* Commonly used for bottom captions/controls on videos and images.
|
|
52
|
+
*/
|
|
53
|
+
export interface GradientOverlayProps {
|
|
54
|
+
/** Direction of gradient (default: 'to-top') */
|
|
55
|
+
direction?: 'to-top' | 'to-bottom' | 'to-left' | 'to-right';
|
|
56
|
+
/** Gradient start opacity 0-100 (default: 60) */
|
|
57
|
+
opacity?: number;
|
|
58
|
+
/** Height/width of gradient (default: '50%') */
|
|
59
|
+
size?: string;
|
|
60
|
+
/** Z-index for stacking (default: 5) */
|
|
61
|
+
zIndex?: number;
|
|
62
|
+
className?: string;
|
|
63
|
+
}
|
|
64
|
+
export declare function GradientOverlay({ direction, opacity, size, zIndex, className, }: GradientOverlayProps): React.JSX.Element;
|
|
65
|
+
//# sourceMappingURL=OverlaySlot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OverlaySlot.d.ts","sourceRoot":"","sources":["../../../src/react/overlay/OverlaySlot.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAE9C,MAAM,MAAM,YAAY,GACpB,UAAU,GACV,YAAY,GACZ,WAAW,GACX,aAAa,GACb,QAAQ,GACR,cAAc,GACd,aAAa,GACb,eAAe,GACf,cAAc,CAAC;AAEnB,MAAM,WAAW,gBAAgB;IAC/B,2BAA2B;IAC3B,QAAQ,EAAE,YAAY,CAAC;IACvB,qCAAqC;IACrC,QAAQ,EAAE,SAAS,CAAC;IACpB,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yCAAyC;IACzC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,sDAAsD;IACtD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,yCAAyC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AA8BD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,QAAQ,EACR,SAAc,EACd,OAAY,EACZ,WAAkB,EAClB,MAAW,GACZ,EAAE,gBAAgB,qBAwClB;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yCAAyC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,wBAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,SAAc,EACd,UAA0B,EAC1B,MAAW,EACX,OAAO,GACR,EAAE,gBAAgB,qBAUlB;AAED;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC,gDAAgD;IAChD,SAAS,CAAC,EAAE,QAAQ,GAAG,WAAW,GAAG,SAAS,GAAG,UAAU,CAAC;IAC5D,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,eAAe,CAAC,EAC9B,SAAoB,EACpB,OAAY,EACZ,IAAY,EACZ,MAAU,EACV,SAAc,GACf,EAAE,oBAAoB,qBAuBtB"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* Get Tailwind classes for slot position
|
|
5
|
+
*/
|
|
6
|
+
function getPositionClasses(position) {
|
|
7
|
+
switch (position) {
|
|
8
|
+
case 'top-left':
|
|
9
|
+
return 'top-0 left-0';
|
|
10
|
+
case 'top-center':
|
|
11
|
+
return 'top-0 left-1/2 -translate-x-1/2';
|
|
12
|
+
case 'top-right':
|
|
13
|
+
return 'top-0 right-0';
|
|
14
|
+
case 'center-left':
|
|
15
|
+
return 'top-1/2 left-0 -translate-y-1/2';
|
|
16
|
+
case 'center':
|
|
17
|
+
return 'top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2';
|
|
18
|
+
case 'center-right':
|
|
19
|
+
return 'top-1/2 right-0 -translate-y-1/2';
|
|
20
|
+
case 'bottom-left':
|
|
21
|
+
return 'bottom-0 left-0';
|
|
22
|
+
case 'bottom-center':
|
|
23
|
+
return 'bottom-0 left-1/2 -translate-x-1/2';
|
|
24
|
+
case 'bottom-right':
|
|
25
|
+
return 'bottom-0 right-0';
|
|
26
|
+
default:
|
|
27
|
+
return '';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* OverlaySlot component for positioning overlay content.
|
|
32
|
+
* Use within VideoPlayer or Slideshow to position interactive elements.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```tsx
|
|
36
|
+
* <VideoPlayer src={hlsUrl}>
|
|
37
|
+
* <OverlaySlot position="top-right">
|
|
38
|
+
* <span className="bg-red-500 px-2 py-1 rounded text-xs">LIVE</span>
|
|
39
|
+
* </OverlaySlot>
|
|
40
|
+
* <OverlaySlot position="bottom-center">
|
|
41
|
+
* <PlayPauseButton />
|
|
42
|
+
* </OverlaySlot>
|
|
43
|
+
* </VideoPlayer>
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export function OverlaySlot({ position, children, className = '', padding = 12, useSafeArea = true, zIndex = 10, }) {
|
|
47
|
+
const positionClasses = getPositionClasses(position);
|
|
48
|
+
const paddingValue = typeof padding === 'number' ? `${padding}px` : padding;
|
|
49
|
+
// Calculate safe area padding based on position
|
|
50
|
+
const getSafeAreaStyle = () => {
|
|
51
|
+
if (!useSafeArea)
|
|
52
|
+
return {};
|
|
53
|
+
const style = {};
|
|
54
|
+
if (position.includes('top')) {
|
|
55
|
+
style.paddingTop = `max(${paddingValue}, env(safe-area-inset-top, ${paddingValue}))`;
|
|
56
|
+
}
|
|
57
|
+
if (position.includes('bottom')) {
|
|
58
|
+
style.paddingBottom = `max(${paddingValue}, env(safe-area-inset-bottom, ${paddingValue}))`;
|
|
59
|
+
}
|
|
60
|
+
if (position.includes('left')) {
|
|
61
|
+
style.paddingLeft = `max(${paddingValue}, env(safe-area-inset-left, ${paddingValue}))`;
|
|
62
|
+
}
|
|
63
|
+
if (position.includes('right')) {
|
|
64
|
+
style.paddingRight = `max(${paddingValue}, env(safe-area-inset-right, ${paddingValue}))`;
|
|
65
|
+
}
|
|
66
|
+
return style;
|
|
67
|
+
};
|
|
68
|
+
return (React.createElement("div", { className: `absolute pointer-events-none ${positionClasses} ${className}`, style: {
|
|
69
|
+
padding: paddingValue,
|
|
70
|
+
zIndex,
|
|
71
|
+
...getSafeAreaStyle(),
|
|
72
|
+
} },
|
|
73
|
+
React.createElement("div", { className: "pointer-events-auto" }, children)));
|
|
74
|
+
}
|
|
75
|
+
export function FullOverlay({ children, className = '', background = 'transparent', zIndex = 20, onClick, }) {
|
|
76
|
+
return (React.createElement("div", { className: `absolute inset-0 ${className}`, style: { background, zIndex }, onClick: onClick }, children));
|
|
77
|
+
}
|
|
78
|
+
export function GradientOverlay({ direction = 'to-top', opacity = 60, size = '50%', zIndex = 5, className = '', }) {
|
|
79
|
+
const directionMap = {
|
|
80
|
+
'to-top': 'bg-gradient-to-t from-black to-transparent bottom-0',
|
|
81
|
+
'to-bottom': 'bg-gradient-to-b from-black to-transparent top-0',
|
|
82
|
+
'to-left': 'bg-gradient-to-l from-black to-transparent right-0',
|
|
83
|
+
'to-right': 'bg-gradient-to-r from-black to-transparent left-0',
|
|
84
|
+
};
|
|
85
|
+
const sizeStyle = direction === 'to-top' || direction === 'to-bottom'
|
|
86
|
+
? { height: size, width: '100%' }
|
|
87
|
+
: { width: size, height: '100%' };
|
|
88
|
+
return (React.createElement("div", { className: `absolute pointer-events-none ${directionMap[direction]} ${className}`, style: {
|
|
89
|
+
...sizeStyle,
|
|
90
|
+
opacity: opacity / 100,
|
|
91
|
+
zIndex,
|
|
92
|
+
} }));
|
|
93
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import React, { type ReactNode } from 'react';
|
|
2
|
+
export interface SlideImage {
|
|
3
|
+
url: string;
|
|
4
|
+
alt?: string;
|
|
5
|
+
caption?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface SlideshowState {
|
|
8
|
+
currentIndex: number;
|
|
9
|
+
totalSlides: number;
|
|
10
|
+
isTransitioning: boolean;
|
|
11
|
+
isPaused: boolean;
|
|
12
|
+
images: SlideImage[];
|
|
13
|
+
}
|
|
14
|
+
export interface SlideshowControls {
|
|
15
|
+
next: () => void;
|
|
16
|
+
prev: () => void;
|
|
17
|
+
goTo: (index: number) => void;
|
|
18
|
+
pause: () => void;
|
|
19
|
+
resume: () => void;
|
|
20
|
+
toggle: () => void;
|
|
21
|
+
}
|
|
22
|
+
export interface SlideshowContextValue {
|
|
23
|
+
state: SlideshowState;
|
|
24
|
+
controls: SlideshowControls;
|
|
25
|
+
}
|
|
26
|
+
export interface SlideshowProps {
|
|
27
|
+
/** Array of images to display */
|
|
28
|
+
images: SlideImage[];
|
|
29
|
+
/** Auto-advance interval in milliseconds (default: 5000) */
|
|
30
|
+
intervalMs?: number;
|
|
31
|
+
/** Auto-advance slides (default: true) */
|
|
32
|
+
autoAdvance?: boolean;
|
|
33
|
+
/** Transition type (default: 'fade') */
|
|
34
|
+
transition?: 'fade' | 'slide' | 'none';
|
|
35
|
+
/** Transition duration in ms (default: 500) */
|
|
36
|
+
transitionDuration?: number;
|
|
37
|
+
/** Show navigation dots (default: true) */
|
|
38
|
+
showDots?: boolean;
|
|
39
|
+
/** Show navigation arrows (default: true) */
|
|
40
|
+
showArrows?: boolean;
|
|
41
|
+
/** Children rendered as overlay */
|
|
42
|
+
children?: ReactNode;
|
|
43
|
+
/** Additional class names */
|
|
44
|
+
className?: string;
|
|
45
|
+
/** Image container class names */
|
|
46
|
+
imageClassName?: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Slideshow component with auto-advancing image carousel.
|
|
50
|
+
* Provides slideshow state and controls to child overlays via context.
|
|
51
|
+
*/
|
|
52
|
+
export declare function Slideshow({ images, intervalMs, autoAdvance, transition, transitionDuration, showDots, showArrows, children, className, imageClassName, }: SlideshowProps): React.JSX.Element;
|
|
53
|
+
/**
|
|
54
|
+
* Hook to access slideshow state and controls from within Slideshow children.
|
|
55
|
+
*/
|
|
56
|
+
export declare function useSlideshowState(): SlideshowContextValue;
|
|
57
|
+
//# sourceMappingURL=Slideshow.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import React, { createContext, useContext, useEffect, useRef, useState, useCallback, } from 'react';
|
|
3
|
+
const SlideshowContext = createContext(null);
|
|
4
|
+
/**
|
|
5
|
+
* Slideshow component with auto-advancing image carousel.
|
|
6
|
+
* Provides slideshow state and controls to child overlays via context.
|
|
7
|
+
*/
|
|
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)
|
|
17
|
+
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)
|
|
24
|
+
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)
|
|
31
|
+
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
|
+
}
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
intervalRef.current = setInterval(next, intervalMs);
|
|
55
|
+
return () => {
|
|
56
|
+
if (intervalRef.current) {
|
|
57
|
+
clearInterval(intervalRef.current);
|
|
58
|
+
intervalRef.current = null;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}, [isPaused, intervalMs, next, totalSlides]);
|
|
62
|
+
// Get transition styles
|
|
63
|
+
const getTransitionStyles = (index) => {
|
|
64
|
+
const isActive = index === currentIndex;
|
|
65
|
+
switch (transition) {
|
|
66
|
+
case 'fade':
|
|
67
|
+
return {
|
|
68
|
+
opacity: isActive ? 1 : 0,
|
|
69
|
+
transition: `opacity ${transitionDuration}ms ease-in-out`,
|
|
70
|
+
position: 'absolute',
|
|
71
|
+
inset: 0,
|
|
72
|
+
};
|
|
73
|
+
case 'slide':
|
|
74
|
+
const offset = (index - currentIndex) * 100;
|
|
75
|
+
return {
|
|
76
|
+
transform: `translateX(${offset}%)`,
|
|
77
|
+
transition: `transform ${transitionDuration}ms ease-in-out`,
|
|
78
|
+
position: 'absolute',
|
|
79
|
+
inset: 0,
|
|
80
|
+
};
|
|
81
|
+
case 'none':
|
|
82
|
+
default:
|
|
83
|
+
return {
|
|
84
|
+
opacity: isActive ? 1 : 0,
|
|
85
|
+
position: 'absolute',
|
|
86
|
+
inset: 0,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
const contextValue = {
|
|
91
|
+
state: {
|
|
92
|
+
currentIndex,
|
|
93
|
+
totalSlides,
|
|
94
|
+
isTransitioning,
|
|
95
|
+
isPaused,
|
|
96
|
+
images,
|
|
97
|
+
},
|
|
98
|
+
controls: { next, prev, goTo, pause, resume, toggle },
|
|
99
|
+
};
|
|
100
|
+
if (images.length === 0) {
|
|
101
|
+
return (React.createElement("div", { className: `relative w-full h-full bg-black flex items-center justify-center ${className}` },
|
|
102
|
+
React.createElement("p", { className: "text-white/60" }, "No images")));
|
|
103
|
+
}
|
|
104
|
+
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" },
|
|
110
|
+
React.createElement("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24" },
|
|
111
|
+
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" },
|
|
113
|
+
React.createElement("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24" },
|
|
114
|
+
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
|
|
116
|
+
? 'bg-white'
|
|
117
|
+
: 'bg-white/40 hover:bg-white/60'}`, "aria-label": `Go to slide ${index + 1}` }))))),
|
|
118
|
+
children)));
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Hook to access slideshow state and controls from within Slideshow children.
|
|
122
|
+
*/
|
|
123
|
+
export function useSlideshowState() {
|
|
124
|
+
const context = useContext(SlideshowContext);
|
|
125
|
+
if (!context) {
|
|
126
|
+
throw new Error('useSlideshowState must be used within a Slideshow');
|
|
127
|
+
}
|
|
128
|
+
return context;
|
|
129
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import React, { type ReactNode } from 'react';
|
|
2
|
+
interface HlsInstance {
|
|
3
|
+
loadSource: (url: string) => void;
|
|
4
|
+
attachMedia: (video: HTMLVideoElement) => void;
|
|
5
|
+
on: (event: string, callback: (...args: unknown[]) => void) => void;
|
|
6
|
+
startLoad: () => void;
|
|
7
|
+
recoverMediaError: () => void;
|
|
8
|
+
destroy: () => void;
|
|
9
|
+
}
|
|
10
|
+
interface HlsStatic {
|
|
11
|
+
isSupported: () => boolean;
|
|
12
|
+
Events: {
|
|
13
|
+
MANIFEST_PARSED: string;
|
|
14
|
+
ERROR: string;
|
|
15
|
+
};
|
|
16
|
+
ErrorTypes: {
|
|
17
|
+
NETWORK_ERROR: string;
|
|
18
|
+
MEDIA_ERROR: string;
|
|
19
|
+
};
|
|
20
|
+
new (config?: {
|
|
21
|
+
enableWorker?: boolean;
|
|
22
|
+
lowLatencyMode?: boolean;
|
|
23
|
+
}): HlsInstance;
|
|
24
|
+
}
|
|
25
|
+
declare global {
|
|
26
|
+
interface Window {
|
|
27
|
+
Hls: HlsStatic;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export interface VideoState {
|
|
31
|
+
isPlaying: boolean;
|
|
32
|
+
currentTime: number;
|
|
33
|
+
duration: number;
|
|
34
|
+
buffered: number;
|
|
35
|
+
volume: number;
|
|
36
|
+
muted: boolean;
|
|
37
|
+
isLoading: boolean;
|
|
38
|
+
error: string | null;
|
|
39
|
+
}
|
|
40
|
+
export interface VideoControls {
|
|
41
|
+
play: () => void;
|
|
42
|
+
pause: () => void;
|
|
43
|
+
toggle: () => void;
|
|
44
|
+
seek: (time: number) => void;
|
|
45
|
+
setVolume: (volume: number) => void;
|
|
46
|
+
setMuted: (muted: boolean) => void;
|
|
47
|
+
}
|
|
48
|
+
export interface VideoContextValue {
|
|
49
|
+
state: VideoState;
|
|
50
|
+
controls: VideoControls;
|
|
51
|
+
videoRef: React.RefObject<HTMLVideoElement>;
|
|
52
|
+
}
|
|
53
|
+
export interface VideoPlayerProps {
|
|
54
|
+
/** HLS playlist URL (m3u8) */
|
|
55
|
+
src: string;
|
|
56
|
+
/** Auto-start playback (default: true) */
|
|
57
|
+
autoplay?: boolean;
|
|
58
|
+
/** Loop video (default: false) */
|
|
59
|
+
loop?: boolean;
|
|
60
|
+
/** Start muted (default: true for autoplay compliance) */
|
|
61
|
+
muted?: boolean;
|
|
62
|
+
/** Poster image URL */
|
|
63
|
+
poster?: string;
|
|
64
|
+
/** Show native controls (default: false) */
|
|
65
|
+
controls?: boolean;
|
|
66
|
+
/** Children rendered as overlay */
|
|
67
|
+
children?: ReactNode;
|
|
68
|
+
/** Additional class names */
|
|
69
|
+
className?: string;
|
|
70
|
+
/** Video wrapper class names */
|
|
71
|
+
videoClassName?: string;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* VideoPlayer component with HLS streaming support.
|
|
75
|
+
* Provides video state and controls to child overlays via context.
|
|
76
|
+
*/
|
|
77
|
+
export declare function VideoPlayer({ src, autoplay, loop, muted, poster, controls, children, className, videoClassName, }: VideoPlayerProps): React.JSX.Element;
|
|
78
|
+
/**
|
|
79
|
+
* Hook to access video state and controls from within VideoPlayer children.
|
|
80
|
+
*/
|
|
81
|
+
export declare function useVideoState(): VideoContextValue;
|
|
82
|
+
export {};
|
|
83
|
+
//# sourceMappingURL=VideoPlayer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VideoPlayer.d.ts","sourceRoot":"","sources":["../../../src/react/overlay/VideoPlayer.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import React, { createContext, useContext, useEffect, useRef, useState, useCallback, } from 'react';
|
|
3
|
+
const VideoContext = createContext(null);
|
|
4
|
+
/**
|
|
5
|
+
* VideoPlayer component with HLS streaming support.
|
|
6
|
+
* Provides video state and controls to child overlays via context.
|
|
7
|
+
*/
|
|
8
|
+
export function VideoPlayer({ src, autoplay = true, loop = false, muted = true, poster, controls = false, children, className = '', videoClassName = '', }) {
|
|
9
|
+
const videoRef = useRef(null);
|
|
10
|
+
const hlsRef = useRef(null);
|
|
11
|
+
const [state, setState] = useState({
|
|
12
|
+
isPlaying: false,
|
|
13
|
+
currentTime: 0,
|
|
14
|
+
duration: 0,
|
|
15
|
+
buffered: 0,
|
|
16
|
+
volume: 1,
|
|
17
|
+
muted: muted,
|
|
18
|
+
isLoading: true,
|
|
19
|
+
error: null,
|
|
20
|
+
});
|
|
21
|
+
// Control functions
|
|
22
|
+
const play = useCallback(() => {
|
|
23
|
+
videoRef.current?.play().catch(() => { });
|
|
24
|
+
}, []);
|
|
25
|
+
const pause = useCallback(() => {
|
|
26
|
+
videoRef.current?.pause();
|
|
27
|
+
}, []);
|
|
28
|
+
const toggle = useCallback(() => {
|
|
29
|
+
if (videoRef.current?.paused) {
|
|
30
|
+
play();
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
pause();
|
|
34
|
+
}
|
|
35
|
+
}, [play, pause]);
|
|
36
|
+
const seek = useCallback((time) => {
|
|
37
|
+
if (videoRef.current) {
|
|
38
|
+
videoRef.current.currentTime = time;
|
|
39
|
+
}
|
|
40
|
+
}, []);
|
|
41
|
+
const setVolume = useCallback((volume) => {
|
|
42
|
+
if (videoRef.current) {
|
|
43
|
+
videoRef.current.volume = Math.max(0, Math.min(1, volume));
|
|
44
|
+
}
|
|
45
|
+
}, []);
|
|
46
|
+
const setMuted = useCallback((muted) => {
|
|
47
|
+
if (videoRef.current) {
|
|
48
|
+
videoRef.current.muted = muted;
|
|
49
|
+
}
|
|
50
|
+
}, []);
|
|
51
|
+
// Load HLS.js from CDN and initialize
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
const video = videoRef.current;
|
|
54
|
+
if (!video || !src)
|
|
55
|
+
return;
|
|
56
|
+
// Load HLS.js script if not already loaded
|
|
57
|
+
const loadHls = async () => {
|
|
58
|
+
if (!window.Hls) {
|
|
59
|
+
await new Promise((resolve, reject) => {
|
|
60
|
+
const script = document.createElement('script');
|
|
61
|
+
script.src = 'https://cdn.jsdelivr.net/npm/hls.js@1';
|
|
62
|
+
script.onload = () => resolve();
|
|
63
|
+
script.onerror = () => reject(new Error('Failed to load HLS.js'));
|
|
64
|
+
document.head.appendChild(script);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
const Hls = window.Hls;
|
|
68
|
+
if (Hls.isSupported()) {
|
|
69
|
+
const hls = new Hls({
|
|
70
|
+
enableWorker: true,
|
|
71
|
+
lowLatencyMode: false,
|
|
72
|
+
});
|
|
73
|
+
hlsRef.current = hls;
|
|
74
|
+
hls.loadSource(src);
|
|
75
|
+
hls.attachMedia(video);
|
|
76
|
+
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
|
77
|
+
setState(s => ({ ...s, isLoading: false }));
|
|
78
|
+
if (autoplay) {
|
|
79
|
+
video.play().catch(() => { });
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
hls.on(Hls.Events.ERROR, (_event, data) => {
|
|
83
|
+
const errorData = data;
|
|
84
|
+
if (errorData.fatal) {
|
|
85
|
+
switch (errorData.type) {
|
|
86
|
+
case Hls.ErrorTypes.NETWORK_ERROR:
|
|
87
|
+
hls.startLoad();
|
|
88
|
+
break;
|
|
89
|
+
case Hls.ErrorTypes.MEDIA_ERROR:
|
|
90
|
+
hls.recoverMediaError();
|
|
91
|
+
break;
|
|
92
|
+
default:
|
|
93
|
+
setState(s => ({ ...s, error: 'Failed to load video', isLoading: false }));
|
|
94
|
+
hls.destroy();
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
else if (video.canPlayType('application/vnd.apple.mpegurl')) {
|
|
101
|
+
// Native HLS support (Safari)
|
|
102
|
+
video.src = src;
|
|
103
|
+
video.addEventListener('loadedmetadata', () => {
|
|
104
|
+
setState(s => ({ ...s, isLoading: false }));
|
|
105
|
+
if (autoplay) {
|
|
106
|
+
video.play().catch(() => { });
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
setState(s => ({ ...s, error: 'HLS not supported', isLoading: false }));
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
loadHls().catch(error => {
|
|
115
|
+
setState(s => ({ ...s, error: error.message, isLoading: false }));
|
|
116
|
+
});
|
|
117
|
+
return () => {
|
|
118
|
+
hlsRef.current?.destroy();
|
|
119
|
+
hlsRef.current = null;
|
|
120
|
+
};
|
|
121
|
+
}, [src, autoplay]);
|
|
122
|
+
// Video event listeners
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
const video = videoRef.current;
|
|
125
|
+
if (!video)
|
|
126
|
+
return;
|
|
127
|
+
const handlePlay = () => setState(s => ({ ...s, isPlaying: true }));
|
|
128
|
+
const handlePause = () => setState(s => ({ ...s, isPlaying: false }));
|
|
129
|
+
const handleTimeUpdate = () => setState(s => ({ ...s, currentTime: video.currentTime }));
|
|
130
|
+
const handleDurationChange = () => setState(s => ({ ...s, duration: video.duration }));
|
|
131
|
+
const handleVolumeChange = () => setState(s => ({ ...s, volume: video.volume, muted: video.muted }));
|
|
132
|
+
const handleProgress = () => {
|
|
133
|
+
if (video.buffered.length > 0) {
|
|
134
|
+
setState(s => ({
|
|
135
|
+
...s,
|
|
136
|
+
buffered: video.buffered.end(video.buffered.length - 1),
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
const handleError = () => setState(s => ({ ...s, error: 'Video playback error', isLoading: false }));
|
|
141
|
+
video.addEventListener('play', handlePlay);
|
|
142
|
+
video.addEventListener('pause', handlePause);
|
|
143
|
+
video.addEventListener('timeupdate', handleTimeUpdate);
|
|
144
|
+
video.addEventListener('durationchange', handleDurationChange);
|
|
145
|
+
video.addEventListener('volumechange', handleVolumeChange);
|
|
146
|
+
video.addEventListener('progress', handleProgress);
|
|
147
|
+
video.addEventListener('error', handleError);
|
|
148
|
+
return () => {
|
|
149
|
+
video.removeEventListener('play', handlePlay);
|
|
150
|
+
video.removeEventListener('pause', handlePause);
|
|
151
|
+
video.removeEventListener('timeupdate', handleTimeUpdate);
|
|
152
|
+
video.removeEventListener('durationchange', handleDurationChange);
|
|
153
|
+
video.removeEventListener('volumechange', handleVolumeChange);
|
|
154
|
+
video.removeEventListener('progress', handleProgress);
|
|
155
|
+
video.removeEventListener('error', handleError);
|
|
156
|
+
};
|
|
157
|
+
}, []);
|
|
158
|
+
const contextValue = {
|
|
159
|
+
state,
|
|
160
|
+
controls: { play, pause, toggle, seek, setVolume, setMuted },
|
|
161
|
+
videoRef: videoRef,
|
|
162
|
+
};
|
|
163
|
+
return (React.createElement(VideoContext.Provider, { value: contextValue },
|
|
164
|
+
React.createElement("div", { className: `relative w-full h-full bg-black ${className}` },
|
|
165
|
+
React.createElement("video", { ref: videoRef, className: `w-full h-full object-contain ${videoClassName}`, poster: poster, loop: loop, muted: muted, controls: controls, playsInline: true }),
|
|
166
|
+
state.isLoading && (React.createElement("div", { className: "absolute inset-0 flex items-center justify-center" },
|
|
167
|
+
React.createElement("div", { className: "w-10 h-10 border-3 border-white/30 border-t-white rounded-full animate-spin" }))),
|
|
168
|
+
state.error && (React.createElement("div", { className: "absolute inset-0 flex items-center justify-center text-white/80" },
|
|
169
|
+
React.createElement("p", null, state.error))),
|
|
170
|
+
children)));
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Hook to access video state and controls from within VideoPlayer children.
|
|
174
|
+
*/
|
|
175
|
+
export function useVideoState() {
|
|
176
|
+
const context = useContext(VideoContext);
|
|
177
|
+
if (!context) {
|
|
178
|
+
throw new Error('useVideoState must be used within a VideoPlayer');
|
|
179
|
+
}
|
|
180
|
+
return context;
|
|
181
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { VideoPlayer, useVideoState, } from './VideoPlayer';
|
|
2
|
+
export type { VideoState, VideoControls, VideoContextValue, VideoPlayerProps, } from './VideoPlayer';
|
|
3
|
+
export { Slideshow, useSlideshowState, } from './Slideshow';
|
|
4
|
+
export type { SlideImage, SlideshowState, SlideshowControls, SlideshowContextValue, SlideshowProps, } from './Slideshow';
|
|
5
|
+
export { OverlaySlot, FullOverlay, GradientOverlay, } from './OverlaySlot';
|
|
6
|
+
export type { SlotPosition, OverlaySlotProps, FullOverlayProps, GradientOverlayProps, } from './OverlaySlot';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/react/overlay/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,WAAW,EACX,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,gBAAgB,GACjB,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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Video player with HLS streaming support
|
|
2
|
+
export { VideoPlayer, useVideoState, } from './VideoPlayer';
|
|
3
|
+
// Image slideshow component
|
|
4
|
+
export { Slideshow, useSlideshowState, } from './Slideshow';
|
|
5
|
+
// Overlay positioning components
|
|
6
|
+
export { OverlaySlot, FullOverlay, GradientOverlay, } from './OverlaySlot';
|