@shortkitsdk/react-native 0.2.24 → 0.2.26
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/README.md +151 -0
- package/android/libs/shortkit-release.aar +0 -0
- package/android/src/main/java/com/shortkit/reactnative/ReactOverlayHost.kt +19 -1
- package/android/src/main/java/com/shortkit/reactnative/ShortKitModule.kt +43 -0
- package/ios/ReactCarouselOverlayHost.swift +51 -3
- package/ios/ReactOverlayHost.swift +67 -7
- package/ios/ReactVideoCarouselOverlayHost.swift +181 -19
- package/ios/SKFabricSurfaceWrapper.mm +7 -1
- package/ios/ShortKitBridge.swift +85 -5
- package/ios/ShortKitFeedView.swift +70 -4
- package/ios/ShortKitFeedViewManager.mm +3 -0
- package/ios/ShortKitModule.mm +46 -3
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Info.plist +2 -2
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.abi.json +5273 -337
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +151 -7
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftinterface +151 -7
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/ShortKitSDK +0 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/_CodeSignature/CodeResources +9 -9
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Info.plist +2 -2
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +5273 -337
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +151 -7
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +151 -7
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.abi.json +5273 -337
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +151 -7
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +151 -7
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/ShortKitSDK +0 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/_CodeSignature/CodeResources +17 -17
- package/package.json +1 -1
- package/src/ShortKitCarouselOverlaySurface.tsx +38 -10
- package/src/ShortKitCommands.ts +4 -0
- package/src/ShortKitFeed.tsx +65 -10
- package/src/ShortKitOverlaySurface.tsx +59 -23
- package/src/ShortKitProvider.tsx +2 -1
- package/src/ShortKitVideoCarouselOverlaySurface.tsx +51 -5
- package/src/index.ts +2 -0
- package/src/serialization.ts +37 -1
- package/src/specs/NativeShortKitModule.ts +68 -3
- package/src/specs/ShortKitFeedViewNativeComponent.ts +11 -0
- package/src/types.ts +85 -3
- package/src/useShortKitCarousel.ts +80 -0
|
@@ -96,6 +96,12 @@ type RefreshStateChangedEvent = Readonly<{
|
|
|
96
96
|
progress: Double;
|
|
97
97
|
}>;
|
|
98
98
|
|
|
99
|
+
type RefreshStateChangedPerFeedEvent = Readonly<{
|
|
100
|
+
feedId: string;
|
|
101
|
+
status: string;
|
|
102
|
+
progress: Double;
|
|
103
|
+
}>;
|
|
104
|
+
|
|
99
105
|
type DidFetchContentItemsEvent = Readonly<{
|
|
100
106
|
items: string; // JSON-serialized ContentItem[]
|
|
101
107
|
}>;
|
|
@@ -109,6 +115,13 @@ type FeedReadyEvent = Readonly<{
|
|
|
109
115
|
feedId: string;
|
|
110
116
|
}>;
|
|
111
117
|
|
|
118
|
+
type VideoCarouselCellTapEvent = Readonly<{
|
|
119
|
+
feedId: string;
|
|
120
|
+
id: string;
|
|
121
|
+
index: Int32;
|
|
122
|
+
pageIndex: Int32;
|
|
123
|
+
}>;
|
|
124
|
+
|
|
112
125
|
type DownloadStartedEvent = Readonly<{
|
|
113
126
|
itemId: string;
|
|
114
127
|
}>;
|
|
@@ -177,12 +190,38 @@ type CarouselActiveImageEvent = Readonly<{
|
|
|
177
190
|
activeImageIndex: Int32;
|
|
178
191
|
}>;
|
|
179
192
|
|
|
193
|
+
type CarouselItemChangedEvent = Readonly<{
|
|
194
|
+
surfaceId: string;
|
|
195
|
+
item: string; // JSON-serialized ImageCarouselItem (with local file:// URLs where cached)
|
|
196
|
+
isActive: boolean;
|
|
197
|
+
activeImageIndex: Int32;
|
|
198
|
+
}>;
|
|
199
|
+
|
|
180
200
|
type VideoCarouselActiveVideoEvent = Readonly<{
|
|
181
201
|
surfaceId: string;
|
|
182
202
|
activeVideo: string;
|
|
183
203
|
activeVideoIndex: Int32;
|
|
184
204
|
}>;
|
|
185
205
|
|
|
206
|
+
type VideoCarouselItemChangedEvent = Readonly<{
|
|
207
|
+
surfaceId: string;
|
|
208
|
+
carouselItem: string; // JSON-serialized VideoCarouselItem
|
|
209
|
+
activeVideo: string | null; // JSON-serialized ContentItem; null when item has no videos
|
|
210
|
+
activeVideoIndex: Int32;
|
|
211
|
+
isActive: boolean;
|
|
212
|
+
playerState: string;
|
|
213
|
+
isMuted: boolean;
|
|
214
|
+
}>;
|
|
215
|
+
|
|
216
|
+
type CarouselActiveVideoCompletedEvent = Readonly<{
|
|
217
|
+
surfaceId: string;
|
|
218
|
+
contentItem: string; // JSON-serialized ContentItem
|
|
219
|
+
indexInCarousel: Int32;
|
|
220
|
+
carouselItem: string; // JSON-serialized VideoCarouselItem
|
|
221
|
+
wasLast: boolean;
|
|
222
|
+
willAutoAdvance: boolean;
|
|
223
|
+
}>;
|
|
224
|
+
|
|
186
225
|
type OverlayFullStateEvent = Readonly<{
|
|
187
226
|
surfaceId: string;
|
|
188
227
|
isActive: boolean;
|
|
@@ -190,8 +229,20 @@ type OverlayFullStateEvent = Readonly<{
|
|
|
190
229
|
isMuted: boolean;
|
|
191
230
|
playbackRate: Double;
|
|
192
231
|
captionsEnabled: boolean;
|
|
193
|
-
activeCue: string;
|
|
194
|
-
feedScrollPhase: string;
|
|
232
|
+
activeCue: string | null;
|
|
233
|
+
feedScrollPhase: string | null;
|
|
234
|
+
}>;
|
|
235
|
+
|
|
236
|
+
type OverlayItemChangedEvent = Readonly<{
|
|
237
|
+
surfaceId: string;
|
|
238
|
+
item: string; // JSON-serialized ContentItem
|
|
239
|
+
isActive: boolean;
|
|
240
|
+
playerState: string;
|
|
241
|
+
isMuted: boolean;
|
|
242
|
+
playbackRate: Double;
|
|
243
|
+
captionsEnabled: boolean;
|
|
244
|
+
activeCue: string | null;
|
|
245
|
+
feedScrollPhase: string | null;
|
|
195
246
|
}>;
|
|
196
247
|
|
|
197
248
|
export interface Spec extends TurboModule {
|
|
@@ -217,6 +268,9 @@ export interface Spec extends TurboModule {
|
|
|
217
268
|
seekAndPlay(seconds: Double): void;
|
|
218
269
|
skipToNext(): void;
|
|
219
270
|
skipToPrevious(): void;
|
|
271
|
+
carouselNext(): boolean;
|
|
272
|
+
carouselPrevious(): boolean;
|
|
273
|
+
carouselSetActiveIndex(index: Int32): boolean;
|
|
220
274
|
setMuted(muted: boolean): void;
|
|
221
275
|
setPlaybackRate(rate: Double): void;
|
|
222
276
|
setCaptionsEnabled(enabled: boolean): void;
|
|
@@ -225,7 +279,8 @@ export interface Spec extends TurboModule {
|
|
|
225
279
|
setMaxBitrate(bitrate: Double): void;
|
|
226
280
|
|
|
227
281
|
// --- Custom feed ---
|
|
228
|
-
setFeedItems(feedId: string, items: string): void;
|
|
282
|
+
setFeedItems(feedId: string, items: string, startAtId: string | null): void;
|
|
283
|
+
scrollFeedToItem(feedId: string, id: string, animated: boolean): void;
|
|
229
284
|
appendFeedItems(feedId: string, items: string): void;
|
|
230
285
|
fetchContent(limit: Int32, filterJSON: string | null): Promise<string>;
|
|
231
286
|
applyFilter(feedId: string, filterJSON: string | null): void;
|
|
@@ -239,6 +294,10 @@ export interface Spec extends TurboModule {
|
|
|
239
294
|
downloadVideo(itemId: string, mode: string): Promise<string>;
|
|
240
295
|
cancelDownload(): void;
|
|
241
296
|
|
|
297
|
+
// --- Carousel accessors ---
|
|
298
|
+
getCarouselActiveIndex(): Int32;
|
|
299
|
+
getCarouselVideoCount(): Int32;
|
|
300
|
+
|
|
242
301
|
// --- Event emitters ---
|
|
243
302
|
readonly onPlayerStateChanged: EventEmitter<PlayerStateEvent>;
|
|
244
303
|
readonly onCurrentItemChanged: EventEmitter<CurrentItemEvent>;
|
|
@@ -257,8 +316,10 @@ export interface Spec extends TurboModule {
|
|
|
257
316
|
readonly onContentTapped: EventEmitter<ContentTappedEvent>;
|
|
258
317
|
readonly onDismiss: EventEmitter<DismissEvent>;
|
|
259
318
|
readonly onRefreshStateChanged: EventEmitter<RefreshStateChangedEvent>;
|
|
319
|
+
readonly onRefreshStateChangedPerFeed: EventEmitter<RefreshStateChangedPerFeedEvent>;
|
|
260
320
|
readonly onDidFetchContentItems: EventEmitter<DidFetchContentItemsEvent>;
|
|
261
321
|
readonly onFeedReady: EventEmitter<FeedReadyEvent>;
|
|
322
|
+
readonly onVideoCarouselCellTap: EventEmitter<VideoCarouselCellTapEvent>;
|
|
262
323
|
|
|
263
324
|
// --- Overlay per-surface events ---
|
|
264
325
|
readonly onOverlayActiveChanged: EventEmitter<OverlayActiveEvent>;
|
|
@@ -270,8 +331,12 @@ export interface Spec extends TurboModule {
|
|
|
270
331
|
readonly onOverlayFeedScrollPhaseChanged: EventEmitter<OverlayFeedScrollPhaseEvent>;
|
|
271
332
|
readonly onOverlayTimeUpdate: EventEmitter<OverlayTimeUpdateEvent>;
|
|
272
333
|
readonly onOverlayFullState: EventEmitter<OverlayFullStateEvent>;
|
|
334
|
+
readonly onOverlayItemChanged: EventEmitter<OverlayItemChangedEvent>;
|
|
273
335
|
readonly onCarouselActiveImageChanged: EventEmitter<CarouselActiveImageEvent>;
|
|
336
|
+
readonly onCarouselItemChanged: EventEmitter<CarouselItemChangedEvent>;
|
|
274
337
|
readonly onVideoCarouselActiveVideoChanged: EventEmitter<VideoCarouselActiveVideoEvent>;
|
|
338
|
+
readonly onVideoCarouselItemChanged: EventEmitter<VideoCarouselItemChangedEvent>;
|
|
339
|
+
readonly onCarouselActiveVideoCompleted: EventEmitter<CarouselActiveVideoCompletedEvent>;
|
|
275
340
|
|
|
276
341
|
// --- Download events ---
|
|
277
342
|
readonly onDownloadStarted: EventEmitter<DownloadStartedEvent>;
|
|
@@ -6,6 +6,17 @@ export interface NativeProps extends ViewProps {
|
|
|
6
6
|
feedId?: string;
|
|
7
7
|
startAtItemId?: string;
|
|
8
8
|
preloadId?: string;
|
|
9
|
+
/**
|
|
10
|
+
* URL of a thumbnail already rendered/cached on the host side (e.g. the
|
|
11
|
+
* grid tile the user tapped to open this feed). When expo-image or FastImage
|
|
12
|
+
* is used on iOS, SDK fetches the decoded UIImage from SDWebImage's memory
|
|
13
|
+
* cache and paints it onto the first cell with zero network and zero
|
|
14
|
+
* latency. Falls back to network fetch for non-SDWebImage clients.
|
|
15
|
+
*/
|
|
16
|
+
seedThumbnailUrl?: string;
|
|
17
|
+
/** JSON-serialized FeedInput[] for initial custom-feed items at mount time. */
|
|
18
|
+
feedItemsJSON?: string;
|
|
19
|
+
active?: boolean;
|
|
9
20
|
}
|
|
10
21
|
|
|
11
22
|
export default codegenNativeComponent<NativeProps>(
|
package/src/types.ts
CHANGED
|
@@ -113,6 +113,8 @@ export interface VideoCarouselItem {
|
|
|
113
113
|
articleUrl?: string;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
export type ContentOrigin = 'ios_upload' | 'other';
|
|
117
|
+
|
|
116
118
|
/**
|
|
117
119
|
* Per-slide input for a VideoCarouselInput. Mirrors the `{ type: 'video' }`
|
|
118
120
|
* FeedInput variant — host apps pass a playback ID and the SDK constructs
|
|
@@ -120,6 +122,7 @@ export interface VideoCarouselItem {
|
|
|
120
122
|
*/
|
|
121
123
|
export interface VideoCarouselVideoInput {
|
|
122
124
|
playbackId: string;
|
|
125
|
+
origin?: ContentOrigin;
|
|
123
126
|
fallbackUrl?: string;
|
|
124
127
|
}
|
|
125
128
|
|
|
@@ -142,7 +145,7 @@ export interface VideoCarouselInput {
|
|
|
142
145
|
}
|
|
143
146
|
|
|
144
147
|
export type FeedInput =
|
|
145
|
-
| { type: 'video'; playbackId: string; fallbackUrl?: string }
|
|
148
|
+
| { type: 'video'; playbackId: string; origin?: ContentOrigin; fallbackUrl?: string }
|
|
146
149
|
| { type: 'imageCarousel'; item: ImageCarouselItem }
|
|
147
150
|
| { type: 'videoCarousel'; item: VideoCarouselInput };
|
|
148
151
|
|
|
@@ -300,6 +303,45 @@ export interface ShortKitFeedProps {
|
|
|
300
303
|
style?: ViewStyle;
|
|
301
304
|
/** Item ID to scroll to on initial load. */
|
|
302
305
|
startAtItemId?: string;
|
|
306
|
+
/**
|
|
307
|
+
* URL of the thumbnail already displayed in your UI (e.g. the grid tile
|
|
308
|
+
* the user just tapped). If your app uses expo-image or FastImage, the SDK
|
|
309
|
+
* will extract the already-decoded UIImage from SDWebImage's memory cache
|
|
310
|
+
* and paint it on the first feed cell with zero network overhead — closing
|
|
311
|
+
* the black-frame gap before video playback begins.
|
|
312
|
+
*
|
|
313
|
+
* Falls back silently (no extra network beyond the existing fallback) for
|
|
314
|
+
* apps on React Native's core <Image> or other image libraries.
|
|
315
|
+
*/
|
|
316
|
+
seedThumbnailUrl?: string;
|
|
317
|
+
/**
|
|
318
|
+
* Initial feed items for custom feed mode (`feedSource: 'custom'`).
|
|
319
|
+
*
|
|
320
|
+
* Items are delivered to the native view as construction-time props so the
|
|
321
|
+
* SDK renders the first cell on the very first run-loop tick — no
|
|
322
|
+
* ref-attach race, no async bridge hop, no loading-state flash.
|
|
323
|
+
*
|
|
324
|
+
* Use for supplying known items at mount (e.g. a cached last-watched
|
|
325
|
+
* video). For post-mount updates (pagination, refresh), use the imperative
|
|
326
|
+
* `setFeedItems()` / `appendFeedItems()` methods on the ref.
|
|
327
|
+
*
|
|
328
|
+
* When both `feedItems` and `preloadId` are set, `preloadId` takes
|
|
329
|
+
* precedence (it carries prefetched HLS manifests / segment 0).
|
|
330
|
+
*/
|
|
331
|
+
feedItems?: FeedInput[];
|
|
332
|
+
/**
|
|
333
|
+
* Whether the feed is the active/focused surface. When `false`, the SDK
|
|
334
|
+
* suspends playback and yields players so an adjacent feed (e.g. another
|
|
335
|
+
* tab) can use them without cross-surface contamination.
|
|
336
|
+
*
|
|
337
|
+
* Pass `useIsFocused()` from React Navigation. When omitted, the SDK
|
|
338
|
+
* infers suspension from UIKit's `willMove(toWindow:)` lifecycle —
|
|
339
|
+
* which works for push/pop navigation but NOT for tab/pager navigation
|
|
340
|
+
* where the view stays in the window hierarchy.
|
|
341
|
+
*
|
|
342
|
+
* @default true
|
|
343
|
+
*/
|
|
344
|
+
active?: boolean;
|
|
303
345
|
onLoop?: (event: LoopEvent) => void;
|
|
304
346
|
onFeedTransition?: (event: FeedTransitionEvent) => void;
|
|
305
347
|
onFormatChange?: (event: FormatChangeEvent) => void;
|
|
@@ -316,6 +358,26 @@ export interface ShortKitFeedProps {
|
|
|
316
358
|
/** Called once when this feed has loaded content and assigned a player
|
|
317
359
|
* to the first cell. Use to dismiss a splash screen or loading overlay. */
|
|
318
360
|
onFeedReady?: () => void;
|
|
361
|
+
/** Called when the active video in a video carousel completes playback. */
|
|
362
|
+
onCarouselActiveVideoCompleted?: (event: {
|
|
363
|
+
surfaceId: string;
|
|
364
|
+
contentItem: ContentItem;
|
|
365
|
+
indexInCarousel: number;
|
|
366
|
+
carouselItem: VideoCarouselItem;
|
|
367
|
+
wasLast: boolean;
|
|
368
|
+
willAutoAdvance: boolean;
|
|
369
|
+
}) => void;
|
|
370
|
+
/**
|
|
371
|
+
* Fires when the user taps a video-carousel cell without triggering the
|
|
372
|
+
* cell's horizontal pan. Use with `useShortKit().setMuted(...)` to
|
|
373
|
+
* implement tap-to-mute on video-carousel overlays. Single-video and
|
|
374
|
+
* image-carousel cells don't need this — JS `<Pressable>` works for them.
|
|
375
|
+
*/
|
|
376
|
+
onVideoCarouselCellTap?: (event: {
|
|
377
|
+
id: string;
|
|
378
|
+
index: number;
|
|
379
|
+
pageIndex: number;
|
|
380
|
+
}) => void;
|
|
319
381
|
}
|
|
320
382
|
|
|
321
383
|
/**
|
|
@@ -323,8 +385,25 @@ export interface ShortKitFeedProps {
|
|
|
323
385
|
* Obtained via `ref` on `<ShortKitFeed>`.
|
|
324
386
|
*/
|
|
325
387
|
export interface ShortKitFeedHandle {
|
|
326
|
-
/**
|
|
327
|
-
|
|
388
|
+
/**
|
|
389
|
+
* Replace the feed's items atomically.
|
|
390
|
+
*
|
|
391
|
+
* @param items The new feed contents.
|
|
392
|
+
* @param options Optional. `startAt` specifies the item id to focus after
|
|
393
|
+
* the items apply. Overrides the `startAtItemId` prop for
|
|
394
|
+
* this call only — the prop's value is preserved for
|
|
395
|
+
* subsequent calls that omit this option.
|
|
396
|
+
*/
|
|
397
|
+
setFeedItems: (items: FeedInput[], options?: { startAt?: string }) => void;
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Imperatively scroll the feed to the item with the given id. Silently
|
|
401
|
+
* no-ops if the id is not in the current items. Call after `onFeedReady`
|
|
402
|
+
* for the initial mount; for an atomic set-and-scroll, use
|
|
403
|
+
* `setFeedItems(items, { startAt })` instead.
|
|
404
|
+
*/
|
|
405
|
+
scrollToItem: (id: string, options?: { animated?: boolean }) => void;
|
|
406
|
+
|
|
328
407
|
/** Append items to this feed instance. */
|
|
329
408
|
appendFeedItems: (items: FeedInput[]) => void;
|
|
330
409
|
/** Apply a content filter to this feed instance. Pass null to clear. */
|
|
@@ -382,6 +461,9 @@ export interface ShortKitWidgetProps {
|
|
|
382
461
|
|
|
383
462
|
// --- Hook Return Types ---
|
|
384
463
|
|
|
464
|
+
export type { ShortKitCarouselState } from './useShortKitCarousel';
|
|
465
|
+
|
|
466
|
+
|
|
385
467
|
export interface ShortKitPlayerState {
|
|
386
468
|
playerState: PlayerState;
|
|
387
469
|
currentItem: ContentItem | null;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import NativeShortKitModule from './specs/NativeShortKitModule';
|
|
3
|
+
import { ShortKitCommands } from './ShortKitCommands';
|
|
4
|
+
import type { VideoCarouselItem } from './types';
|
|
5
|
+
|
|
6
|
+
export interface ShortKitCarouselState {
|
|
7
|
+
/** Index of the currently active video in the carousel, or null if no carousel is active. */
|
|
8
|
+
activeIndex: number | null;
|
|
9
|
+
/** Number of videos in the active carousel item, or 0 if none. */
|
|
10
|
+
videoCount: number;
|
|
11
|
+
/** The active carousel item, or null if no carousel is active. */
|
|
12
|
+
activeCarouselItem: VideoCarouselItem | null;
|
|
13
|
+
/** Advance to the next video in the carousel. Returns true if the command was dispatched. */
|
|
14
|
+
next: () => boolean;
|
|
15
|
+
/** Go back to the previous video in the carousel. Returns true if the command was dispatched. */
|
|
16
|
+
previous: () => boolean;
|
|
17
|
+
/** Jump to a specific index in the carousel. Returns true if the command was dispatched. */
|
|
18
|
+
setActiveIndex: (index: number) => boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Hook to access video carousel state and commands.
|
|
23
|
+
*
|
|
24
|
+
* Subscribes to `onVideoCarouselItemChanged` and
|
|
25
|
+
* `onVideoCarouselActiveVideoChanged` from the native TurboModule and surfaces
|
|
26
|
+
* carousel state as reactive values. The imperative commands (`next`,
|
|
27
|
+
* `previous`, `setActiveIndex`) delegate to `ShortKitCommands`.
|
|
28
|
+
*
|
|
29
|
+
* Does not require a `<ShortKitProvider>` — it subscribes directly to native
|
|
30
|
+
* events and is suitable for use inside overlay components.
|
|
31
|
+
*/
|
|
32
|
+
export function useShortKitCarousel(): ShortKitCarouselState {
|
|
33
|
+
const [activeIndex, setActiveIndexState] = useState<number | null>(() => {
|
|
34
|
+
const v = NativeShortKitModule?.getCarouselActiveIndex?.() ?? -1;
|
|
35
|
+
return v < 0 ? null : v;
|
|
36
|
+
});
|
|
37
|
+
const [activeCarouselItem, setActiveCarouselItem] = useState<VideoCarouselItem | null>(null);
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (!NativeShortKitModule) return;
|
|
41
|
+
const subItem = NativeShortKitModule.onVideoCarouselItemChanged((e: any) => {
|
|
42
|
+
try {
|
|
43
|
+
const item = e.carouselItem
|
|
44
|
+
? (JSON.parse(e.carouselItem) as VideoCarouselItem)
|
|
45
|
+
: null;
|
|
46
|
+
setActiveCarouselItem(item);
|
|
47
|
+
setActiveIndexState(
|
|
48
|
+
typeof e.activeVideoIndex === 'number' ? e.activeVideoIndex : null,
|
|
49
|
+
);
|
|
50
|
+
} catch {
|
|
51
|
+
// ignore parse errors
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
const subIdx = NativeShortKitModule.onVideoCarouselActiveVideoChanged((e: any) => {
|
|
55
|
+
setActiveIndexState(
|
|
56
|
+
typeof e.activeVideoIndex === 'number' ? e.activeVideoIndex : null,
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
return () => {
|
|
60
|
+
subItem?.remove?.();
|
|
61
|
+
subIdx?.remove?.();
|
|
62
|
+
};
|
|
63
|
+
}, []);
|
|
64
|
+
|
|
65
|
+
const next = useCallback(() => ShortKitCommands.carouselNext(), []);
|
|
66
|
+
const previous = useCallback(() => ShortKitCommands.carouselPrevious(), []);
|
|
67
|
+
const setActiveIndex = useCallback(
|
|
68
|
+
(i: number) => ShortKitCommands.carouselSetActiveIndex(i),
|
|
69
|
+
[],
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
activeIndex,
|
|
74
|
+
videoCount: activeCarouselItem?.videos?.length ?? 0,
|
|
75
|
+
activeCarouselItem,
|
|
76
|
+
next,
|
|
77
|
+
previous,
|
|
78
|
+
setActiveIndex,
|
|
79
|
+
};
|
|
80
|
+
}
|