@xhub-reels/sdk 0.2.5 → 0.2.6
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 +66 -0
- package/dist/index.cjs +76 -0
- package/dist/index.d.cts +28 -1
- package/dist/index.d.ts +28 -1
- package/dist/index.js +76 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -86,6 +86,72 @@ function MyFeed() {
|
|
|
86
86
|
}
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
+
## Thumbnail grid
|
|
90
|
+
|
|
91
|
+
`<ReelsFeedThumbnail>` is a headless grid component for the pre-drawer browsing UX
|
|
92
|
+
(e.g. a community feed that opens the player drawer on tap). It must be mounted
|
|
93
|
+
inside `<ReelsProvider>` and reads items from the same shared feed. The host
|
|
94
|
+
provides card visuals via `renderThumbnail`.
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
import { ReelsProvider, ReelsFeedThumbnail } from 'xhub-reels-sdk';
|
|
98
|
+
|
|
99
|
+
function CommunityPage() {
|
|
100
|
+
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<ReelsProvider adapters={{ dataSource, interaction }}>
|
|
104
|
+
<ReelsFeedThumbnail
|
|
105
|
+
renderThumbnail={(item) => (
|
|
106
|
+
<article className="aspect-[3/2] rounded-lg overflow-hidden">
|
|
107
|
+
<img src={item.poster} alt="" />
|
|
108
|
+
<p>@{item.author.name}</p>
|
|
109
|
+
</article>
|
|
110
|
+
)}
|
|
111
|
+
onThumbnailClick={(id) => {
|
|
112
|
+
setDrawerOpen(true);
|
|
113
|
+
window.history.replaceState(null, '', `#reel_uuid=${id}`);
|
|
114
|
+
}}
|
|
115
|
+
/>
|
|
116
|
+
{drawerOpen && <ReelsDrawer onClose={() => setDrawerOpen(false)} />}
|
|
117
|
+
</ReelsProvider>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
By default, clicking a card calls `setFocusedIndexImmediate(index)` on the
|
|
123
|
+
`ResourceGovernor` so the player opens instantly without a scroll-to-index. To
|
|
124
|
+
disable this glue (e.g. if the host manages focus separately), pass
|
|
125
|
+
`setFocusOnClick={false}`.
|
|
126
|
+
|
|
127
|
+
### Props
|
|
128
|
+
|
|
129
|
+
| Prop | Type | Default | Description |
|
|
130
|
+
|---|---|---|---|
|
|
131
|
+
| `renderThumbnail` | `(item, index) => ReactNode` | required | Card visual for a single item |
|
|
132
|
+
| `onThumbnailClick` | `(id, item, index) => void` | — | Click handler |
|
|
133
|
+
| `renderLoading` | `() => ReactNode` | — | Shown while loading with no items |
|
|
134
|
+
| `renderEmpty` | `() => ReactNode` | — | Shown when feed is empty |
|
|
135
|
+
| `renderError` | `({ message, retry }) => ReactNode` | — | Shown on error with no items |
|
|
136
|
+
| `className` | `string` | `'grid grid-cols-2 gap-3'` | Outer wrapper className |
|
|
137
|
+
| `wrap` | `boolean` | `true` | Set `false` to render without an outer `<div>` |
|
|
138
|
+
| `setFocusOnClick` | `boolean` | `true` | Pre-focus the slot before `onThumbnailClick` fires |
|
|
139
|
+
| `prefetchOnHover` | `boolean` | `false` | Opt-in HLS metadata prefetch on `pointerenter` |
|
|
140
|
+
| `getKey` | `(item, index) => string` | `item.id` | Key override for duplicate lists |
|
|
141
|
+
|
|
142
|
+
### Hover prefetch
|
|
143
|
+
|
|
144
|
+
For hover-capable devices, opt into manifest prefetch so opening the player
|
|
145
|
+
feels instant:
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
<ReelsFeedThumbnail prefetchOnHover renderThumbnail={...} />
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The SDK uses `adapters.videoLoader?.preloadMetadata?.(url)` and dedupes per item
|
|
152
|
+
so a card only triggers one prefetch regardless of how many times it's hovered.
|
|
153
|
+
Off by default to avoid surprising bandwidth on touch devices.
|
|
154
|
+
|
|
89
155
|
## Gesture Engine
|
|
90
156
|
|
|
91
157
|
```tsx
|
package/dist/index.cjs
CHANGED
|
@@ -2520,6 +2520,81 @@ function parsePxTranslateY(el) {
|
|
|
2520
2520
|
if (!match || !match[1]) return 0;
|
|
2521
2521
|
return Number.parseFloat(match[1]);
|
|
2522
2522
|
}
|
|
2523
|
+
function ReelsFeedThumbnail({
|
|
2524
|
+
renderThumbnail,
|
|
2525
|
+
onThumbnailClick,
|
|
2526
|
+
renderLoading,
|
|
2527
|
+
renderEmpty,
|
|
2528
|
+
renderError,
|
|
2529
|
+
className = "grid grid-cols-2 gap-3",
|
|
2530
|
+
wrap = true,
|
|
2531
|
+
setFocusOnClick = true,
|
|
2532
|
+
prefetchOnHover = false,
|
|
2533
|
+
getKey
|
|
2534
|
+
}) {
|
|
2535
|
+
const { items, loading, error, refresh } = useFeed();
|
|
2536
|
+
const { setFocusedIndexImmediate } = useResource();
|
|
2537
|
+
const { adapters } = useSDK();
|
|
2538
|
+
const prefetchedRef = react.useRef(/* @__PURE__ */ new Set());
|
|
2539
|
+
const handleClick = react.useCallback(
|
|
2540
|
+
(id, item, index) => {
|
|
2541
|
+
if (setFocusOnClick) {
|
|
2542
|
+
setFocusedIndexImmediate(index);
|
|
2543
|
+
}
|
|
2544
|
+
onThumbnailClick?.(id, item, index);
|
|
2545
|
+
},
|
|
2546
|
+
[setFocusOnClick, setFocusedIndexImmediate, onThumbnailClick]
|
|
2547
|
+
);
|
|
2548
|
+
const handlePointerEnter = react.useCallback(
|
|
2549
|
+
(item) => {
|
|
2550
|
+
if (!prefetchOnHover) return;
|
|
2551
|
+
if (!isVideoItem(item)) return;
|
|
2552
|
+
if (item.source.type !== "hls") return;
|
|
2553
|
+
const url = item.source.url;
|
|
2554
|
+
if (prefetchedRef.current.has(url)) return;
|
|
2555
|
+
prefetchedRef.current.add(url);
|
|
2556
|
+
adapters.videoLoader?.preloadMetadata?.(url);
|
|
2557
|
+
},
|
|
2558
|
+
[prefetchOnHover, adapters.videoLoader]
|
|
2559
|
+
);
|
|
2560
|
+
if (loading && items.length === 0) {
|
|
2561
|
+
if (renderLoading) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderLoading() });
|
|
2562
|
+
return null;
|
|
2563
|
+
}
|
|
2564
|
+
if (error && items.length === 0) {
|
|
2565
|
+
if (renderError) {
|
|
2566
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderError({ message: error.message, retry: refresh }) });
|
|
2567
|
+
}
|
|
2568
|
+
return null;
|
|
2569
|
+
}
|
|
2570
|
+
if (!loading && items.length === 0) {
|
|
2571
|
+
if (renderEmpty) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderEmpty() });
|
|
2572
|
+
return null;
|
|
2573
|
+
}
|
|
2574
|
+
const content = items.map((item, index) => {
|
|
2575
|
+
const key = getKey ? getKey(item, index) : item.id;
|
|
2576
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2577
|
+
"button",
|
|
2578
|
+
{
|
|
2579
|
+
type: "button",
|
|
2580
|
+
onClick: () => handleClick(item.id, item, index),
|
|
2581
|
+
onPointerEnter: () => handlePointerEnter(item),
|
|
2582
|
+
style: {
|
|
2583
|
+
all: "unset",
|
|
2584
|
+
cursor: "pointer",
|
|
2585
|
+
display: "block",
|
|
2586
|
+
width: "100%"
|
|
2587
|
+
},
|
|
2588
|
+
children: renderThumbnail(item, index)
|
|
2589
|
+
},
|
|
2590
|
+
key
|
|
2591
|
+
);
|
|
2592
|
+
});
|
|
2593
|
+
if (!wrap) {
|
|
2594
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: content });
|
|
2595
|
+
}
|
|
2596
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className, children: content });
|
|
2597
|
+
}
|
|
2523
2598
|
function usePlayerSelector(selector) {
|
|
2524
2599
|
const { playerEngine } = useSDK();
|
|
2525
2600
|
const selectorRef = react.useRef(selector);
|
|
@@ -3156,6 +3231,7 @@ exports.OptimisticManager = OptimisticManager;
|
|
|
3156
3231
|
exports.PlayerEngine = PlayerEngine;
|
|
3157
3232
|
exports.PlayerStatus = PlayerStatus;
|
|
3158
3233
|
exports.ReelsFeed = ReelsFeed;
|
|
3234
|
+
exports.ReelsFeedThumbnail = ReelsFeedThumbnail;
|
|
3159
3235
|
exports.ReelsProvider = ReelsProvider;
|
|
3160
3236
|
exports.ResourceGovernor = ResourceGovernor;
|
|
3161
3237
|
exports.VALID_TRANSITIONS = VALID_TRANSITIONS;
|
package/dist/index.d.cts
CHANGED
|
@@ -789,6 +789,33 @@ declare function useSDK(): SDKContextValue;
|
|
|
789
789
|
|
|
790
790
|
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;
|
|
791
791
|
|
|
792
|
+
interface ReelsFeedThumbnailProps {
|
|
793
|
+
/** Render function for a single item's visual card. */
|
|
794
|
+
renderThumbnail: (videoData: ContentItem, index: number) => ReactNode;
|
|
795
|
+
/** Click handler — receives the item id, full data, and index in the feed. */
|
|
796
|
+
onThumbnailClick?: (id: string, videoData: ContentItem, index: number) => void;
|
|
797
|
+
/** Rendered when the feed is loading and has zero items yet. */
|
|
798
|
+
renderLoading?: () => ReactNode;
|
|
799
|
+
/** Rendered when the feed has loaded but is empty. */
|
|
800
|
+
renderEmpty?: () => ReactNode;
|
|
801
|
+
/** Rendered when the feed fails with no items — receives a retry callback. */
|
|
802
|
+
renderError?: (error: {
|
|
803
|
+
message: string;
|
|
804
|
+
retry: () => void;
|
|
805
|
+
}) => ReactNode;
|
|
806
|
+
/** Outer wrapper className. Defaults to `grid grid-cols-2 gap-3`. */
|
|
807
|
+
className?: string;
|
|
808
|
+
/** If the host wants to render its own wrapper, pass `false`. Default: true. */
|
|
809
|
+
wrap?: boolean;
|
|
810
|
+
/** Enable click → update focused index on ReelsProvider before host reacts. Default: true. */
|
|
811
|
+
setFocusOnClick?: boolean;
|
|
812
|
+
/** Prefetch metadata for this slot on pointer enter. Off by default. */
|
|
813
|
+
prefetchOnHover?: boolean;
|
|
814
|
+
/** Key override — useful when host renders multiple lists from the same feed. */
|
|
815
|
+
getKey?: (item: ContentItem, index: number) => string;
|
|
816
|
+
}
|
|
817
|
+
declare function ReelsFeedThumbnail({ renderThumbnail, onThumbnailClick, renderLoading, renderEmpty, renderError, className, wrap, setFocusOnClick, prefetchOnHover, getKey, }: ReelsFeedThumbnailProps): react_jsx_runtime.JSX.Element | null;
|
|
818
|
+
|
|
792
819
|
/**
|
|
793
820
|
* useHls — React hook for hls.js lifecycle management (3-Tier buffer support)
|
|
794
821
|
*
|
|
@@ -1174,4 +1201,4 @@ declare class HttpError extends Error {
|
|
|
1174
1201
|
constructor(status: number, message: string, body?: string | undefined);
|
|
1175
1202
|
}
|
|
1176
1203
|
|
|
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 };
|
|
1204
|
+
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
|
@@ -789,6 +789,33 @@ declare function useSDK(): SDKContextValue;
|
|
|
789
789
|
|
|
790
790
|
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;
|
|
791
791
|
|
|
792
|
+
interface ReelsFeedThumbnailProps {
|
|
793
|
+
/** Render function for a single item's visual card. */
|
|
794
|
+
renderThumbnail: (videoData: ContentItem, index: number) => ReactNode;
|
|
795
|
+
/** Click handler — receives the item id, full data, and index in the feed. */
|
|
796
|
+
onThumbnailClick?: (id: string, videoData: ContentItem, index: number) => void;
|
|
797
|
+
/** Rendered when the feed is loading and has zero items yet. */
|
|
798
|
+
renderLoading?: () => ReactNode;
|
|
799
|
+
/** Rendered when the feed has loaded but is empty. */
|
|
800
|
+
renderEmpty?: () => ReactNode;
|
|
801
|
+
/** Rendered when the feed fails with no items — receives a retry callback. */
|
|
802
|
+
renderError?: (error: {
|
|
803
|
+
message: string;
|
|
804
|
+
retry: () => void;
|
|
805
|
+
}) => ReactNode;
|
|
806
|
+
/** Outer wrapper className. Defaults to `grid grid-cols-2 gap-3`. */
|
|
807
|
+
className?: string;
|
|
808
|
+
/** If the host wants to render its own wrapper, pass `false`. Default: true. */
|
|
809
|
+
wrap?: boolean;
|
|
810
|
+
/** Enable click → update focused index on ReelsProvider before host reacts. Default: true. */
|
|
811
|
+
setFocusOnClick?: boolean;
|
|
812
|
+
/** Prefetch metadata for this slot on pointer enter. Off by default. */
|
|
813
|
+
prefetchOnHover?: boolean;
|
|
814
|
+
/** Key override — useful when host renders multiple lists from the same feed. */
|
|
815
|
+
getKey?: (item: ContentItem, index: number) => string;
|
|
816
|
+
}
|
|
817
|
+
declare function ReelsFeedThumbnail({ renderThumbnail, onThumbnailClick, renderLoading, renderEmpty, renderError, className, wrap, setFocusOnClick, prefetchOnHover, getKey, }: ReelsFeedThumbnailProps): react_jsx_runtime.JSX.Element | null;
|
|
818
|
+
|
|
792
819
|
/**
|
|
793
820
|
* useHls — React hook for hls.js lifecycle management (3-Tier buffer support)
|
|
794
821
|
*
|
|
@@ -1174,4 +1201,4 @@ declare class HttpError extends Error {
|
|
|
1174
1201
|
constructor(status: number, message: string, body?: string | undefined);
|
|
1175
1202
|
}
|
|
1176
1203
|
|
|
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 };
|
|
1204
|
+
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
|
@@ -2514,6 +2514,81 @@ function parsePxTranslateY(el) {
|
|
|
2514
2514
|
if (!match || !match[1]) return 0;
|
|
2515
2515
|
return Number.parseFloat(match[1]);
|
|
2516
2516
|
}
|
|
2517
|
+
function ReelsFeedThumbnail({
|
|
2518
|
+
renderThumbnail,
|
|
2519
|
+
onThumbnailClick,
|
|
2520
|
+
renderLoading,
|
|
2521
|
+
renderEmpty,
|
|
2522
|
+
renderError,
|
|
2523
|
+
className = "grid grid-cols-2 gap-3",
|
|
2524
|
+
wrap = true,
|
|
2525
|
+
setFocusOnClick = true,
|
|
2526
|
+
prefetchOnHover = false,
|
|
2527
|
+
getKey
|
|
2528
|
+
}) {
|
|
2529
|
+
const { items, loading, error, refresh } = useFeed();
|
|
2530
|
+
const { setFocusedIndexImmediate } = useResource();
|
|
2531
|
+
const { adapters } = useSDK();
|
|
2532
|
+
const prefetchedRef = useRef(/* @__PURE__ */ new Set());
|
|
2533
|
+
const handleClick = useCallback(
|
|
2534
|
+
(id, item, index) => {
|
|
2535
|
+
if (setFocusOnClick) {
|
|
2536
|
+
setFocusedIndexImmediate(index);
|
|
2537
|
+
}
|
|
2538
|
+
onThumbnailClick?.(id, item, index);
|
|
2539
|
+
},
|
|
2540
|
+
[setFocusOnClick, setFocusedIndexImmediate, onThumbnailClick]
|
|
2541
|
+
);
|
|
2542
|
+
const handlePointerEnter = useCallback(
|
|
2543
|
+
(item) => {
|
|
2544
|
+
if (!prefetchOnHover) return;
|
|
2545
|
+
if (!isVideoItem(item)) return;
|
|
2546
|
+
if (item.source.type !== "hls") return;
|
|
2547
|
+
const url = item.source.url;
|
|
2548
|
+
if (prefetchedRef.current.has(url)) return;
|
|
2549
|
+
prefetchedRef.current.add(url);
|
|
2550
|
+
adapters.videoLoader?.preloadMetadata?.(url);
|
|
2551
|
+
},
|
|
2552
|
+
[prefetchOnHover, adapters.videoLoader]
|
|
2553
|
+
);
|
|
2554
|
+
if (loading && items.length === 0) {
|
|
2555
|
+
if (renderLoading) return /* @__PURE__ */ jsx(Fragment, { children: renderLoading() });
|
|
2556
|
+
return null;
|
|
2557
|
+
}
|
|
2558
|
+
if (error && items.length === 0) {
|
|
2559
|
+
if (renderError) {
|
|
2560
|
+
return /* @__PURE__ */ jsx(Fragment, { children: renderError({ message: error.message, retry: refresh }) });
|
|
2561
|
+
}
|
|
2562
|
+
return null;
|
|
2563
|
+
}
|
|
2564
|
+
if (!loading && items.length === 0) {
|
|
2565
|
+
if (renderEmpty) return /* @__PURE__ */ jsx(Fragment, { children: renderEmpty() });
|
|
2566
|
+
return null;
|
|
2567
|
+
}
|
|
2568
|
+
const content = items.map((item, index) => {
|
|
2569
|
+
const key = getKey ? getKey(item, index) : item.id;
|
|
2570
|
+
return /* @__PURE__ */ jsx(
|
|
2571
|
+
"button",
|
|
2572
|
+
{
|
|
2573
|
+
type: "button",
|
|
2574
|
+
onClick: () => handleClick(item.id, item, index),
|
|
2575
|
+
onPointerEnter: () => handlePointerEnter(item),
|
|
2576
|
+
style: {
|
|
2577
|
+
all: "unset",
|
|
2578
|
+
cursor: "pointer",
|
|
2579
|
+
display: "block",
|
|
2580
|
+
width: "100%"
|
|
2581
|
+
},
|
|
2582
|
+
children: renderThumbnail(item, index)
|
|
2583
|
+
},
|
|
2584
|
+
key
|
|
2585
|
+
);
|
|
2586
|
+
});
|
|
2587
|
+
if (!wrap) {
|
|
2588
|
+
return /* @__PURE__ */ jsx(Fragment, { children: content });
|
|
2589
|
+
}
|
|
2590
|
+
return /* @__PURE__ */ jsx("div", { className, children: content });
|
|
2591
|
+
}
|
|
2517
2592
|
function usePlayerSelector(selector) {
|
|
2518
2593
|
const { playerEngine } = useSDK();
|
|
2519
2594
|
const selectorRef = useRef(selector);
|
|
@@ -3128,4 +3203,4 @@ var HttpError = class extends Error {
|
|
|
3128
3203
|
}
|
|
3129
3204
|
};
|
|
3130
3205
|
|
|
3131
|
-
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 };
|
|
3206
|
+
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 };
|