@xhub-reels/sdk 0.2.12 → 0.2.14
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 +91 -13
- package/dist/index.cjs +734 -52
- package/dist/index.d.cts +219 -4
- package/dist/index.d.ts +219 -4
- package/dist/index.js +730 -54
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -443,6 +443,7 @@ declare class PlayerEngine {
|
|
|
443
443
|
private circuitResetTimer;
|
|
444
444
|
private watchTimeInterval;
|
|
445
445
|
private lastStatus;
|
|
446
|
+
private _destroyed;
|
|
446
447
|
constructor(config?: PlayerConfig, analytics?: IAnalytics, logger?: ILogger);
|
|
447
448
|
/**
|
|
448
449
|
* Load a video and prepare for playback.
|
|
@@ -498,6 +499,8 @@ declare class FeedManager {
|
|
|
498
499
|
private inFlightRequests;
|
|
499
500
|
/** LRU tracking: itemId → lastAccessTime */
|
|
500
501
|
private accessOrder;
|
|
502
|
+
/** Idempotency guard for destroy() */
|
|
503
|
+
private _destroyed;
|
|
501
504
|
/** Prefetch cache — instance-scoped (not static) */
|
|
502
505
|
private prefetchCache;
|
|
503
506
|
constructor(dataSource: IDataSource, config?: FeedConfig, logger?: ILogger);
|
|
@@ -557,6 +560,8 @@ declare class OptimisticManager {
|
|
|
557
560
|
private likeDebounceTimers;
|
|
558
561
|
/** Pending like direction: contentId → final intended state */
|
|
559
562
|
private pendingLikeState;
|
|
563
|
+
/** Idempotency guard for destroy() */
|
|
564
|
+
private _destroyed;
|
|
560
565
|
constructor(interaction: Partial<IInteraction>, logger?: ILogger);
|
|
561
566
|
/**
|
|
562
567
|
* Debounced like toggle — prevents rapid API spam on double-tap.
|
|
@@ -627,6 +632,7 @@ declare class ResourceGovernor {
|
|
|
627
632
|
private readonly logger?;
|
|
628
633
|
private focusDebounceTimer;
|
|
629
634
|
private networkUnsubscribe?;
|
|
635
|
+
private _destroyed;
|
|
630
636
|
constructor(config?: ResourceConfig, videoLoader?: IVideoLoader, network?: INetworkAdapter, logger?: ILogger);
|
|
631
637
|
activate(): Promise<void>;
|
|
632
638
|
deactivate(): void;
|
|
@@ -659,6 +665,103 @@ declare class ResourceGovernor {
|
|
|
659
665
|
private recalculate;
|
|
660
666
|
}
|
|
661
667
|
|
|
668
|
+
/**
|
|
669
|
+
* NavigationManager — Owns the open/close lifecycle of the SDK reels viewer.
|
|
670
|
+
*
|
|
671
|
+
* Framework-agnostic class (zustand/vanilla) mirroring FeedManager /
|
|
672
|
+
* ResourceGovernor / PlayerEngine conventions. It is the single source of
|
|
673
|
+
* truth for "is the reels viewer open, and at which index".
|
|
674
|
+
*
|
|
675
|
+
* WHY THIS EXISTS:
|
|
676
|
+
* Previously the host app owned the modal entirely (via onThumbnailClick).
|
|
677
|
+
* That meant the SDK could not warm a video before the host mounted
|
|
678
|
+
* <ReelsFeed>, producing the "poster stuck ~3s" stall. By owning the
|
|
679
|
+
* open/close phase, the SDK can:
|
|
680
|
+
* 1. mount <ReelsFeed> early (hidden behind the animating container),
|
|
681
|
+
* 2. prewarm the focused video DURING the open animation,
|
|
682
|
+
* 3. reveal an already-decoded first frame → no poster stall.
|
|
683
|
+
*
|
|
684
|
+
* PHASE MODEL (state machine):
|
|
685
|
+
* closed → opening : open(index) called (e.g. from a thumbnail tap)
|
|
686
|
+
* opening → open : ReelsModal finished its enter animation
|
|
687
|
+
* open → closing : close() called
|
|
688
|
+
* closing → closed : ReelsModal finished its exit animation
|
|
689
|
+
*
|
|
690
|
+
* Invalid transitions are dropped (see isValidNavTransition). This mirrors
|
|
691
|
+
* the PlayerEngine state-machine discipline used elsewhere in the SDK.
|
|
692
|
+
*
|
|
693
|
+
* The component layer (ReelsModal) drives the animation timing and calls
|
|
694
|
+
* `setPhase('open')` / `setPhase('closed')` when its Web Animations API
|
|
695
|
+
* transitions finish. NavigationManager itself holds no timers tied to
|
|
696
|
+
* animation — it only holds a safety fallback timer so a missing/janky
|
|
697
|
+
* animationend can never strand the phase forever.
|
|
698
|
+
*/
|
|
699
|
+
|
|
700
|
+
type NavigationPhase = 'closed' | 'opening' | 'open' | 'closing';
|
|
701
|
+
interface NavigationState {
|
|
702
|
+
/** Current lifecycle phase of the reels viewer. */
|
|
703
|
+
phase: NavigationPhase;
|
|
704
|
+
/**
|
|
705
|
+
* Index the viewer was opened at. Held through the whole open lifecycle
|
|
706
|
+
* (opening → open → closing) and cleared back to null only once fully
|
|
707
|
+
* closed. ReelsModal mounts <ReelsFeed> whenever this is non-null so the
|
|
708
|
+
* feed can warm during `opening`.
|
|
709
|
+
*/
|
|
710
|
+
openIndex: number | null;
|
|
711
|
+
}
|
|
712
|
+
interface NavigationConfig {
|
|
713
|
+
/**
|
|
714
|
+
* Safety fallback (ms). If the component never reports its enter/exit
|
|
715
|
+
* animation finished, the manager force-advances the phase so the viewer
|
|
716
|
+
* can never get stuck in `opening`/`closing`. Set generously above the
|
|
717
|
+
* real animation duration. Default: 1200.
|
|
718
|
+
*/
|
|
719
|
+
phaseTimeoutMs?: number;
|
|
720
|
+
}
|
|
721
|
+
declare const DEFAULT_NAVIGATION_CONFIG: Required<NavigationConfig>;
|
|
722
|
+
declare function isValidNavTransition(from: NavigationPhase, to: NavigationPhase): boolean;
|
|
723
|
+
declare class NavigationManager {
|
|
724
|
+
readonly store: StoreApi<NavigationState>;
|
|
725
|
+
private readonly config;
|
|
726
|
+
private readonly logger?;
|
|
727
|
+
/** Fallback timer guarding against a missing animationend report. */
|
|
728
|
+
private phaseTimer;
|
|
729
|
+
private _destroyed;
|
|
730
|
+
constructor(config?: NavigationConfig, logger?: ILogger);
|
|
731
|
+
/**
|
|
732
|
+
* Open the reels viewer at `index`. Safe to call from a thumbnail click
|
|
733
|
+
* handler — it transitions into `opening`, at which point ReelsModal mounts
|
|
734
|
+
* <ReelsFeed> hidden and begins prewarming.
|
|
735
|
+
*
|
|
736
|
+
* Calling open() while already open/opening just retargets the index
|
|
737
|
+
* (e.g. user taps a different thumbnail before the animation settles).
|
|
738
|
+
*/
|
|
739
|
+
open(index: number): void;
|
|
740
|
+
/**
|
|
741
|
+
* Begin closing the viewer. ReelsModal animates out then calls
|
|
742
|
+
* `setPhase('closed')`. Idempotent if already closing/closed.
|
|
743
|
+
*/
|
|
744
|
+
close(): void;
|
|
745
|
+
/**
|
|
746
|
+
* Report an animation-driven phase change from the component layer.
|
|
747
|
+
* Invalid transitions are dropped. When reaching `closed`, openIndex is
|
|
748
|
+
* cleared so <ReelsFeed> unmounts.
|
|
749
|
+
*/
|
|
750
|
+
setPhase(next: NavigationPhase): void;
|
|
751
|
+
getPhase(): NavigationPhase;
|
|
752
|
+
getOpenIndex(): number | null;
|
|
753
|
+
isOpen(): boolean;
|
|
754
|
+
/** Whether <ReelsFeed> should be mounted (any non-closed phase). */
|
|
755
|
+
shouldMount(): boolean;
|
|
756
|
+
destroy(): void;
|
|
757
|
+
/**
|
|
758
|
+
* Apply a guarded phase transition. Returns true if it was applied.
|
|
759
|
+
* Manages the fallback timer for transient phases (opening/closing).
|
|
760
|
+
*/
|
|
761
|
+
private transition;
|
|
762
|
+
private clearPhaseTimer;
|
|
763
|
+
}
|
|
764
|
+
|
|
662
765
|
/**
|
|
663
766
|
* Player State Machine — Guarded transitions
|
|
664
767
|
*
|
|
@@ -777,6 +880,7 @@ interface SDKContextValue {
|
|
|
777
880
|
playerEngine: PlayerEngine;
|
|
778
881
|
resourceGovernor: ResourceGovernor;
|
|
779
882
|
optimisticManager: OptimisticManager;
|
|
883
|
+
navigationManager: NavigationManager;
|
|
780
884
|
adapters: Required<Omit<SDKAdapters, 'dataSource'>> & Pick<SDKAdapters, 'dataSource'>;
|
|
781
885
|
}
|
|
782
886
|
interface ReelsProviderProps {
|
|
@@ -786,8 +890,10 @@ interface ReelsProviderProps {
|
|
|
786
890
|
initialItems?: ContentItem[];
|
|
787
891
|
/** Enable verbose logging (default: false) */
|
|
788
892
|
debug?: boolean;
|
|
893
|
+
/** Config for the owned reels viewer open/close lifecycle (used by ReelsModal) */
|
|
894
|
+
navigationConfig?: NavigationConfig;
|
|
789
895
|
}
|
|
790
|
-
declare function ReelsProvider({ children, adapters, initialItems, debug, }: ReelsProviderProps): react_jsx_runtime.JSX.Element;
|
|
896
|
+
declare function ReelsProvider({ children, adapters, initialItems, debug, navigationConfig, }: ReelsProviderProps): react_jsx_runtime.JSX.Element;
|
|
791
897
|
declare function useSDK(): SDKContextValue;
|
|
792
898
|
|
|
793
899
|
declare function ReelsFeed({ renderOverlay, renderActions, renderPauseAction, renderLoading, renderEmpty, renderError: _renderError, showFps, loadMoreThreshold, onSlotChange, gestureConfig, snapConfig, initialMuted, onAutoplayBlocked, }: ReelsFeedProps): string | number | bigint | boolean | Iterable<react.ReactNode> | Promise<string | number | bigint | boolean | react.ReactPortal | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<react.ReactNode> | null | undefined> | react_jsx_runtime.JSX.Element | null | undefined;
|
|
@@ -812,12 +918,97 @@ interface ReelsFeedThumbnailProps {
|
|
|
812
918
|
wrap?: boolean;
|
|
813
919
|
/** Enable click → update focused index on ReelsProvider before host reacts. Default: true. */
|
|
814
920
|
setFocusOnClick?: boolean;
|
|
815
|
-
/**
|
|
921
|
+
/**
|
|
922
|
+
* Open the SDK-owned <ReelsModal> on click (calls NavigationManager.open).
|
|
923
|
+
* This is the recommended path — the SDK controls modal timing and prewarms
|
|
924
|
+
* the video during the open animation, eliminating the "poster stuck ~3s"
|
|
925
|
+
* stall. Requires a <ReelsModal> mounted under the same <ReelsProvider>.
|
|
926
|
+
*
|
|
927
|
+
* Leave false (default) to keep the headless behaviour where the host owns
|
|
928
|
+
* the modal and reacts via `onThumbnailClick`.
|
|
929
|
+
*
|
|
930
|
+
* @default false
|
|
931
|
+
*/
|
|
932
|
+
openOnClick?: boolean;
|
|
933
|
+
/**
|
|
934
|
+
* Prefetch HLS metadata for this slot on pointer enter (desktop hover).
|
|
935
|
+
* Useless on mobile WebView (no hover) — use `prewarmOnClick` there.
|
|
936
|
+
* Off by default.
|
|
937
|
+
*/
|
|
816
938
|
prefetchOnHover?: boolean;
|
|
939
|
+
/**
|
|
940
|
+
* Prefetch HLS metadata for the tapped slot synchronously on click, inside
|
|
941
|
+
* the tap gesture. This is the mobile-friendly counterpart to
|
|
942
|
+
* `prefetchOnHover` and starts buffering the instant the user taps — before
|
|
943
|
+
* any modal mounts. Recommended for mobile WebView. Default: true.
|
|
944
|
+
*/
|
|
945
|
+
prewarmOnClick?: boolean;
|
|
817
946
|
/** Key override — useful when host renders multiple lists from the same feed. */
|
|
818
947
|
getKey?: (item: ContentItem, index: number) => string;
|
|
819
948
|
}
|
|
820
|
-
declare function ReelsFeedThumbnail({ renderThumbnail, onThumbnailClick, renderLoading, renderEmpty, renderError, className, wrap, setFocusOnClick, prefetchOnHover, getKey, }: ReelsFeedThumbnailProps): react_jsx_runtime.JSX.Element | null;
|
|
949
|
+
declare function ReelsFeedThumbnail({ renderThumbnail, onThumbnailClick, renderLoading, renderEmpty, renderError, className, wrap, setFocusOnClick, openOnClick, prefetchOnHover, prewarmOnClick, getKey, }: ReelsFeedThumbnailProps): react_jsx_runtime.JSX.Element | null;
|
|
950
|
+
|
|
951
|
+
interface ReelsModalAnimationConfig {
|
|
952
|
+
/** Enter/exit duration in ms (default: 300) */
|
|
953
|
+
duration?: number;
|
|
954
|
+
/** CSS easing (default: 'cubic-bezier(0.22, 1, 0.36, 1)') */
|
|
955
|
+
easing?: string;
|
|
956
|
+
/**
|
|
957
|
+
* Panel travel direction:
|
|
958
|
+
* - 'up' (default): slides up from the bottom edge (bottom-sheet drawer)
|
|
959
|
+
* - 'down': slides down from the top edge
|
|
960
|
+
* - 'fade': no translate, opacity only
|
|
961
|
+
*/
|
|
962
|
+
direction?: 'up' | 'down' | 'fade';
|
|
963
|
+
}
|
|
964
|
+
/** State handed to render-prop customisation callbacks. */
|
|
965
|
+
interface ReelsModalRenderState {
|
|
966
|
+
phase: NavigationPhase;
|
|
967
|
+
openIndex: number | null;
|
|
968
|
+
/** Begin closing the viewer (runs the exit animation). */
|
|
969
|
+
close: () => void;
|
|
970
|
+
}
|
|
971
|
+
interface ReelsModalProps {
|
|
972
|
+
/** Props forwarded to the inner <ReelsFeed> (render callbacks, initialMuted, etc.). */
|
|
973
|
+
feedProps?: ReelsFeedProps;
|
|
974
|
+
/** Enter/exit animation overrides. Defaults applied when omitted. */
|
|
975
|
+
animationConfig?: ReelsModalAnimationConfig;
|
|
976
|
+
/**
|
|
977
|
+
* Custom backdrop visual. Rendered inside the SDK-owned backdrop layer,
|
|
978
|
+
* whose opacity the SDK animates. If omitted, a semi-transparent black
|
|
979
|
+
* backdrop is used. Return null to suppress the default visual.
|
|
980
|
+
*/
|
|
981
|
+
renderBackdrop?: (state: ReelsModalRenderState) => ReactNode;
|
|
982
|
+
/**
|
|
983
|
+
* Custom close affordance, rendered above <ReelsFeed> inside the panel.
|
|
984
|
+
* If omitted, a top-right ✕ button is shown. Return null to hide it
|
|
985
|
+
* (e.g. when the host relies on swipe-to-dismiss or its own chrome).
|
|
986
|
+
*/
|
|
987
|
+
renderCloseButton?: (state: ReelsModalRenderState) => ReactNode;
|
|
988
|
+
/** Fired when the enter animation completes (viewer fully open). */
|
|
989
|
+
onOpen?: (index: number | null) => void;
|
|
990
|
+
/** Fired when the exit animation completes (viewer fully closed). */
|
|
991
|
+
onClose?: () => void;
|
|
992
|
+
/** Close when the backdrop is clicked. Default: true. */
|
|
993
|
+
closeOnBackdropClick?: boolean;
|
|
994
|
+
/** Close when Escape is pressed. Default: true. */
|
|
995
|
+
closeOnEscape?: boolean;
|
|
996
|
+
/** Lock body scroll while open. Default: true. */
|
|
997
|
+
lockBodyScroll?: boolean;
|
|
998
|
+
/**
|
|
999
|
+
* How many slots to prewarm on open. `prewarmForward` items ahead of the
|
|
1000
|
+
* focused index plus 1 behind get an immediate preloadMetadata() call so
|
|
1001
|
+
* buffering starts in the same tick as the tap. Default: 2.
|
|
1002
|
+
*/
|
|
1003
|
+
prewarmForward?: number;
|
|
1004
|
+
/** className on the sliding panel element. */
|
|
1005
|
+
className?: string;
|
|
1006
|
+
/** z-index for the modal root. Default: 1000. */
|
|
1007
|
+
zIndex?: number;
|
|
1008
|
+
/** Portal target. Default: document.body. */
|
|
1009
|
+
portalTarget?: HTMLElement | null;
|
|
1010
|
+
}
|
|
1011
|
+
declare function ReelsModal({ feedProps, animationConfig, renderBackdrop, renderCloseButton, onOpen, onClose, closeOnBackdropClick, closeOnEscape, lockBodyScroll, prewarmForward, className, zIndex, portalTarget, }: ReelsModalProps): react.ReactPortal | null;
|
|
821
1012
|
|
|
822
1013
|
/**
|
|
823
1014
|
* useHls — React hook for hls.js lifecycle management (3-Tier buffer support)
|
|
@@ -1004,6 +1195,30 @@ declare function useResource(): {
|
|
|
1004
1195
|
getTier: (index: number) => 3 | 1 | 2 | 4 | null;
|
|
1005
1196
|
};
|
|
1006
1197
|
|
|
1198
|
+
/**
|
|
1199
|
+
* useNavigation — React hook for NavigationManager
|
|
1200
|
+
*
|
|
1201
|
+
* Subscribes to the reels viewer open/close lifecycle via useSyncExternalStore.
|
|
1202
|
+
* Mirrors the useResource / useFeed selector pattern — no Zustand React bindings.
|
|
1203
|
+
*
|
|
1204
|
+
* Primary consumers:
|
|
1205
|
+
* - ReelsModal: reads `phase` / `openIndex` to mount + animate the viewer,
|
|
1206
|
+
* and calls `setPhase` when its enter/exit animation finishes.
|
|
1207
|
+
* - ReelsFeedThumbnail: calls `open(index)` on click when `openOnClick` is set.
|
|
1208
|
+
* - Host app: can read `isOpen` / call `close()` to drive its own chrome.
|
|
1209
|
+
*/
|
|
1210
|
+
|
|
1211
|
+
declare function useNavigationSelector<T>(selector: (state: NavigationState) => T): T;
|
|
1212
|
+
declare function useNavigation(): {
|
|
1213
|
+
phase: NavigationPhase;
|
|
1214
|
+
openIndex: number | null;
|
|
1215
|
+
isOpen: boolean;
|
|
1216
|
+
shouldMount: boolean;
|
|
1217
|
+
open: (index: number) => void;
|
|
1218
|
+
close: () => void;
|
|
1219
|
+
setPhase: (next: NavigationPhase) => void;
|
|
1220
|
+
};
|
|
1221
|
+
|
|
1007
1222
|
/**
|
|
1008
1223
|
* Mock adapters for development and testing
|
|
1009
1224
|
*/
|
|
@@ -1204,4 +1419,4 @@ declare class HttpError extends Error {
|
|
|
1204
1419
|
constructor(status: number, message: string, body?: string | undefined);
|
|
1205
1420
|
}
|
|
1206
1421
|
|
|
1207
|
-
export { type Article, type ArticleImage, type Author, type BufferTier, type CommentItem, type CommentPage, type ContentItem, type ContentStats, DEFAULT_FEED_CONFIG, DEFAULT_PLAYER_CONFIG, DEFAULT_RESOURCE_CONFIG, DefaultActions, DefaultOverlay, DefaultPauseAction, DefaultSkeleton, type FeedConfig, type FeedError, FeedManager, type FeedPage, type FeedState, HttpDataSource, type HttpDataSourceConfig, HttpError, type IAnalytics, type ICommentAdapter, type IDataSource, type IInteraction, type ILogger, type INetworkAdapter, type ISessionStorage, type IVideoLoader, type InteractionState, type LogLevel, MockAnalytics, MockCommentAdapter, MockDataSource, MockInteraction, MockLogger, MockNetworkAdapter, MockSessionStorage, MockVideoLoader, type NetworkType, OptimisticManager, type PauseSlotActions, type PlayerConfig, PlayerEngine, type PlayerError, type PlayerEvent, type PlayerEventListener, type PlayerState, PlayerStatus, type PointerGestureConfig, type PreloadResult, type PreloadStatus, ReelsFeed, type ReelsFeedProps, ReelsFeedThumbnail, type ReelsFeedThumbnailProps, ReelsProvider, type ReelsProviderProps, type ResourceConfig, ResourceGovernor, type ResourceState, type SDKAdapters, type SDKContextValue, type SlotActions, type SnapTarget, type UseHlsOptions, type UseHlsReturn, VALID_TRANSITIONS, type VideoItem, type VideoQuality, VideoSlot, type VideoSource, canPause, canPlay, canSeek, isArticle, isValidTransition, isVideoItem, useFeed, useFeedSelector, useHls, usePlayer, usePlayerSelector, usePointerGesture, useResource, useResourceSelector, useSDK, useSnapAnimation };
|
|
1422
|
+
export { type Article, type ArticleImage, type Author, type BufferTier, type CommentItem, type CommentPage, type ContentItem, type ContentStats, DEFAULT_FEED_CONFIG, DEFAULT_NAVIGATION_CONFIG, DEFAULT_PLAYER_CONFIG, DEFAULT_RESOURCE_CONFIG, DefaultActions, DefaultOverlay, DefaultPauseAction, DefaultSkeleton, type FeedConfig, type FeedError, FeedManager, type FeedPage, type FeedState, HttpDataSource, type HttpDataSourceConfig, HttpError, type IAnalytics, type ICommentAdapter, type IDataSource, type IInteraction, type ILogger, type INetworkAdapter, type ISessionStorage, type IVideoLoader, type InteractionState, type LogLevel, MockAnalytics, MockCommentAdapter, MockDataSource, MockInteraction, MockLogger, MockNetworkAdapter, MockSessionStorage, MockVideoLoader, type NavigationConfig, NavigationManager, type NavigationPhase, type NavigationState, type NetworkType, OptimisticManager, type PauseSlotActions, type PlayerConfig, PlayerEngine, type PlayerError, type PlayerEvent, type PlayerEventListener, type PlayerState, PlayerStatus, type PointerGestureConfig, type PreloadResult, type PreloadStatus, ReelsFeed, type ReelsFeedProps, ReelsFeedThumbnail, type ReelsFeedThumbnailProps, ReelsModal, type ReelsModalAnimationConfig, type ReelsModalProps, type ReelsModalRenderState, ReelsProvider, type ReelsProviderProps, type ResourceConfig, ResourceGovernor, type ResourceState, type SDKAdapters, type SDKContextValue, type SlotActions, type SnapTarget, type UseHlsOptions, type UseHlsReturn, VALID_TRANSITIONS, type VideoItem, type VideoQuality, VideoSlot, type VideoSource, canPause, canPlay, canSeek, isArticle, isValidNavTransition, isValidTransition, isVideoItem, useFeed, useFeedSelector, useHls, useNavigation, useNavigationSelector, usePlayer, usePlayerSelector, usePointerGesture, useResource, useResourceSelector, useSDK, useSnapAnimation };
|
package/dist/index.d.ts
CHANGED
|
@@ -443,6 +443,7 @@ declare class PlayerEngine {
|
|
|
443
443
|
private circuitResetTimer;
|
|
444
444
|
private watchTimeInterval;
|
|
445
445
|
private lastStatus;
|
|
446
|
+
private _destroyed;
|
|
446
447
|
constructor(config?: PlayerConfig, analytics?: IAnalytics, logger?: ILogger);
|
|
447
448
|
/**
|
|
448
449
|
* Load a video and prepare for playback.
|
|
@@ -498,6 +499,8 @@ declare class FeedManager {
|
|
|
498
499
|
private inFlightRequests;
|
|
499
500
|
/** LRU tracking: itemId → lastAccessTime */
|
|
500
501
|
private accessOrder;
|
|
502
|
+
/** Idempotency guard for destroy() */
|
|
503
|
+
private _destroyed;
|
|
501
504
|
/** Prefetch cache — instance-scoped (not static) */
|
|
502
505
|
private prefetchCache;
|
|
503
506
|
constructor(dataSource: IDataSource, config?: FeedConfig, logger?: ILogger);
|
|
@@ -557,6 +560,8 @@ declare class OptimisticManager {
|
|
|
557
560
|
private likeDebounceTimers;
|
|
558
561
|
/** Pending like direction: contentId → final intended state */
|
|
559
562
|
private pendingLikeState;
|
|
563
|
+
/** Idempotency guard for destroy() */
|
|
564
|
+
private _destroyed;
|
|
560
565
|
constructor(interaction: Partial<IInteraction>, logger?: ILogger);
|
|
561
566
|
/**
|
|
562
567
|
* Debounced like toggle — prevents rapid API spam on double-tap.
|
|
@@ -627,6 +632,7 @@ declare class ResourceGovernor {
|
|
|
627
632
|
private readonly logger?;
|
|
628
633
|
private focusDebounceTimer;
|
|
629
634
|
private networkUnsubscribe?;
|
|
635
|
+
private _destroyed;
|
|
630
636
|
constructor(config?: ResourceConfig, videoLoader?: IVideoLoader, network?: INetworkAdapter, logger?: ILogger);
|
|
631
637
|
activate(): Promise<void>;
|
|
632
638
|
deactivate(): void;
|
|
@@ -659,6 +665,103 @@ declare class ResourceGovernor {
|
|
|
659
665
|
private recalculate;
|
|
660
666
|
}
|
|
661
667
|
|
|
668
|
+
/**
|
|
669
|
+
* NavigationManager — Owns the open/close lifecycle of the SDK reels viewer.
|
|
670
|
+
*
|
|
671
|
+
* Framework-agnostic class (zustand/vanilla) mirroring FeedManager /
|
|
672
|
+
* ResourceGovernor / PlayerEngine conventions. It is the single source of
|
|
673
|
+
* truth for "is the reels viewer open, and at which index".
|
|
674
|
+
*
|
|
675
|
+
* WHY THIS EXISTS:
|
|
676
|
+
* Previously the host app owned the modal entirely (via onThumbnailClick).
|
|
677
|
+
* That meant the SDK could not warm a video before the host mounted
|
|
678
|
+
* <ReelsFeed>, producing the "poster stuck ~3s" stall. By owning the
|
|
679
|
+
* open/close phase, the SDK can:
|
|
680
|
+
* 1. mount <ReelsFeed> early (hidden behind the animating container),
|
|
681
|
+
* 2. prewarm the focused video DURING the open animation,
|
|
682
|
+
* 3. reveal an already-decoded first frame → no poster stall.
|
|
683
|
+
*
|
|
684
|
+
* PHASE MODEL (state machine):
|
|
685
|
+
* closed → opening : open(index) called (e.g. from a thumbnail tap)
|
|
686
|
+
* opening → open : ReelsModal finished its enter animation
|
|
687
|
+
* open → closing : close() called
|
|
688
|
+
* closing → closed : ReelsModal finished its exit animation
|
|
689
|
+
*
|
|
690
|
+
* Invalid transitions are dropped (see isValidNavTransition). This mirrors
|
|
691
|
+
* the PlayerEngine state-machine discipline used elsewhere in the SDK.
|
|
692
|
+
*
|
|
693
|
+
* The component layer (ReelsModal) drives the animation timing and calls
|
|
694
|
+
* `setPhase('open')` / `setPhase('closed')` when its Web Animations API
|
|
695
|
+
* transitions finish. NavigationManager itself holds no timers tied to
|
|
696
|
+
* animation — it only holds a safety fallback timer so a missing/janky
|
|
697
|
+
* animationend can never strand the phase forever.
|
|
698
|
+
*/
|
|
699
|
+
|
|
700
|
+
type NavigationPhase = 'closed' | 'opening' | 'open' | 'closing';
|
|
701
|
+
interface NavigationState {
|
|
702
|
+
/** Current lifecycle phase of the reels viewer. */
|
|
703
|
+
phase: NavigationPhase;
|
|
704
|
+
/**
|
|
705
|
+
* Index the viewer was opened at. Held through the whole open lifecycle
|
|
706
|
+
* (opening → open → closing) and cleared back to null only once fully
|
|
707
|
+
* closed. ReelsModal mounts <ReelsFeed> whenever this is non-null so the
|
|
708
|
+
* feed can warm during `opening`.
|
|
709
|
+
*/
|
|
710
|
+
openIndex: number | null;
|
|
711
|
+
}
|
|
712
|
+
interface NavigationConfig {
|
|
713
|
+
/**
|
|
714
|
+
* Safety fallback (ms). If the component never reports its enter/exit
|
|
715
|
+
* animation finished, the manager force-advances the phase so the viewer
|
|
716
|
+
* can never get stuck in `opening`/`closing`. Set generously above the
|
|
717
|
+
* real animation duration. Default: 1200.
|
|
718
|
+
*/
|
|
719
|
+
phaseTimeoutMs?: number;
|
|
720
|
+
}
|
|
721
|
+
declare const DEFAULT_NAVIGATION_CONFIG: Required<NavigationConfig>;
|
|
722
|
+
declare function isValidNavTransition(from: NavigationPhase, to: NavigationPhase): boolean;
|
|
723
|
+
declare class NavigationManager {
|
|
724
|
+
readonly store: StoreApi<NavigationState>;
|
|
725
|
+
private readonly config;
|
|
726
|
+
private readonly logger?;
|
|
727
|
+
/** Fallback timer guarding against a missing animationend report. */
|
|
728
|
+
private phaseTimer;
|
|
729
|
+
private _destroyed;
|
|
730
|
+
constructor(config?: NavigationConfig, logger?: ILogger);
|
|
731
|
+
/**
|
|
732
|
+
* Open the reels viewer at `index`. Safe to call from a thumbnail click
|
|
733
|
+
* handler — it transitions into `opening`, at which point ReelsModal mounts
|
|
734
|
+
* <ReelsFeed> hidden and begins prewarming.
|
|
735
|
+
*
|
|
736
|
+
* Calling open() while already open/opening just retargets the index
|
|
737
|
+
* (e.g. user taps a different thumbnail before the animation settles).
|
|
738
|
+
*/
|
|
739
|
+
open(index: number): void;
|
|
740
|
+
/**
|
|
741
|
+
* Begin closing the viewer. ReelsModal animates out then calls
|
|
742
|
+
* `setPhase('closed')`. Idempotent if already closing/closed.
|
|
743
|
+
*/
|
|
744
|
+
close(): void;
|
|
745
|
+
/**
|
|
746
|
+
* Report an animation-driven phase change from the component layer.
|
|
747
|
+
* Invalid transitions are dropped. When reaching `closed`, openIndex is
|
|
748
|
+
* cleared so <ReelsFeed> unmounts.
|
|
749
|
+
*/
|
|
750
|
+
setPhase(next: NavigationPhase): void;
|
|
751
|
+
getPhase(): NavigationPhase;
|
|
752
|
+
getOpenIndex(): number | null;
|
|
753
|
+
isOpen(): boolean;
|
|
754
|
+
/** Whether <ReelsFeed> should be mounted (any non-closed phase). */
|
|
755
|
+
shouldMount(): boolean;
|
|
756
|
+
destroy(): void;
|
|
757
|
+
/**
|
|
758
|
+
* Apply a guarded phase transition. Returns true if it was applied.
|
|
759
|
+
* Manages the fallback timer for transient phases (opening/closing).
|
|
760
|
+
*/
|
|
761
|
+
private transition;
|
|
762
|
+
private clearPhaseTimer;
|
|
763
|
+
}
|
|
764
|
+
|
|
662
765
|
/**
|
|
663
766
|
* Player State Machine — Guarded transitions
|
|
664
767
|
*
|
|
@@ -777,6 +880,7 @@ interface SDKContextValue {
|
|
|
777
880
|
playerEngine: PlayerEngine;
|
|
778
881
|
resourceGovernor: ResourceGovernor;
|
|
779
882
|
optimisticManager: OptimisticManager;
|
|
883
|
+
navigationManager: NavigationManager;
|
|
780
884
|
adapters: Required<Omit<SDKAdapters, 'dataSource'>> & Pick<SDKAdapters, 'dataSource'>;
|
|
781
885
|
}
|
|
782
886
|
interface ReelsProviderProps {
|
|
@@ -786,8 +890,10 @@ interface ReelsProviderProps {
|
|
|
786
890
|
initialItems?: ContentItem[];
|
|
787
891
|
/** Enable verbose logging (default: false) */
|
|
788
892
|
debug?: boolean;
|
|
893
|
+
/** Config for the owned reels viewer open/close lifecycle (used by ReelsModal) */
|
|
894
|
+
navigationConfig?: NavigationConfig;
|
|
789
895
|
}
|
|
790
|
-
declare function ReelsProvider({ children, adapters, initialItems, debug, }: ReelsProviderProps): react_jsx_runtime.JSX.Element;
|
|
896
|
+
declare function ReelsProvider({ children, adapters, initialItems, debug, navigationConfig, }: ReelsProviderProps): react_jsx_runtime.JSX.Element;
|
|
791
897
|
declare function useSDK(): SDKContextValue;
|
|
792
898
|
|
|
793
899
|
declare function ReelsFeed({ renderOverlay, renderActions, renderPauseAction, renderLoading, renderEmpty, renderError: _renderError, showFps, loadMoreThreshold, onSlotChange, gestureConfig, snapConfig, initialMuted, onAutoplayBlocked, }: ReelsFeedProps): string | number | bigint | boolean | Iterable<react.ReactNode> | Promise<string | number | bigint | boolean | react.ReactPortal | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<react.ReactNode> | null | undefined> | react_jsx_runtime.JSX.Element | null | undefined;
|
|
@@ -812,12 +918,97 @@ interface ReelsFeedThumbnailProps {
|
|
|
812
918
|
wrap?: boolean;
|
|
813
919
|
/** Enable click → update focused index on ReelsProvider before host reacts. Default: true. */
|
|
814
920
|
setFocusOnClick?: boolean;
|
|
815
|
-
/**
|
|
921
|
+
/**
|
|
922
|
+
* Open the SDK-owned <ReelsModal> on click (calls NavigationManager.open).
|
|
923
|
+
* This is the recommended path — the SDK controls modal timing and prewarms
|
|
924
|
+
* the video during the open animation, eliminating the "poster stuck ~3s"
|
|
925
|
+
* stall. Requires a <ReelsModal> mounted under the same <ReelsProvider>.
|
|
926
|
+
*
|
|
927
|
+
* Leave false (default) to keep the headless behaviour where the host owns
|
|
928
|
+
* the modal and reacts via `onThumbnailClick`.
|
|
929
|
+
*
|
|
930
|
+
* @default false
|
|
931
|
+
*/
|
|
932
|
+
openOnClick?: boolean;
|
|
933
|
+
/**
|
|
934
|
+
* Prefetch HLS metadata for this slot on pointer enter (desktop hover).
|
|
935
|
+
* Useless on mobile WebView (no hover) — use `prewarmOnClick` there.
|
|
936
|
+
* Off by default.
|
|
937
|
+
*/
|
|
816
938
|
prefetchOnHover?: boolean;
|
|
939
|
+
/**
|
|
940
|
+
* Prefetch HLS metadata for the tapped slot synchronously on click, inside
|
|
941
|
+
* the tap gesture. This is the mobile-friendly counterpart to
|
|
942
|
+
* `prefetchOnHover` and starts buffering the instant the user taps — before
|
|
943
|
+
* any modal mounts. Recommended for mobile WebView. Default: true.
|
|
944
|
+
*/
|
|
945
|
+
prewarmOnClick?: boolean;
|
|
817
946
|
/** Key override — useful when host renders multiple lists from the same feed. */
|
|
818
947
|
getKey?: (item: ContentItem, index: number) => string;
|
|
819
948
|
}
|
|
820
|
-
declare function ReelsFeedThumbnail({ renderThumbnail, onThumbnailClick, renderLoading, renderEmpty, renderError, className, wrap, setFocusOnClick, prefetchOnHover, getKey, }: ReelsFeedThumbnailProps): react_jsx_runtime.JSX.Element | null;
|
|
949
|
+
declare function ReelsFeedThumbnail({ renderThumbnail, onThumbnailClick, renderLoading, renderEmpty, renderError, className, wrap, setFocusOnClick, openOnClick, prefetchOnHover, prewarmOnClick, getKey, }: ReelsFeedThumbnailProps): react_jsx_runtime.JSX.Element | null;
|
|
950
|
+
|
|
951
|
+
interface ReelsModalAnimationConfig {
|
|
952
|
+
/** Enter/exit duration in ms (default: 300) */
|
|
953
|
+
duration?: number;
|
|
954
|
+
/** CSS easing (default: 'cubic-bezier(0.22, 1, 0.36, 1)') */
|
|
955
|
+
easing?: string;
|
|
956
|
+
/**
|
|
957
|
+
* Panel travel direction:
|
|
958
|
+
* - 'up' (default): slides up from the bottom edge (bottom-sheet drawer)
|
|
959
|
+
* - 'down': slides down from the top edge
|
|
960
|
+
* - 'fade': no translate, opacity only
|
|
961
|
+
*/
|
|
962
|
+
direction?: 'up' | 'down' | 'fade';
|
|
963
|
+
}
|
|
964
|
+
/** State handed to render-prop customisation callbacks. */
|
|
965
|
+
interface ReelsModalRenderState {
|
|
966
|
+
phase: NavigationPhase;
|
|
967
|
+
openIndex: number | null;
|
|
968
|
+
/** Begin closing the viewer (runs the exit animation). */
|
|
969
|
+
close: () => void;
|
|
970
|
+
}
|
|
971
|
+
interface ReelsModalProps {
|
|
972
|
+
/** Props forwarded to the inner <ReelsFeed> (render callbacks, initialMuted, etc.). */
|
|
973
|
+
feedProps?: ReelsFeedProps;
|
|
974
|
+
/** Enter/exit animation overrides. Defaults applied when omitted. */
|
|
975
|
+
animationConfig?: ReelsModalAnimationConfig;
|
|
976
|
+
/**
|
|
977
|
+
* Custom backdrop visual. Rendered inside the SDK-owned backdrop layer,
|
|
978
|
+
* whose opacity the SDK animates. If omitted, a semi-transparent black
|
|
979
|
+
* backdrop is used. Return null to suppress the default visual.
|
|
980
|
+
*/
|
|
981
|
+
renderBackdrop?: (state: ReelsModalRenderState) => ReactNode;
|
|
982
|
+
/**
|
|
983
|
+
* Custom close affordance, rendered above <ReelsFeed> inside the panel.
|
|
984
|
+
* If omitted, a top-right ✕ button is shown. Return null to hide it
|
|
985
|
+
* (e.g. when the host relies on swipe-to-dismiss or its own chrome).
|
|
986
|
+
*/
|
|
987
|
+
renderCloseButton?: (state: ReelsModalRenderState) => ReactNode;
|
|
988
|
+
/** Fired when the enter animation completes (viewer fully open). */
|
|
989
|
+
onOpen?: (index: number | null) => void;
|
|
990
|
+
/** Fired when the exit animation completes (viewer fully closed). */
|
|
991
|
+
onClose?: () => void;
|
|
992
|
+
/** Close when the backdrop is clicked. Default: true. */
|
|
993
|
+
closeOnBackdropClick?: boolean;
|
|
994
|
+
/** Close when Escape is pressed. Default: true. */
|
|
995
|
+
closeOnEscape?: boolean;
|
|
996
|
+
/** Lock body scroll while open. Default: true. */
|
|
997
|
+
lockBodyScroll?: boolean;
|
|
998
|
+
/**
|
|
999
|
+
* How many slots to prewarm on open. `prewarmForward` items ahead of the
|
|
1000
|
+
* focused index plus 1 behind get an immediate preloadMetadata() call so
|
|
1001
|
+
* buffering starts in the same tick as the tap. Default: 2.
|
|
1002
|
+
*/
|
|
1003
|
+
prewarmForward?: number;
|
|
1004
|
+
/** className on the sliding panel element. */
|
|
1005
|
+
className?: string;
|
|
1006
|
+
/** z-index for the modal root. Default: 1000. */
|
|
1007
|
+
zIndex?: number;
|
|
1008
|
+
/** Portal target. Default: document.body. */
|
|
1009
|
+
portalTarget?: HTMLElement | null;
|
|
1010
|
+
}
|
|
1011
|
+
declare function ReelsModal({ feedProps, animationConfig, renderBackdrop, renderCloseButton, onOpen, onClose, closeOnBackdropClick, closeOnEscape, lockBodyScroll, prewarmForward, className, zIndex, portalTarget, }: ReelsModalProps): react.ReactPortal | null;
|
|
821
1012
|
|
|
822
1013
|
/**
|
|
823
1014
|
* useHls — React hook for hls.js lifecycle management (3-Tier buffer support)
|
|
@@ -1004,6 +1195,30 @@ declare function useResource(): {
|
|
|
1004
1195
|
getTier: (index: number) => 3 | 1 | 2 | 4 | null;
|
|
1005
1196
|
};
|
|
1006
1197
|
|
|
1198
|
+
/**
|
|
1199
|
+
* useNavigation — React hook for NavigationManager
|
|
1200
|
+
*
|
|
1201
|
+
* Subscribes to the reels viewer open/close lifecycle via useSyncExternalStore.
|
|
1202
|
+
* Mirrors the useResource / useFeed selector pattern — no Zustand React bindings.
|
|
1203
|
+
*
|
|
1204
|
+
* Primary consumers:
|
|
1205
|
+
* - ReelsModal: reads `phase` / `openIndex` to mount + animate the viewer,
|
|
1206
|
+
* and calls `setPhase` when its enter/exit animation finishes.
|
|
1207
|
+
* - ReelsFeedThumbnail: calls `open(index)` on click when `openOnClick` is set.
|
|
1208
|
+
* - Host app: can read `isOpen` / call `close()` to drive its own chrome.
|
|
1209
|
+
*/
|
|
1210
|
+
|
|
1211
|
+
declare function useNavigationSelector<T>(selector: (state: NavigationState) => T): T;
|
|
1212
|
+
declare function useNavigation(): {
|
|
1213
|
+
phase: NavigationPhase;
|
|
1214
|
+
openIndex: number | null;
|
|
1215
|
+
isOpen: boolean;
|
|
1216
|
+
shouldMount: boolean;
|
|
1217
|
+
open: (index: number) => void;
|
|
1218
|
+
close: () => void;
|
|
1219
|
+
setPhase: (next: NavigationPhase) => void;
|
|
1220
|
+
};
|
|
1221
|
+
|
|
1007
1222
|
/**
|
|
1008
1223
|
* Mock adapters for development and testing
|
|
1009
1224
|
*/
|
|
@@ -1204,4 +1419,4 @@ declare class HttpError extends Error {
|
|
|
1204
1419
|
constructor(status: number, message: string, body?: string | undefined);
|
|
1205
1420
|
}
|
|
1206
1421
|
|
|
1207
|
-
export { type Article, type ArticleImage, type Author, type BufferTier, type CommentItem, type CommentPage, type ContentItem, type ContentStats, DEFAULT_FEED_CONFIG, DEFAULT_PLAYER_CONFIG, DEFAULT_RESOURCE_CONFIG, DefaultActions, DefaultOverlay, DefaultPauseAction, DefaultSkeleton, type FeedConfig, type FeedError, FeedManager, type FeedPage, type FeedState, HttpDataSource, type HttpDataSourceConfig, HttpError, type IAnalytics, type ICommentAdapter, type IDataSource, type IInteraction, type ILogger, type INetworkAdapter, type ISessionStorage, type IVideoLoader, type InteractionState, type LogLevel, MockAnalytics, MockCommentAdapter, MockDataSource, MockInteraction, MockLogger, MockNetworkAdapter, MockSessionStorage, MockVideoLoader, type NetworkType, OptimisticManager, type PauseSlotActions, type PlayerConfig, PlayerEngine, type PlayerError, type PlayerEvent, type PlayerEventListener, type PlayerState, PlayerStatus, type PointerGestureConfig, type PreloadResult, type PreloadStatus, ReelsFeed, type ReelsFeedProps, ReelsFeedThumbnail, type ReelsFeedThumbnailProps, ReelsProvider, type ReelsProviderProps, type ResourceConfig, ResourceGovernor, type ResourceState, type SDKAdapters, type SDKContextValue, type SlotActions, type SnapTarget, type UseHlsOptions, type UseHlsReturn, VALID_TRANSITIONS, type VideoItem, type VideoQuality, VideoSlot, type VideoSource, canPause, canPlay, canSeek, isArticle, isValidTransition, isVideoItem, useFeed, useFeedSelector, useHls, usePlayer, usePlayerSelector, usePointerGesture, useResource, useResourceSelector, useSDK, useSnapAnimation };
|
|
1422
|
+
export { type Article, type ArticleImage, type Author, type BufferTier, type CommentItem, type CommentPage, type ContentItem, type ContentStats, DEFAULT_FEED_CONFIG, DEFAULT_NAVIGATION_CONFIG, DEFAULT_PLAYER_CONFIG, DEFAULT_RESOURCE_CONFIG, DefaultActions, DefaultOverlay, DefaultPauseAction, DefaultSkeleton, type FeedConfig, type FeedError, FeedManager, type FeedPage, type FeedState, HttpDataSource, type HttpDataSourceConfig, HttpError, type IAnalytics, type ICommentAdapter, type IDataSource, type IInteraction, type ILogger, type INetworkAdapter, type ISessionStorage, type IVideoLoader, type InteractionState, type LogLevel, MockAnalytics, MockCommentAdapter, MockDataSource, MockInteraction, MockLogger, MockNetworkAdapter, MockSessionStorage, MockVideoLoader, type NavigationConfig, NavigationManager, type NavigationPhase, type NavigationState, type NetworkType, OptimisticManager, type PauseSlotActions, type PlayerConfig, PlayerEngine, type PlayerError, type PlayerEvent, type PlayerEventListener, type PlayerState, PlayerStatus, type PointerGestureConfig, type PreloadResult, type PreloadStatus, ReelsFeed, type ReelsFeedProps, ReelsFeedThumbnail, type ReelsFeedThumbnailProps, ReelsModal, type ReelsModalAnimationConfig, type ReelsModalProps, type ReelsModalRenderState, ReelsProvider, type ReelsProviderProps, type ResourceConfig, ResourceGovernor, type ResourceState, type SDKAdapters, type SDKContextValue, type SlotActions, type SnapTarget, type UseHlsOptions, type UseHlsReturn, VALID_TRANSITIONS, type VideoItem, type VideoQuality, VideoSlot, type VideoSource, canPause, canPlay, canSeek, isArticle, isValidNavTransition, isValidTransition, isVideoItem, useFeed, useFeedSelector, useHls, useNavigation, useNavigationSelector, usePlayer, usePlayerSelector, usePointerGesture, useResource, useResourceSelector, useSDK, useSnapAnimation };
|