@xhub-reels/sdk 0.2.5 → 0.2.11
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 +89 -6
- package/dist/index.d.cts +28 -1
- package/dist/index.d.ts +28 -1
- package/dist/index.js +89 -7
- 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
|
@@ -593,16 +593,23 @@ var FeedManager = class {
|
|
|
593
593
|
applyItems(incoming, _nextCursor, append) {
|
|
594
594
|
const { itemsById, displayOrder } = this.store.getState();
|
|
595
595
|
const nextById = new Map(itemsById);
|
|
596
|
-
const existingIds = new Set(displayOrder);
|
|
597
|
-
const newIds = [];
|
|
598
596
|
for (const item of incoming) {
|
|
599
|
-
if (!existingIds.has(item.id)) {
|
|
600
|
-
newIds.push(item.id);
|
|
601
|
-
}
|
|
602
597
|
nextById.set(item.id, item);
|
|
603
598
|
this.accessOrder.set(item.id, Date.now());
|
|
604
599
|
}
|
|
605
|
-
|
|
600
|
+
let nextOrder;
|
|
601
|
+
if (append) {
|
|
602
|
+
const existingIds = new Set(displayOrder);
|
|
603
|
+
const newIds = [];
|
|
604
|
+
for (const item of incoming) {
|
|
605
|
+
if (!existingIds.has(item.id)) {
|
|
606
|
+
newIds.push(item.id);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
nextOrder = [...displayOrder, ...newIds];
|
|
610
|
+
} else {
|
|
611
|
+
nextOrder = incoming.map((item) => item.id);
|
|
612
|
+
}
|
|
606
613
|
if (nextById.size > this.config.maxCacheSize) {
|
|
607
614
|
this.evictLRU(nextById, nextOrder);
|
|
608
615
|
}
|
|
@@ -2520,6 +2527,81 @@ function parsePxTranslateY(el) {
|
|
|
2520
2527
|
if (!match || !match[1]) return 0;
|
|
2521
2528
|
return Number.parseFloat(match[1]);
|
|
2522
2529
|
}
|
|
2530
|
+
function ReelsFeedThumbnail({
|
|
2531
|
+
renderThumbnail,
|
|
2532
|
+
onThumbnailClick,
|
|
2533
|
+
renderLoading,
|
|
2534
|
+
renderEmpty,
|
|
2535
|
+
renderError,
|
|
2536
|
+
className = "grid grid-cols-2 gap-3",
|
|
2537
|
+
wrap = true,
|
|
2538
|
+
setFocusOnClick = true,
|
|
2539
|
+
prefetchOnHover = false,
|
|
2540
|
+
getKey
|
|
2541
|
+
}) {
|
|
2542
|
+
const { items, loading, error, refresh } = useFeed();
|
|
2543
|
+
const { setFocusedIndexImmediate } = useResource();
|
|
2544
|
+
const { adapters } = useSDK();
|
|
2545
|
+
const prefetchedRef = react.useRef(/* @__PURE__ */ new Set());
|
|
2546
|
+
const handleClick = react.useCallback(
|
|
2547
|
+
(id, item, index) => {
|
|
2548
|
+
if (setFocusOnClick) {
|
|
2549
|
+
setFocusedIndexImmediate(index);
|
|
2550
|
+
}
|
|
2551
|
+
onThumbnailClick?.(id, item, index);
|
|
2552
|
+
},
|
|
2553
|
+
[setFocusOnClick, setFocusedIndexImmediate, onThumbnailClick]
|
|
2554
|
+
);
|
|
2555
|
+
const handlePointerEnter = react.useCallback(
|
|
2556
|
+
(item) => {
|
|
2557
|
+
if (!prefetchOnHover) return;
|
|
2558
|
+
if (!isVideoItem(item)) return;
|
|
2559
|
+
if (item.source.type !== "hls") return;
|
|
2560
|
+
const url = item.source.url;
|
|
2561
|
+
if (prefetchedRef.current.has(url)) return;
|
|
2562
|
+
prefetchedRef.current.add(url);
|
|
2563
|
+
adapters.videoLoader?.preloadMetadata?.(url);
|
|
2564
|
+
},
|
|
2565
|
+
[prefetchOnHover, adapters.videoLoader]
|
|
2566
|
+
);
|
|
2567
|
+
if (loading && items.length === 0) {
|
|
2568
|
+
if (renderLoading) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderLoading() });
|
|
2569
|
+
return null;
|
|
2570
|
+
}
|
|
2571
|
+
if (error && items.length === 0) {
|
|
2572
|
+
if (renderError) {
|
|
2573
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderError({ message: error.message, retry: refresh }) });
|
|
2574
|
+
}
|
|
2575
|
+
return null;
|
|
2576
|
+
}
|
|
2577
|
+
if (!loading && items.length === 0) {
|
|
2578
|
+
if (renderEmpty) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderEmpty() });
|
|
2579
|
+
return null;
|
|
2580
|
+
}
|
|
2581
|
+
const content = items.map((item, index) => {
|
|
2582
|
+
const key = getKey ? getKey(item, index) : item.id;
|
|
2583
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2584
|
+
"button",
|
|
2585
|
+
{
|
|
2586
|
+
type: "button",
|
|
2587
|
+
onClick: () => handleClick(item.id, item, index),
|
|
2588
|
+
onPointerEnter: () => handlePointerEnter(item),
|
|
2589
|
+
style: {
|
|
2590
|
+
all: "unset",
|
|
2591
|
+
cursor: "pointer",
|
|
2592
|
+
display: "block",
|
|
2593
|
+
width: "100%"
|
|
2594
|
+
},
|
|
2595
|
+
children: renderThumbnail(item, index)
|
|
2596
|
+
},
|
|
2597
|
+
key
|
|
2598
|
+
);
|
|
2599
|
+
});
|
|
2600
|
+
if (!wrap) {
|
|
2601
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: content });
|
|
2602
|
+
}
|
|
2603
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className, children: content });
|
|
2604
|
+
}
|
|
2523
2605
|
function usePlayerSelector(selector) {
|
|
2524
2606
|
const { playerEngine } = useSDK();
|
|
2525
2607
|
const selectorRef = react.useRef(selector);
|
|
@@ -3156,6 +3238,7 @@ exports.OptimisticManager = OptimisticManager;
|
|
|
3156
3238
|
exports.PlayerEngine = PlayerEngine;
|
|
3157
3239
|
exports.PlayerStatus = PlayerStatus;
|
|
3158
3240
|
exports.ReelsFeed = ReelsFeed;
|
|
3241
|
+
exports.ReelsFeedThumbnail = ReelsFeedThumbnail;
|
|
3159
3242
|
exports.ReelsProvider = ReelsProvider;
|
|
3160
3243
|
exports.ResourceGovernor = ResourceGovernor;
|
|
3161
3244
|
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
|
@@ -587,16 +587,23 @@ var FeedManager = class {
|
|
|
587
587
|
applyItems(incoming, _nextCursor, append) {
|
|
588
588
|
const { itemsById, displayOrder } = this.store.getState();
|
|
589
589
|
const nextById = new Map(itemsById);
|
|
590
|
-
const existingIds = new Set(displayOrder);
|
|
591
|
-
const newIds = [];
|
|
592
590
|
for (const item of incoming) {
|
|
593
|
-
if (!existingIds.has(item.id)) {
|
|
594
|
-
newIds.push(item.id);
|
|
595
|
-
}
|
|
596
591
|
nextById.set(item.id, item);
|
|
597
592
|
this.accessOrder.set(item.id, Date.now());
|
|
598
593
|
}
|
|
599
|
-
|
|
594
|
+
let nextOrder;
|
|
595
|
+
if (append) {
|
|
596
|
+
const existingIds = new Set(displayOrder);
|
|
597
|
+
const newIds = [];
|
|
598
|
+
for (const item of incoming) {
|
|
599
|
+
if (!existingIds.has(item.id)) {
|
|
600
|
+
newIds.push(item.id);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
nextOrder = [...displayOrder, ...newIds];
|
|
604
|
+
} else {
|
|
605
|
+
nextOrder = incoming.map((item) => item.id);
|
|
606
|
+
}
|
|
600
607
|
if (nextById.size > this.config.maxCacheSize) {
|
|
601
608
|
this.evictLRU(nextById, nextOrder);
|
|
602
609
|
}
|
|
@@ -2514,6 +2521,81 @@ function parsePxTranslateY(el) {
|
|
|
2514
2521
|
if (!match || !match[1]) return 0;
|
|
2515
2522
|
return Number.parseFloat(match[1]);
|
|
2516
2523
|
}
|
|
2524
|
+
function ReelsFeedThumbnail({
|
|
2525
|
+
renderThumbnail,
|
|
2526
|
+
onThumbnailClick,
|
|
2527
|
+
renderLoading,
|
|
2528
|
+
renderEmpty,
|
|
2529
|
+
renderError,
|
|
2530
|
+
className = "grid grid-cols-2 gap-3",
|
|
2531
|
+
wrap = true,
|
|
2532
|
+
setFocusOnClick = true,
|
|
2533
|
+
prefetchOnHover = false,
|
|
2534
|
+
getKey
|
|
2535
|
+
}) {
|
|
2536
|
+
const { items, loading, error, refresh } = useFeed();
|
|
2537
|
+
const { setFocusedIndexImmediate } = useResource();
|
|
2538
|
+
const { adapters } = useSDK();
|
|
2539
|
+
const prefetchedRef = useRef(/* @__PURE__ */ new Set());
|
|
2540
|
+
const handleClick = useCallback(
|
|
2541
|
+
(id, item, index) => {
|
|
2542
|
+
if (setFocusOnClick) {
|
|
2543
|
+
setFocusedIndexImmediate(index);
|
|
2544
|
+
}
|
|
2545
|
+
onThumbnailClick?.(id, item, index);
|
|
2546
|
+
},
|
|
2547
|
+
[setFocusOnClick, setFocusedIndexImmediate, onThumbnailClick]
|
|
2548
|
+
);
|
|
2549
|
+
const handlePointerEnter = useCallback(
|
|
2550
|
+
(item) => {
|
|
2551
|
+
if (!prefetchOnHover) return;
|
|
2552
|
+
if (!isVideoItem(item)) return;
|
|
2553
|
+
if (item.source.type !== "hls") return;
|
|
2554
|
+
const url = item.source.url;
|
|
2555
|
+
if (prefetchedRef.current.has(url)) return;
|
|
2556
|
+
prefetchedRef.current.add(url);
|
|
2557
|
+
adapters.videoLoader?.preloadMetadata?.(url);
|
|
2558
|
+
},
|
|
2559
|
+
[prefetchOnHover, adapters.videoLoader]
|
|
2560
|
+
);
|
|
2561
|
+
if (loading && items.length === 0) {
|
|
2562
|
+
if (renderLoading) return /* @__PURE__ */ jsx(Fragment, { children: renderLoading() });
|
|
2563
|
+
return null;
|
|
2564
|
+
}
|
|
2565
|
+
if (error && items.length === 0) {
|
|
2566
|
+
if (renderError) {
|
|
2567
|
+
return /* @__PURE__ */ jsx(Fragment, { children: renderError({ message: error.message, retry: refresh }) });
|
|
2568
|
+
}
|
|
2569
|
+
return null;
|
|
2570
|
+
}
|
|
2571
|
+
if (!loading && items.length === 0) {
|
|
2572
|
+
if (renderEmpty) return /* @__PURE__ */ jsx(Fragment, { children: renderEmpty() });
|
|
2573
|
+
return null;
|
|
2574
|
+
}
|
|
2575
|
+
const content = items.map((item, index) => {
|
|
2576
|
+
const key = getKey ? getKey(item, index) : item.id;
|
|
2577
|
+
return /* @__PURE__ */ jsx(
|
|
2578
|
+
"button",
|
|
2579
|
+
{
|
|
2580
|
+
type: "button",
|
|
2581
|
+
onClick: () => handleClick(item.id, item, index),
|
|
2582
|
+
onPointerEnter: () => handlePointerEnter(item),
|
|
2583
|
+
style: {
|
|
2584
|
+
all: "unset",
|
|
2585
|
+
cursor: "pointer",
|
|
2586
|
+
display: "block",
|
|
2587
|
+
width: "100%"
|
|
2588
|
+
},
|
|
2589
|
+
children: renderThumbnail(item, index)
|
|
2590
|
+
},
|
|
2591
|
+
key
|
|
2592
|
+
);
|
|
2593
|
+
});
|
|
2594
|
+
if (!wrap) {
|
|
2595
|
+
return /* @__PURE__ */ jsx(Fragment, { children: content });
|
|
2596
|
+
}
|
|
2597
|
+
return /* @__PURE__ */ jsx("div", { className, children: content });
|
|
2598
|
+
}
|
|
2517
2599
|
function usePlayerSelector(selector) {
|
|
2518
2600
|
const { playerEngine } = useSDK();
|
|
2519
2601
|
const selectorRef = useRef(selector);
|
|
@@ -3128,4 +3210,4 @@ var HttpError = class extends Error {
|
|
|
3128
3210
|
}
|
|
3129
3211
|
};
|
|
3130
3212
|
|
|
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 };
|
|
3213
|
+
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 };
|