feat(react)!: make poster composable - #2563
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
✅ Deploy Preview for vjs10-site ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
@videojs/cdn
@videojs/core
@videojs/element
@videojs/html
@videojs/media
@videojs/react
@videojs/spf
@videojs/store
@videojs/utils
@videojs/cloudflare-video
@videojs/dash-video
@videojs/hlsjs-video
@videojs/mux-audio
@videojs/mux-video
@videojs/native-hls-video
@videojs/shaka-video
@videojs/spotify-audio
@videojs/tiktok-video
@videojs/twitch-video
@videojs/vimeo-video
@videojs/wistia-video
@videojs/youtube-video
@videojs/google-cast
@videojs/mux-data
commit: |
📦 Bundle Size Report🎨 @videojs/html — 17 small size changes
Presets (7)
Media (18)
Players (5)
Skins (29)
UI Components (62)
⚛️ @videojs/react
Small changes (12, ≤ 300 B)
Presets (7)
Media (22)
Extensions (2)
Players (5)
Skins (18)
UI Components (39)
🧩 @videojs/core — 1 small size change
Entries (76)
🏷️ @videojs/element — no changesEntries (2)
📦 @videojs/store — no changesEntries (3)
🔧 @videojs/utils — no changesEntries (13)
📦 @videojs/cdn — no changes📦 @videojs/cloudflare-video — no changes📦 @videojs/dash-video — no changes📦 @videojs/google-cast — no changes📦 @videojs/hlsjs-video — no changes📦 @videojs/media — no changesEntries (3)
📦 @videojs/mux — no changes📦 @videojs/mux-audio — no changesEntries (2)
📦 @videojs/mux-data — no changes📦 @videojs/mux-video — no changesEntries (2)
📦 @videojs/native-hls-video — no changes📦 @videojs/shaka-video — no changes📦 @videojs/spf — no changesEntries (7)
📦 @videojs/spotify-audio — no changes📦 @videojs/tiktok-video — no changes📦 @videojs/twitch-video — no changes📦 @videojs/vimeo-video — no changes📦 @videojs/wistia-video — no changesEntries (2)
📦 @videojs/youtube-video — no changesℹ️ How to interpretEach entry is independently bundled, minified, and brotli-compressed. Initial size includes its static import graph; lazy dynamic chunks are reported separately. Entries are not additive because their dependency graphs overlap. Preset rows represent realistic combined bundles. Changes of 300 B or less across initial, lazy, and total size are collapsed, not discarded. Run |
40f2b36 to
e29fb2c
Compare
7cd5d2c to
3792691
Compare
3792691 to
322e11d
Compare
42b1f0d to
1ca1f90
Compare
1ca1f90 to
9857133
Compare
9857133 to
1beeab3
Compare
1beeab3 to
7c1870c
Compare
✅ Deploy Preview for vjs-registry ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
7c1870c to
2f38686
Compare
Align the React poster API with the HTML wrapper/image structure by replacing the single `<Poster />` component with `<Poster.Root><Poster.Image /></Poster.Root>`, and give `<media-poster>` a fallback image so it renders on its own. `Poster.Root` owns visibility and image-loading state, while `Poster.Image` owns source resolution and the native image lifecycle, so applications can add blur previews, overlays, and temporary sibling layers without replacing the controlled poster image. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`Poster.Image` told `Poster.Root` about a resolving source from a passive effect, so the root's first frame rendered without `data-loading` and layers keyed off that attribute flashed. A layout effect updates the root before paint. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A wrapper's public props come from the part that receives its rest props, and so far its `children` did too. A compound wrapper can hand children to a nested part instead, such as a poster root passing them to the image's `render`, so the generated props now type `children` from whichever part renders them and leave an explicitly declared `children` member alone. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The shared Poster skin component renders `Poster.Root` around a `Poster.Image` part, so both targets emit a `media-poster` root with a `media-poster-image` inside it and the image part owns its own sizing and source-less hiding. The root keeps only the Shadow DOM rules an image slotted from outside a skin needs, which closes the tracked poster ownership gap. Ejected HTML skins and the Sandbox's slotted posters carry the image class over, and the E2E selectors follow the new root. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2f38686 to
a522d07
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 931d1b5. Configure here.
| else if (img.complete && (img.getAttribute('src') || img.hasAttribute('srcset'))) { | ||
| setRequest({ src, srcSet, state: 'error' }); | ||
| } | ||
| }, |
There was a problem hiding this comment.
Cached remount never settles load state
Medium Severity
didCheckInitialImageRef stays true after the first image node is seen, including when that node later unmounts. A render override that returns null without a src (the documented renderPoster pattern) then remounts a cached image, which can decode before onLoad is attached and never fire load. The root is left on data-loading, so sibling layers keyed off that attribute never clear.
Reviewed by Cursor Bugbot for commit 931d1b5. Configure here.


