@xhub-reels/sdk 0.1.22 → 0.2.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.
- package/README.md +42 -0
- package/dist/index.cjs +83 -0
- package/dist/index.d.cts +36 -1
- package/dist/index.d.ts +36 -1
- package/dist/index.js +83 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -111,6 +111,48 @@ function SwipeableFeed() {
|
|
|
111
111
|
}
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
+
## Thumbnail grid
|
|
115
|
+
|
|
116
|
+
Headless pre-drawer browsing UX. Mount inside `<ReelsProvider>`. Host provides the
|
|
117
|
+
card visuals via `renderThumbnail`; the SDK ships no default card.
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
import { ReelsFeedThumbnail } from 'xhub-reels-sdk';
|
|
121
|
+
|
|
122
|
+
<ReelsFeedThumbnail
|
|
123
|
+
renderThumbnail={(videoData) => (
|
|
124
|
+
<article className="aspect-[3/2] rounded-lg overflow-hidden">
|
|
125
|
+
<img src={videoData.poster} alt="" />
|
|
126
|
+
<p>@{videoData.author.name}</p>
|
|
127
|
+
</article>
|
|
128
|
+
)}
|
|
129
|
+
onThumbnailClick={(id) => openDrawer(id)}
|
|
130
|
+
/>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Opt-in hover prefetch (warms HLS manifest via `adapters.videoLoader.preloadMetadata`):
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
<ReelsFeedThumbnail prefetchOnHover renderThumbnail={...} />
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
On click the SDK calls `ResourceGovernor.setFocusedIndexImmediate(index)` before
|
|
140
|
+
invoking `onThumbnailClick`, so opening `<ReelsFeed>` lands on the tapped item
|
|
141
|
+
without a scroll-to-index flash. Disable with `setFocusOnClick={false}`.
|
|
142
|
+
|
|
143
|
+
Custom slots for the zero-items branches:
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
<ReelsFeedThumbnail
|
|
147
|
+
renderThumbnail={(item) => <Card item={item} />}
|
|
148
|
+
renderLoading={() => <SkeletonGrid />}
|
|
149
|
+
renderEmpty={() => <EmptyState />}
|
|
150
|
+
renderError={({ message, retry }) => (
|
|
151
|
+
<ErrorBox message={message} onRetry={retry} />
|
|
152
|
+
)}
|
|
153
|
+
/>
|
|
154
|
+
```
|
|
155
|
+
|
|
114
156
|
## Architecture
|
|
115
157
|
|
|
116
158
|
```
|
package/dist/index.cjs
CHANGED
|
@@ -2499,6 +2499,88 @@ function parsePxTranslateY(el) {
|
|
|
2499
2499
|
if (!match || !match[1]) return 0;
|
|
2500
2500
|
return Number.parseFloat(match[1]);
|
|
2501
2501
|
}
|
|
2502
|
+
var DEFAULT_CLASSNAME = "grid grid-cols-2 gap-3";
|
|
2503
|
+
var defaultGetKey = (item) => item.id;
|
|
2504
|
+
var buttonResetStyle = {
|
|
2505
|
+
all: "unset",
|
|
2506
|
+
display: "block",
|
|
2507
|
+
cursor: "pointer",
|
|
2508
|
+
width: "100%",
|
|
2509
|
+
boxSizing: "border-box"
|
|
2510
|
+
};
|
|
2511
|
+
function ReelsFeedThumbnail({
|
|
2512
|
+
renderThumbnail,
|
|
2513
|
+
onThumbnailClick,
|
|
2514
|
+
renderLoading,
|
|
2515
|
+
renderEmpty,
|
|
2516
|
+
renderError,
|
|
2517
|
+
className = DEFAULT_CLASSNAME,
|
|
2518
|
+
wrap = true,
|
|
2519
|
+
setFocusOnClick = true,
|
|
2520
|
+
prefetchOnHover = false,
|
|
2521
|
+
getKey = defaultGetKey
|
|
2522
|
+
}) {
|
|
2523
|
+
const { items, loading, error, refresh } = useFeed();
|
|
2524
|
+
const { setFocusedIndexImmediate } = useResource();
|
|
2525
|
+
const { adapters } = useSDK();
|
|
2526
|
+
const prefetchedRef = react.useRef(/* @__PURE__ */ new Set());
|
|
2527
|
+
const handleClick = react.useCallback(
|
|
2528
|
+
(item, index) => {
|
|
2529
|
+
if (setFocusOnClick) {
|
|
2530
|
+
setFocusedIndexImmediate(index);
|
|
2531
|
+
}
|
|
2532
|
+
onThumbnailClick?.(item.id, item, index);
|
|
2533
|
+
},
|
|
2534
|
+
[onThumbnailClick, setFocusOnClick, setFocusedIndexImmediate]
|
|
2535
|
+
);
|
|
2536
|
+
const handlePointerEnter = react.useCallback(
|
|
2537
|
+
(item) => {
|
|
2538
|
+
if (!prefetchOnHover) return;
|
|
2539
|
+
if (!isVideoItem(item)) return;
|
|
2540
|
+
if (item.source.type !== "hls") return;
|
|
2541
|
+
if (prefetchedRef.current.has(item.id)) return;
|
|
2542
|
+
const loader = adapters.videoLoader;
|
|
2543
|
+
if (!loader?.preloadMetadata) return;
|
|
2544
|
+
if (loader.isPreloaded(item.id)) {
|
|
2545
|
+
prefetchedRef.current.add(item.id);
|
|
2546
|
+
return;
|
|
2547
|
+
}
|
|
2548
|
+
loader.preloadMetadata(item.source.url);
|
|
2549
|
+
prefetchedRef.current.add(item.id);
|
|
2550
|
+
},
|
|
2551
|
+
[prefetchOnHover, adapters.videoLoader]
|
|
2552
|
+
);
|
|
2553
|
+
const children = react.useMemo(
|
|
2554
|
+
() => items.map((item, index) => {
|
|
2555
|
+
const key = getKey(item, index);
|
|
2556
|
+
const onEnter = prefetchOnHover ? (_e) => handlePointerEnter(item) : void 0;
|
|
2557
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2558
|
+
"button",
|
|
2559
|
+
{
|
|
2560
|
+
type: "button",
|
|
2561
|
+
style: buttonResetStyle,
|
|
2562
|
+
"data-thumbnail-index": index,
|
|
2563
|
+
"data-thumbnail-id": item.id,
|
|
2564
|
+
onClick: () => handleClick(item, index),
|
|
2565
|
+
onPointerEnter: onEnter,
|
|
2566
|
+
children: renderThumbnail(item, index)
|
|
2567
|
+
},
|
|
2568
|
+
key
|
|
2569
|
+
);
|
|
2570
|
+
}),
|
|
2571
|
+
[items, getKey, prefetchOnHover, handlePointerEnter, handleClick, renderThumbnail]
|
|
2572
|
+
);
|
|
2573
|
+
if (items.length === 0) {
|
|
2574
|
+
if (loading && renderLoading) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderLoading() });
|
|
2575
|
+
if (error && renderError) {
|
|
2576
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderError({ message: error.message, retry: refresh }) });
|
|
2577
|
+
}
|
|
2578
|
+
if (!loading && !error && renderEmpty) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderEmpty() });
|
|
2579
|
+
return null;
|
|
2580
|
+
}
|
|
2581
|
+
if (!wrap) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
|
|
2582
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className, children });
|
|
2583
|
+
}
|
|
2502
2584
|
function usePlayerSelector(selector) {
|
|
2503
2585
|
const { playerEngine } = useSDK();
|
|
2504
2586
|
const selectorRef = react.useRef(selector);
|
|
@@ -3131,6 +3213,7 @@ exports.OptimisticManager = OptimisticManager;
|
|
|
3131
3213
|
exports.PlayerEngine = PlayerEngine;
|
|
3132
3214
|
exports.PlayerStatus = PlayerStatus;
|
|
3133
3215
|
exports.ReelsFeed = ReelsFeed;
|
|
3216
|
+
exports.ReelsFeedThumbnail = ReelsFeedThumbnail;
|
|
3134
3217
|
exports.ReelsProvider = ReelsProvider;
|
|
3135
3218
|
exports.ResourceGovernor = ResourceGovernor;
|
|
3136
3219
|
exports.VALID_TRANSITIONS = VALID_TRANSITIONS;
|
package/dist/index.d.cts
CHANGED
|
@@ -875,6 +875,41 @@ interface VideoSlotProps {
|
|
|
875
875
|
}
|
|
876
876
|
declare function VideoSlot({ item, index, isActive, isPrefetch, isPreloaded, bufferTier, isMuted, onToggleMute, onAutoplayBlocked, showFps, isDragging, renderOverlay, renderActions, renderPauseAction, }: VideoSlotProps): react_jsx_runtime.JSX.Element;
|
|
877
877
|
|
|
878
|
+
interface ReelsFeedThumbnailProps {
|
|
879
|
+
/** Render function for a single item's visual card. */
|
|
880
|
+
renderThumbnail: (videoData: ContentItem, index: number) => ReactNode;
|
|
881
|
+
/** Click handler — receives the item id, full data, and index in the feed. */
|
|
882
|
+
onThumbnailClick?: (id: string, videoData: ContentItem, index: number) => void;
|
|
883
|
+
/** Rendered when the feed is loading and has zero items yet. */
|
|
884
|
+
renderLoading?: () => ReactNode;
|
|
885
|
+
/** Rendered when the feed has loaded but is empty. */
|
|
886
|
+
renderEmpty?: () => ReactNode;
|
|
887
|
+
/** Rendered when the feed fails with no items — receives a retry callback. */
|
|
888
|
+
renderError?: (error: {
|
|
889
|
+
message: string;
|
|
890
|
+
retry: () => void;
|
|
891
|
+
}) => ReactNode;
|
|
892
|
+
/** Outer wrapper className. Defaults to `grid grid-cols-2 gap-3`. */
|
|
893
|
+
className?: string;
|
|
894
|
+
/** If the host wants to render its own wrapper, pass `false`. */
|
|
895
|
+
wrap?: boolean;
|
|
896
|
+
/**
|
|
897
|
+
* Update ResourceGovernor's focused index on click so the drawer/feed opens
|
|
898
|
+
* already pointing at the tapped item.
|
|
899
|
+
* @default true
|
|
900
|
+
*/
|
|
901
|
+
setFocusOnClick?: boolean;
|
|
902
|
+
/**
|
|
903
|
+
* Prefetch HLS metadata for this slot on `pointerenter`. Off by default;
|
|
904
|
+
* only useful on hover-capable devices.
|
|
905
|
+
* @default false
|
|
906
|
+
*/
|
|
907
|
+
prefetchOnHover?: boolean;
|
|
908
|
+
/** Key override — useful when host renders multiple lists from the same feed. */
|
|
909
|
+
getKey?: (item: ContentItem, index: number) => string;
|
|
910
|
+
}
|
|
911
|
+
declare function ReelsFeedThumbnail({ renderThumbnail, onThumbnailClick, renderLoading, renderEmpty, renderError, className, wrap, setFocusOnClick, prefetchOnHover, getKey, }: ReelsFeedThumbnailProps): react_jsx_runtime.JSX.Element | null;
|
|
912
|
+
|
|
878
913
|
declare function DefaultOverlay({ item }: {
|
|
879
914
|
item: ContentItem;
|
|
880
915
|
}): react_jsx_runtime.JSX.Element;
|
|
@@ -1174,4 +1209,4 @@ declare class HttpError extends Error {
|
|
|
1174
1209
|
constructor(status: number, message: string, body?: string | undefined);
|
|
1175
1210
|
}
|
|
1176
1211
|
|
|
1177
|
-
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, 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 };
|
|
1212
|
+
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -875,6 +875,41 @@ interface VideoSlotProps {
|
|
|
875
875
|
}
|
|
876
876
|
declare function VideoSlot({ item, index, isActive, isPrefetch, isPreloaded, bufferTier, isMuted, onToggleMute, onAutoplayBlocked, showFps, isDragging, renderOverlay, renderActions, renderPauseAction, }: VideoSlotProps): react_jsx_runtime.JSX.Element;
|
|
877
877
|
|
|
878
|
+
interface ReelsFeedThumbnailProps {
|
|
879
|
+
/** Render function for a single item's visual card. */
|
|
880
|
+
renderThumbnail: (videoData: ContentItem, index: number) => ReactNode;
|
|
881
|
+
/** Click handler — receives the item id, full data, and index in the feed. */
|
|
882
|
+
onThumbnailClick?: (id: string, videoData: ContentItem, index: number) => void;
|
|
883
|
+
/** Rendered when the feed is loading and has zero items yet. */
|
|
884
|
+
renderLoading?: () => ReactNode;
|
|
885
|
+
/** Rendered when the feed has loaded but is empty. */
|
|
886
|
+
renderEmpty?: () => ReactNode;
|
|
887
|
+
/** Rendered when the feed fails with no items — receives a retry callback. */
|
|
888
|
+
renderError?: (error: {
|
|
889
|
+
message: string;
|
|
890
|
+
retry: () => void;
|
|
891
|
+
}) => ReactNode;
|
|
892
|
+
/** Outer wrapper className. Defaults to `grid grid-cols-2 gap-3`. */
|
|
893
|
+
className?: string;
|
|
894
|
+
/** If the host wants to render its own wrapper, pass `false`. */
|
|
895
|
+
wrap?: boolean;
|
|
896
|
+
/**
|
|
897
|
+
* Update ResourceGovernor's focused index on click so the drawer/feed opens
|
|
898
|
+
* already pointing at the tapped item.
|
|
899
|
+
* @default true
|
|
900
|
+
*/
|
|
901
|
+
setFocusOnClick?: boolean;
|
|
902
|
+
/**
|
|
903
|
+
* Prefetch HLS metadata for this slot on `pointerenter`. Off by default;
|
|
904
|
+
* only useful on hover-capable devices.
|
|
905
|
+
* @default false
|
|
906
|
+
*/
|
|
907
|
+
prefetchOnHover?: boolean;
|
|
908
|
+
/** Key override — useful when host renders multiple lists from the same feed. */
|
|
909
|
+
getKey?: (item: ContentItem, index: number) => string;
|
|
910
|
+
}
|
|
911
|
+
declare function ReelsFeedThumbnail({ renderThumbnail, onThumbnailClick, renderLoading, renderEmpty, renderError, className, wrap, setFocusOnClick, prefetchOnHover, getKey, }: ReelsFeedThumbnailProps): react_jsx_runtime.JSX.Element | null;
|
|
912
|
+
|
|
878
913
|
declare function DefaultOverlay({ item }: {
|
|
879
914
|
item: ContentItem;
|
|
880
915
|
}): react_jsx_runtime.JSX.Element;
|
|
@@ -1174,4 +1209,4 @@ declare class HttpError extends Error {
|
|
|
1174
1209
|
constructor(status: number, message: string, body?: string | undefined);
|
|
1175
1210
|
}
|
|
1176
1211
|
|
|
1177
|
-
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, 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 };
|
|
1212
|
+
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 };
|
package/dist/index.js
CHANGED
|
@@ -2493,6 +2493,88 @@ function parsePxTranslateY(el) {
|
|
|
2493
2493
|
if (!match || !match[1]) return 0;
|
|
2494
2494
|
return Number.parseFloat(match[1]);
|
|
2495
2495
|
}
|
|
2496
|
+
var DEFAULT_CLASSNAME = "grid grid-cols-2 gap-3";
|
|
2497
|
+
var defaultGetKey = (item) => item.id;
|
|
2498
|
+
var buttonResetStyle = {
|
|
2499
|
+
all: "unset",
|
|
2500
|
+
display: "block",
|
|
2501
|
+
cursor: "pointer",
|
|
2502
|
+
width: "100%",
|
|
2503
|
+
boxSizing: "border-box"
|
|
2504
|
+
};
|
|
2505
|
+
function ReelsFeedThumbnail({
|
|
2506
|
+
renderThumbnail,
|
|
2507
|
+
onThumbnailClick,
|
|
2508
|
+
renderLoading,
|
|
2509
|
+
renderEmpty,
|
|
2510
|
+
renderError,
|
|
2511
|
+
className = DEFAULT_CLASSNAME,
|
|
2512
|
+
wrap = true,
|
|
2513
|
+
setFocusOnClick = true,
|
|
2514
|
+
prefetchOnHover = false,
|
|
2515
|
+
getKey = defaultGetKey
|
|
2516
|
+
}) {
|
|
2517
|
+
const { items, loading, error, refresh } = useFeed();
|
|
2518
|
+
const { setFocusedIndexImmediate } = useResource();
|
|
2519
|
+
const { adapters } = useSDK();
|
|
2520
|
+
const prefetchedRef = useRef(/* @__PURE__ */ new Set());
|
|
2521
|
+
const handleClick = useCallback(
|
|
2522
|
+
(item, index) => {
|
|
2523
|
+
if (setFocusOnClick) {
|
|
2524
|
+
setFocusedIndexImmediate(index);
|
|
2525
|
+
}
|
|
2526
|
+
onThumbnailClick?.(item.id, item, index);
|
|
2527
|
+
},
|
|
2528
|
+
[onThumbnailClick, setFocusOnClick, setFocusedIndexImmediate]
|
|
2529
|
+
);
|
|
2530
|
+
const handlePointerEnter = useCallback(
|
|
2531
|
+
(item) => {
|
|
2532
|
+
if (!prefetchOnHover) return;
|
|
2533
|
+
if (!isVideoItem(item)) return;
|
|
2534
|
+
if (item.source.type !== "hls") return;
|
|
2535
|
+
if (prefetchedRef.current.has(item.id)) return;
|
|
2536
|
+
const loader = adapters.videoLoader;
|
|
2537
|
+
if (!loader?.preloadMetadata) return;
|
|
2538
|
+
if (loader.isPreloaded(item.id)) {
|
|
2539
|
+
prefetchedRef.current.add(item.id);
|
|
2540
|
+
return;
|
|
2541
|
+
}
|
|
2542
|
+
loader.preloadMetadata(item.source.url);
|
|
2543
|
+
prefetchedRef.current.add(item.id);
|
|
2544
|
+
},
|
|
2545
|
+
[prefetchOnHover, adapters.videoLoader]
|
|
2546
|
+
);
|
|
2547
|
+
const children = useMemo(
|
|
2548
|
+
() => items.map((item, index) => {
|
|
2549
|
+
const key = getKey(item, index);
|
|
2550
|
+
const onEnter = prefetchOnHover ? (_e) => handlePointerEnter(item) : void 0;
|
|
2551
|
+
return /* @__PURE__ */ jsx(
|
|
2552
|
+
"button",
|
|
2553
|
+
{
|
|
2554
|
+
type: "button",
|
|
2555
|
+
style: buttonResetStyle,
|
|
2556
|
+
"data-thumbnail-index": index,
|
|
2557
|
+
"data-thumbnail-id": item.id,
|
|
2558
|
+
onClick: () => handleClick(item, index),
|
|
2559
|
+
onPointerEnter: onEnter,
|
|
2560
|
+
children: renderThumbnail(item, index)
|
|
2561
|
+
},
|
|
2562
|
+
key
|
|
2563
|
+
);
|
|
2564
|
+
}),
|
|
2565
|
+
[items, getKey, prefetchOnHover, handlePointerEnter, handleClick, renderThumbnail]
|
|
2566
|
+
);
|
|
2567
|
+
if (items.length === 0) {
|
|
2568
|
+
if (loading && renderLoading) return /* @__PURE__ */ jsx(Fragment, { children: renderLoading() });
|
|
2569
|
+
if (error && renderError) {
|
|
2570
|
+
return /* @__PURE__ */ jsx(Fragment, { children: renderError({ message: error.message, retry: refresh }) });
|
|
2571
|
+
}
|
|
2572
|
+
if (!loading && !error && renderEmpty) return /* @__PURE__ */ jsx(Fragment, { children: renderEmpty() });
|
|
2573
|
+
return null;
|
|
2574
|
+
}
|
|
2575
|
+
if (!wrap) return /* @__PURE__ */ jsx(Fragment, { children });
|
|
2576
|
+
return /* @__PURE__ */ jsx("div", { className, children });
|
|
2577
|
+
}
|
|
2496
2578
|
function usePlayerSelector(selector) {
|
|
2497
2579
|
const { playerEngine } = useSDK();
|
|
2498
2580
|
const selectorRef = useRef(selector);
|
|
@@ -3103,4 +3185,4 @@ var HttpError = class extends Error {
|
|
|
3103
3185
|
}
|
|
3104
3186
|
};
|
|
3105
3187
|
|
|
3106
|
-
export { DEFAULT_FEED_CONFIG, DEFAULT_PLAYER_CONFIG, DEFAULT_RESOURCE_CONFIG, DefaultActions, DefaultOverlay, DefaultPauseAction, DefaultSkeleton, FeedManager, HttpDataSource, HttpError, MockAnalytics, MockCommentAdapter, MockDataSource, MockInteraction, MockLogger, MockNetworkAdapter, MockSessionStorage, MockVideoLoader, OptimisticManager, PlayerEngine, PlayerStatus, ReelsFeed, ReelsProvider, ResourceGovernor, VALID_TRANSITIONS, VideoSlot, canPause, canPlay, canSeek, isArticle, isValidTransition, isVideoItem, useFeed, useFeedSelector, useHls, usePlayer, usePlayerSelector, usePointerGesture, useResource, useResourceSelector, useSDK, useSnapAnimation };
|
|
3188
|
+
export { DEFAULT_FEED_CONFIG, DEFAULT_PLAYER_CONFIG, DEFAULT_RESOURCE_CONFIG, DefaultActions, DefaultOverlay, DefaultPauseAction, DefaultSkeleton, FeedManager, HttpDataSource, HttpError, MockAnalytics, MockCommentAdapter, MockDataSource, MockInteraction, MockLogger, MockNetworkAdapter, MockSessionStorage, MockVideoLoader, OptimisticManager, PlayerEngine, PlayerStatus, ReelsFeed, ReelsFeedThumbnail, ReelsProvider, ResourceGovernor, VALID_TRANSITIONS, VideoSlot, canPause, canPlay, canSeek, isArticle, isValidTransition, isVideoItem, useFeed, useFeedSelector, useHls, usePlayer, usePlayerSelector, usePointerGesture, useResource, useResourceSelector, useSDK, useSnapAnimation };
|