@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/README.md
CHANGED
|
@@ -86,16 +86,53 @@ function MyFeed() {
|
|
|
86
86
|
}
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
## Thumbnail grid
|
|
89
|
+
## Thumbnail grid → Modal
|
|
90
90
|
|
|
91
|
-
`<ReelsFeedThumbnail>` is a
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
`<ReelsFeedThumbnail>` is a grid component for the pre-player browsing UX (e.g. a
|
|
92
|
+
community feed that opens the player on tap). It must be mounted inside
|
|
93
|
+
`<ReelsProvider>` and reads items from the same shared feed. The host provides
|
|
94
|
+
card visuals via `renderThumbnail`.
|
|
95
|
+
|
|
96
|
+
### SDK-owned modal (recommended)
|
|
97
|
+
|
|
98
|
+
Pair `<ReelsFeedThumbnail openOnClick>` with `<ReelsModal>` to let the SDK own the
|
|
99
|
+
open→play lifecycle end to end. This eliminates the "poster stuck for ~3s" stall:
|
|
100
|
+
on tap, the SDK **mounts the feed offscreen-but-painted, prewarms the focused
|
|
101
|
+
video and its neighbors synchronously, then slides the panel in** — so the first
|
|
102
|
+
frame is already decoded by the time the animation finishes.
|
|
95
103
|
|
|
96
104
|
```tsx
|
|
97
|
-
import { ReelsProvider, ReelsFeedThumbnail } from 'xhub-reels-sdk';
|
|
105
|
+
import { ReelsProvider, ReelsFeedThumbnail, ReelsModal } from 'xhub-reels-sdk';
|
|
106
|
+
|
|
107
|
+
function CommunityPage() {
|
|
108
|
+
return (
|
|
109
|
+
<ReelsProvider adapters={{ dataSource, interaction }}>
|
|
110
|
+
<ReelsFeedThumbnail
|
|
111
|
+
openOnClick
|
|
112
|
+
renderThumbnail={(item) => (
|
|
113
|
+
<article className="aspect-[3/2] rounded-lg overflow-hidden">
|
|
114
|
+
<img src={item.poster} alt="" />
|
|
115
|
+
<p>@{item.author.name}</p>
|
|
116
|
+
</article>
|
|
117
|
+
)}
|
|
118
|
+
/>
|
|
119
|
+
<ReelsModal feedProps={{ renderOverlay, renderActions, initialMuted: false }} />
|
|
120
|
+
</ReelsProvider>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`openOnClick` calls `navigation.open(index)` on tap; `<ReelsModal>` subscribes to
|
|
126
|
+
the same navigation store and drives the rest. Because `play()` now starts inside
|
|
127
|
+
the tap's transient-activation window, **`initialMuted={false}` unmuted autoplay
|
|
128
|
+
is far more reliable** on mobile WebViews.
|
|
129
|
+
|
|
130
|
+
### Headless callback (back-compat)
|
|
98
131
|
|
|
132
|
+
The original headless approach still works — drive your own drawer/modal via
|
|
133
|
+
`onThumbnailClick`:
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
99
136
|
function CommunityPage() {
|
|
100
137
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
101
138
|
|
|
@@ -105,7 +142,6 @@ function CommunityPage() {
|
|
|
105
142
|
renderThumbnail={(item) => (
|
|
106
143
|
<article className="aspect-[3/2] rounded-lg overflow-hidden">
|
|
107
144
|
<img src={item.poster} alt="" />
|
|
108
|
-
<p>@{item.author.name}</p>
|
|
109
145
|
</article>
|
|
110
146
|
)}
|
|
111
147
|
onThumbnailClick={(id) => {
|
|
@@ -113,7 +149,7 @@ function CommunityPage() {
|
|
|
113
149
|
window.history.replaceState(null, '', `#reel_uuid=${id}`);
|
|
114
150
|
}}
|
|
115
151
|
/>
|
|
116
|
-
{drawerOpen && <
|
|
152
|
+
{drawerOpen && <YourDrawer onClose={() => setDrawerOpen(false)} />}
|
|
117
153
|
</ReelsProvider>
|
|
118
154
|
);
|
|
119
155
|
}
|
|
@@ -124,21 +160,48 @@ By default, clicking a card calls `setFocusedIndexImmediate(index)` on the
|
|
|
124
160
|
disable this glue (e.g. if the host manages focus separately), pass
|
|
125
161
|
`setFocusOnClick={false}`.
|
|
126
162
|
|
|
127
|
-
###
|
|
163
|
+
### `<ReelsFeedThumbnail>` props
|
|
128
164
|
|
|
129
165
|
| Prop | Type | Default | Description |
|
|
130
166
|
|---|---|---|---|
|
|
131
167
|
| `renderThumbnail` | `(item, index) => ReactNode` | required | Card visual for a single item |
|
|
132
|
-
| `
|
|
168
|
+
| `openOnClick` | `boolean` | `false` | Open the SDK-owned `<ReelsModal>` via `navigation.open(index)` on tap |
|
|
169
|
+
| `prewarmOnClick` | `boolean` | `true` | Fire HLS metadata prefetch on tap (mobile-friendly; runs even without hover) |
|
|
170
|
+
| `onThumbnailClick` | `(id, item, index) => void` | — | Click handler (fires alongside `open` for back-compat) |
|
|
171
|
+
| `setFocusOnClick` | `boolean` | `true` | Pre-focus the slot before `open`/`onThumbnailClick` fires |
|
|
172
|
+
| `prefetchOnHover` | `boolean` | `false` | Opt-in HLS metadata prefetch on `pointerenter` |
|
|
133
173
|
| `renderLoading` | `() => ReactNode` | — | Shown while loading with no items |
|
|
134
174
|
| `renderEmpty` | `() => ReactNode` | — | Shown when feed is empty |
|
|
135
175
|
| `renderError` | `({ message, retry }) => ReactNode` | — | Shown on error with no items |
|
|
136
176
|
| `className` | `string` | `'grid grid-cols-2 gap-3'` | Outer wrapper className |
|
|
137
177
|
| `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
178
|
| `getKey` | `(item, index) => string` | `item.id` | Key override for duplicate lists |
|
|
141
179
|
|
|
180
|
+
### `<ReelsModal>` props
|
|
181
|
+
|
|
182
|
+
The modal is a **customizable shell**: the SDK owns timing and prewarm, while the
|
|
183
|
+
host can override the animation and chrome. Omit any prop to fall back to a
|
|
184
|
+
sensible default.
|
|
185
|
+
|
|
186
|
+
| Prop | Type | Default | Description |
|
|
187
|
+
|---|---|---|---|
|
|
188
|
+
| `feedProps` | `ReelsFeedProps` | — | Render props forwarded to the inner `<ReelsFeed>` (`renderOverlay`, `renderActions`, `initialMuted`, …) |
|
|
189
|
+
| `animationConfig` | `{ duration?, easing?, direction? }` | `{ 300, cubic-bezier(0.22, 1, 0.36, 1), 'up' }` | Slide-in tuning; `direction` is `'up' \| 'down' \| 'fade'` |
|
|
190
|
+
| `renderBackdrop` | `(state) => ReactNode` | dimmed overlay | Custom backdrop; `state` is `{ phase, openIndex, close }` |
|
|
191
|
+
| `renderCloseButton` | `(state) => ReactNode` | default ✕ button | Custom close affordance |
|
|
192
|
+
| `onOpen` | `(index) => void` | — | Fired when a modal session opens |
|
|
193
|
+
| `onClose` | `() => void` | — | Fired when the modal fully closes |
|
|
194
|
+
| `closeOnBackdropClick` | `boolean` | `true` | Close when the backdrop is clicked |
|
|
195
|
+
| `closeOnEscape` | `boolean` | `true` | Close on the `Escape` key |
|
|
196
|
+
| `lockBodyScroll` | `boolean` | `true` | Lock `<body>` scroll while open |
|
|
197
|
+
| `prewarmForward` | `number` | `2` | How many forward neighbors to prewarm on open |
|
|
198
|
+
| `className` | `string` | — | Class on the sliding panel |
|
|
199
|
+
| `zIndex` | `number` | `1000` | Stacking context for the portal |
|
|
200
|
+
| `portalTarget` | `HTMLElement \| null` | `document.body` | Portal mount node |
|
|
201
|
+
|
|
202
|
+
`<ReelsModal>` honors `prefers-reduced-motion` (skips the slide), traps focus,
|
|
203
|
+
restores focus to the trigger on close, and renders into a portal.
|
|
204
|
+
|
|
142
205
|
### Hover prefetch
|
|
143
206
|
|
|
144
207
|
For hover-capable devices, opt into manifest prefetch so opening the player
|
|
@@ -150,7 +213,22 @@ feels instant:
|
|
|
150
213
|
|
|
151
214
|
The SDK uses `adapters.videoLoader?.preloadMetadata?.(url)` and dedupes per item
|
|
152
215
|
so a card only triggers one prefetch regardless of how many times it's hovered.
|
|
153
|
-
|
|
216
|
+
On touch devices there's no hover, so prefer `prewarmOnClick` (on by default).
|
|
217
|
+
|
|
218
|
+
### Migrating from the headless callback
|
|
219
|
+
|
|
220
|
+
If you currently open your own drawer from `onThumbnailClick`:
|
|
221
|
+
|
|
222
|
+
1. Add `openOnClick` to `<ReelsFeedThumbnail>` and mount `<ReelsModal>` as a
|
|
223
|
+
sibling inside `<ReelsProvider>`.
|
|
224
|
+
2. Move your drawer's render props (`renderOverlay`, `renderActions`,
|
|
225
|
+
`initialMuted`, …) onto `<ReelsModal feedProps={{ … }}>`.
|
|
226
|
+
3. Re-create your drawer's look with `animationConfig`, `renderBackdrop`, and
|
|
227
|
+
`renderCloseButton` — or drop them to use the defaults.
|
|
228
|
+
4. Remove the host `drawerOpen` state and your `<ReelsDrawer>`/modal wrapper.
|
|
229
|
+
|
|
230
|
+
`onThumbnailClick` still fires alongside `open`, so you can migrate
|
|
231
|
+
incrementally (e.g. keep your URL-hash side effect) before deleting host state.
|
|
154
232
|
|
|
155
233
|
## Gesture Engine
|
|
156
234
|
|