Summary
Align the React poster API with the HTML wrapper/image structure by replacing the single
<Poster />component with<Poster.Root><Poster.Image /></Poster.Root>, and give<media-poster>a fallback image so it renders on its own.Poster.Rootowns visibility and image-loading state, whilePoster.Imageowns source resolution and the native image lifecycle. This lets applications add blur previews, overlays, and temporary sibling layers without replacing the controlled poster image. The existingrenderPosterpreset escape hatch remains image-level throughPoster.Image.Changes
Poster.Root,Poster.Image, and their public prop typesdata-loading<img part="image">in the<media-poster>shadow root only while no image is supplied, and set it aside as soon as one isPostertakes the core image props plus arenderImageescape hatch, forwards them toPoster.Imageinside the poster slot, and places its children beside the slot as sibling layers, so both targets emit amedia-posterroot with amedia-poster-imagepart; the image part owns its sizing and source-less hiding, and the root keeps only the Shadow DOM rules a slotted image needs, which closes the tracked poster ownership gapchildrenby the part that renders them and to follow the target's children name when a prop indexesPropsOf<typeof $.Part>['children'], sorenderImagelowers toPoster.ImageProps['render']in ReactBreaking change
React consumers must migrate from
<Poster />to<Poster.Root><Poster.Image /></Poster.Root>. Styling that targeted the former image should move image rules tomedia-poster-image; root visibility and loading rules belong onmedia-poster.Testing
pnpm -F @videojs/react test src/ui/poster src/presetspnpm -F @videojs/html test src/ui/poster src/presetspnpm -F @videojs/skins testpnpm -F vjsc test src/plugins/tests/target-type.test.tspnpm typecheckpnpm lintNote
Medium Risk
Breaking React API and DOM structure for poster styling require consumer migration; poster visibility and image lifecycle logic changed in both HTML and React bindings.
Overview
Breaking React change: the single
<Poster />image component becomes a compound API—<Poster.Root>holds visibility anddata-*load state;<Poster.Image>owns source resolution, native image props, andrender. Sibling layers (blur, overlays) can sit inside the root without replacing the controlled image.renderPosteron skins/presets maps toPoster.Image'srenderviarenderImageon the skinPosterwrapper.HTML
<media-poster>now uses an open shadow root with a fallback<img part="image">when no child image is present (replacing the dev warning for missing images). A supplied or slotted image takes over and the fallback is removed so two images never coexist.Skins & tooling: VJSC skins emit
Poster.Root+Poster.Imagewith.media-poster/.media-poster-imagestyling split; poster ownership gap ingaps.mdis closed. The VJSC target-type plugin types wrapper props likerenderImageasPoster.ImageProps['render']when children flow to a nested part. E2E selectors and parity tests target the poster root instead of bareimgelements.Docs & tests updated for migration (
Poster.Root/Poster.Image), shadow fallback behavior, and compound composition cases including SSRdata-loadingon the root.Reviewed by Cursor Bugbot for commit 931d1b5. Bugbot is set up for automated code reviews on this repo. Configure here.