@thewhateverapp/tile-sdk 0.10.1 → 0.11.1

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.
@@ -49,8 +49,37 @@ export declare class TileBridge {
49
49
  private currentToken;
50
50
  private tokenExpiresAt;
51
51
  private keyboardState;
52
+ private baselineViewportHeight;
53
+ private keyboardDetectionEnabled;
54
+ private lastReportedKeyboardState;
55
+ private viewportDebounceTimer;
56
+ private readonly KEYBOARD_THRESHOLD;
57
+ private readonly DEBOUNCE_MS;
58
+ private inputFocused;
59
+ private focusedElement;
52
60
  constructor(expectedOrigin?: string, router?: NextRouter);
53
61
  private initialize;
62
+ /**
63
+ * Initialize keyboard detection using VisualViewport API and input focus tracking
64
+ * This runs inside the WebView to detect when the soft keyboard appears
65
+ */
66
+ private initializeKeyboardDetection;
67
+ /**
68
+ * Process viewport change and determine keyboard state
69
+ */
70
+ private processViewportChange;
71
+ /**
72
+ * Handle focusin events - input element gained focus
73
+ */
74
+ private handleFocusIn;
75
+ /**
76
+ * Handle focusout events - input element lost focus
77
+ */
78
+ private handleFocusOut;
79
+ /**
80
+ * Check if an element is an input-like element that would trigger keyboard
81
+ */
82
+ private isInputElement;
54
83
  private handleMessage;
55
84
  private handleConfig;
56
85
  private handleResponse;
@@ -1 +1 @@
1
- {"version":3,"file":"TileBridge.d.ts","sourceRoot":"","sources":["../../src/bridge/TileBridge.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"TileBridge.d.ts","sourceRoot":"","sources":["../../src/bridge/TileBridge.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,GAAG,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAGD,KAAK,UAAU,GAAG;IAChB,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB,CAAC;AAEF,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,gBAAgB,CAAmD;IAC3E,OAAO,CAAC,aAAa,CAAoD;IACzE,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,MAAM,CAA2B;IAGzC,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,cAAc,CAAqB;IAG3C,OAAO,CAAC,aAAa,CAAgD;IAGrE,OAAO,CAAC,sBAAsB,CAAa;IAC3C,OAAO,CAAC,wBAAwB,CAAkB;IAClD,OAAO,CAAC,yBAAyB,CAAuE;IACxG,OAAO,CAAC,qBAAqB,CAA8C;IAC3E,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAO;IAC1C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAO;IAGnC,OAAO,CAAC,YAAY,CAAkB;IACtC,OAAO,CAAC,cAAc,CAAwB;gBAElC,cAAc,GAAE,MAAkC,EAAE,MAAM,CAAC,EAAE,UAAU;IA6BnF,OAAO,CAAC,UAAU;IAoClB;;;OAGG;IACH,OAAO,CAAC,2BAA2B;IAwDnC;;OAEG;IACH,OAAO,CAAC,qBAAqB;IA0C7B;;OAEG;IACH,OAAO,CAAC,aAAa;IA+BrB;;OAEG;IACH,OAAO,CAAC,cAAc;IAgCtB;;OAEG;IACH,OAAO,CAAC,cAAc;IAuBtB,OAAO,CAAC,aAAa;IAyDrB,OAAO,CAAC,YAAY;IAgBpB,OAAO,CAAC,cAAc;IAUtB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,oBAAoB;IAoB5B;;OAEG;IACH,OAAO,CAAC,WAAW;IAuBnB;;OAEG;IACH,OAAO,CAAC,cAAc;IAmBtB,OAAO,CAAC,YAAY;IAmCpB;;;;;OAKG;IACI,cAAc,IAAI,IAAI;IAuD7B;;;;;OAKG;IACI,cAAc,IAAI,IAAI;IAmD7B;;;OAGG;IACI,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAI1C;;OAEG;IACI,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,QAAQ,GAAG,OAAkB,GAAG,IAAI;IAOxE;;OAEG;IACI,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI;IAOtD;;;OAGG;IACI,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAOpD;;OAEG;IACU,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4B1D;;OAEG;IACU,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IA2B/D;;OAEG;IACU,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAoBlD;;OAEG;IACI,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAWzD;;OAEG;IACU,OAAO,CAAC,OAAO,CAAC,EAAE;QAC7B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;QAClB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,GAAG,CAAC;IAiChB;;OAEG;IACU,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IA8B9D;;OAEG;IACU,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC;IAyB3C;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAoCzB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAgCxB;;;;;OAKG;IACU,YAAY,CAAC,OAAO,CAAC,EAAE;QAClC,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,MAAM,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,KAAK,CAAC;KACvC,GAAG,OAAO,CAAC;QACV,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IA+CF;;;;;OAKG;IACU,UAAU,CAAC,OAAO,CAAC,EAAE;QAChC,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC;QACV,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IAwCF;;;;;OAKG;IACU,WAAW,CAAC,OAAO,CAAC,EAAE;QACjC,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,KAAK,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;IA6CH;;OAEG;IACI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,GAAG,MAAM,IAAI;IAgBlE;;OAEG;IACI,SAAS,IAAI,UAAU,GAAG,IAAI;IAIrC;;OAEG;IACI,OAAO,IAAI,OAAO;IAMzB;;;OAGG;IACI,QAAQ,IAAI,MAAM,GAAG,IAAI;IAWhC;;;OAGG;IACI,YAAY,IAAI,aAAa,GAAG,IAAI;IAU3C;;OAEG;IACI,aAAa,IAAI,OAAO;IAO/B;;;;OAIG;IACU,YAAY,CAAC,SAAS,GAAE,MAAc,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAyC5E;;;OAGG;IACI,aAAa,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,IAAI,CAAA;KAAE,KAAK,IAAI,GAAG,MAAM,IAAI;IAM7F;;;OAGG;IACI,gBAAgB,IAAI,aAAa;IAIxC;;OAEG;IACI,iBAAiB,IAAI,OAAO;IAInC;;OAEG;IACI,iBAAiB,IAAI,MAAM;IAIlC;;;;;OAKG;IACI,gBAAgB,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,GAAG,MAAM,IAAI;IAI5E;;;;;OAKG;IACU,YAAY,IAAI,OAAO,CAAC,UAAU,CAAC;IAuBhD,OAAO,CAAC,SAAS;IAOjB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,aAAa;CAgCtB;AAKD,wBAAgB,aAAa,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,UAAU,CAQ7D"}
@@ -15,6 +15,16 @@ export class TileBridge {
15
15
  this.tokenExpiresAt = null;
16
16
  // Keyboard state from parent (mobile app)
17
17
  this.keyboardState = { visible: false, height: 0 };
18
+ // VisualViewport tracking for keyboard detection (Plan A)
19
+ this.baselineViewportHeight = 0;
20
+ this.keyboardDetectionEnabled = false;
21
+ this.lastReportedKeyboardState = { visible: false, height: 0 };
22
+ this.viewportDebounceTimer = null;
23
+ this.KEYBOARD_THRESHOLD = 150; // Minimum height change to consider keyboard visible
24
+ this.DEBOUNCE_MS = 100; // Debounce viewport events
25
+ // Input focus tracking (Plan B)
26
+ this.inputFocused = false;
27
+ this.focusedElement = null;
18
28
  this.parentOrigin = expectedOrigin;
19
29
  this.router = router || null;
20
30
  // Auto-detect parent origin based on environment
@@ -66,6 +76,182 @@ export class TileBridge {
66
76
  this.ready = true;
67
77
  // Send ready signal to parent
68
78
  this.sendToParent({ type: 'tile:ready' });
79
+ // Initialize keyboard detection (Plan A: VisualViewport + Plan B: Input Focus)
80
+ this.initializeKeyboardDetection();
81
+ }
82
+ /**
83
+ * Initialize keyboard detection using VisualViewport API and input focus tracking
84
+ * This runs inside the WebView to detect when the soft keyboard appears
85
+ */
86
+ initializeKeyboardDetection() {
87
+ if (typeof window === 'undefined')
88
+ return;
89
+ // Only enable in iframe context (when running in mobile WebView)
90
+ if (!this.isInIframe()) {
91
+ if (this.isDevelopment()) {
92
+ console.log('[TileBridge] Keyboard detection skipped - not in iframe');
93
+ }
94
+ return;
95
+ }
96
+ this.keyboardDetectionEnabled = true;
97
+ // ==================== PLAN A: VisualViewport Detection ====================
98
+ // Use VisualViewport API to detect keyboard by monitoring viewport height changes
99
+ const vv = window.visualViewport;
100
+ if (vv) {
101
+ // Set baseline height (full viewport without keyboard)
102
+ this.baselineViewportHeight = vv.height;
103
+ if (this.isDevelopment() || this.isPreview()) {
104
+ console.log('[TileBridge] ⌨️ Keyboard detection initialized', {
105
+ baselineHeight: this.baselineViewportHeight,
106
+ hasVisualViewport: true,
107
+ });
108
+ }
109
+ // Handler for viewport resize events
110
+ const handleViewportChange = () => {
111
+ if (!this.keyboardDetectionEnabled)
112
+ return;
113
+ // Debounce rapid events
114
+ if (this.viewportDebounceTimer) {
115
+ clearTimeout(this.viewportDebounceTimer);
116
+ }
117
+ this.viewportDebounceTimer = setTimeout(() => {
118
+ this.processViewportChange();
119
+ }, this.DEBOUNCE_MS);
120
+ };
121
+ // Subscribe to resize and scroll events on visualViewport
122
+ vv.addEventListener('resize', handleViewportChange);
123
+ vv.addEventListener('scroll', handleViewportChange);
124
+ }
125
+ else {
126
+ if (this.isDevelopment() || this.isPreview()) {
127
+ console.log('[TileBridge] ⌨️ VisualViewport not available, using focus-only detection');
128
+ }
129
+ }
130
+ // ==================== PLAN B: Input Focus Detection ====================
131
+ // Track when input elements are focused/blurred
132
+ document.addEventListener('focusin', this.handleFocusIn.bind(this));
133
+ document.addEventListener('focusout', this.handleFocusOut.bind(this));
134
+ }
135
+ /**
136
+ * Process viewport change and determine keyboard state
137
+ */
138
+ processViewportChange() {
139
+ const vv = window.visualViewport;
140
+ if (!vv)
141
+ return;
142
+ const currentHeight = vv.height;
143
+ const heightDiff = this.baselineViewportHeight - currentHeight;
144
+ const keyboardVisible = heightDiff > this.KEYBOARD_THRESHOLD;
145
+ const keyboardHeight = keyboardVisible ? Math.round(heightDiff) : 0;
146
+ // Only report if state changed
147
+ if (this.lastReportedKeyboardState.visible !== keyboardVisible ||
148
+ Math.abs(this.lastReportedKeyboardState.height - keyboardHeight) > 10) {
149
+ this.lastReportedKeyboardState = { visible: keyboardVisible, height: keyboardHeight };
150
+ if (this.isDevelopment() || this.isPreview()) {
151
+ console.log('[TileBridge] ⌨️ Keyboard state changed (viewport)', {
152
+ visible: keyboardVisible,
153
+ height: keyboardHeight,
154
+ baselineHeight: this.baselineViewportHeight,
155
+ currentHeight,
156
+ });
157
+ }
158
+ // Send keyboard state to parent (React Native)
159
+ this.sendToParent({
160
+ type: 'tile:keyboard',
161
+ payload: {
162
+ visible: keyboardVisible,
163
+ height: keyboardHeight,
164
+ source: 'viewport',
165
+ },
166
+ });
167
+ }
168
+ // Update baseline when keyboard is hidden (to handle orientation changes)
169
+ if (!keyboardVisible && currentHeight > this.baselineViewportHeight) {
170
+ this.baselineViewportHeight = currentHeight;
171
+ }
172
+ }
173
+ /**
174
+ * Handle focusin events - input element gained focus
175
+ */
176
+ handleFocusIn(event) {
177
+ const target = event.target;
178
+ if (!target)
179
+ return;
180
+ // Check if the focused element is an input-like element
181
+ const isInputLike = this.isInputElement(target);
182
+ if (isInputLike && !this.inputFocused) {
183
+ this.inputFocused = true;
184
+ this.focusedElement = target;
185
+ if (this.isDevelopment() || this.isPreview()) {
186
+ console.log('[TileBridge] ⌨️ Input focused', {
187
+ tagName: target.tagName,
188
+ type: target.type,
189
+ id: target.id,
190
+ });
191
+ }
192
+ // Send focus event to parent
193
+ this.sendToParent({
194
+ type: 'tile:input-focused',
195
+ payload: {
196
+ tagName: target.tagName.toLowerCase(),
197
+ type: target.type || null,
198
+ id: target.id || null,
199
+ },
200
+ });
201
+ }
202
+ }
203
+ /**
204
+ * Handle focusout events - input element lost focus
205
+ */
206
+ handleFocusOut(event) {
207
+ const target = event.target;
208
+ if (!target)
209
+ return;
210
+ // Check if the blurred element is the one we're tracking
211
+ const isInputLike = this.isInputElement(target);
212
+ if (isInputLike && this.inputFocused) {
213
+ // Use setTimeout to check if focus moved to another input
214
+ // If relatedTarget is another input, we stay in "focused" state
215
+ setTimeout(() => {
216
+ const activeElement = document.activeElement;
217
+ const stillFocused = activeElement && this.isInputElement(activeElement);
218
+ if (!stillFocused) {
219
+ this.inputFocused = false;
220
+ this.focusedElement = null;
221
+ if (this.isDevelopment() || this.isPreview()) {
222
+ console.log('[TileBridge] ⌨️ Input blurred');
223
+ }
224
+ // Send blur event to parent
225
+ this.sendToParent({
226
+ type: 'tile:input-blurred',
227
+ payload: {},
228
+ });
229
+ }
230
+ }, 10);
231
+ }
232
+ }
233
+ /**
234
+ * Check if an element is an input-like element that would trigger keyboard
235
+ */
236
+ isInputElement(element) {
237
+ const tagName = element.tagName.toLowerCase();
238
+ // Standard input elements
239
+ if (tagName === 'textarea')
240
+ return true;
241
+ if (tagName === 'select')
242
+ return false; // Select doesn't trigger keyboard
243
+ if (tagName === 'input') {
244
+ const inputType = element.type?.toLowerCase();
245
+ // These input types trigger the keyboard
246
+ const keyboardTypes = ['text', 'email', 'password', 'search', 'tel', 'url', 'number'];
247
+ return !inputType || keyboardTypes.includes(inputType);
248
+ }
249
+ // Contenteditable elements
250
+ if (element.hasAttribute('contenteditable')) {
251
+ const value = element.getAttribute('contenteditable');
252
+ return value === 'true' || value === '';
253
+ }
254
+ return false;
69
255
  }
70
256
  handleMessage(event) {
71
257
  // Validate origin (skip validation in dev and preview)
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';
@@ -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
@@ -4,4 +4,5 @@ export { useTile } from './useTile';
4
4
  export { useKeyboard } from './useKeyboard';
5
5
  export { TileContainer } from './TileContainer';
6
6
  export { withTile } from './withTile';
7
+ export * from './overlay';
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -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"}
@@ -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';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thewhateverapp/tile-sdk",
3
- "version": "0.10.1",
3
+ "version": "0.11.1",
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",