Skip to content

feat(react)!: make poster composable - #2563

Merged
mihar-22 merged 7 commits into
mainfrom
t3code/align-poster-html-api
Sep 9, 2026
Merged

feat(react)!: make poster composable#2563
mihar-22 merged 7 commits into
mainfrom
t3code/align-poster-html-api

Conversation

@mihar-22

@mihar-22 mihar-22 commented Aug 31, 2026

Copy link
Copy Markdown
Member

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.Root owns visibility and image-loading state, while Poster.Image owns 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 existing renderPoster preset escape hatch remains image-level through Poster.Image.

Changes

  • expose Poster.Root, Poster.Image, and their public prop types
  • move poster state attributes to the root and preserve native image props/render customization on the image; the image reports its load state to the root before paint and resets it from the same layout effect, and the root treats a resolved poster as loading until the image reports, so server markup already carries data-loading
  • draw a fallback <img part="image"> in the <media-poster> shadow root only while no image is supplied, and set it aside as soon as one is
  • author the poster compound in the VJSC skins: the skin Poster takes the core image props plus a renderImage escape hatch, forwards them to Poster.Image inside the poster slot, and places its children beside the slot as sibling layers, so both targets emit a media-poster root with a media-poster-image part; 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 gap
  • teach the VJSC compiler to type a generated wrapper's children by the part that renders them and to follow the target's children name when a prop indexes PropsOf<typeof $.Part>['children'], so renderImage lowers to Poster.ImageProps['render'] in React
  • carry the image class over for slotted Sandbox posters, and follow the new root in the E2E selectors
  • update reference docs, the design record, migration guidance, and examples
  • cover sibling composition, root-level loading state, and the HTML fallback in tests

Breaking change

React consumers must migrate from <Poster /> to <Poster.Root><Poster.Image /></Poster.Root>. Styling that targeted the former image should move image rules to media-poster-image; root visibility and loading rules belong on media-poster.

Testing

  • pnpm -F @videojs/react test src/ui/poster src/presets
  • pnpm -F @videojs/html test src/ui/poster src/presets
  • pnpm -F @videojs/skins test
  • pnpm -F vjsc test src/plugins/tests/target-type.test.ts
  • pnpm typecheck
  • pnpm lint

Note

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 and data-* load state; <Poster.Image> owns source resolution, native image props, and render. Sibling layers (blur, overlays) can sit inside the root without replacing the controlled image. renderPoster on skins/presets maps to Poster.Image's render via renderImage on the skin Poster wrapper.

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.Image with .media-poster / .media-poster-image styling split; poster ownership gap in gaps.md is closed. The VJSC target-type plugin types wrapper props like renderImage as Poster.ImageProps['render'] when children flow to a nested part. E2E selectors and parity tests target the poster root instead of bare img elements.

Docs & tests updated for migration (Poster.Root / Poster.Image), shadow fallback behavior, and compound composition cases including SSR data-loading on the root.

Reviewed by Cursor Bugbot for commit 931d1b5. Bugbot is set up for automated code reviews on this repo. Configure here.

@vercel

vercel Bot commented Aug 31, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
v10-sandbox Ready Ready Preview Sep 9, 2026 5:20am UTC

Request Review

@netlify

netlify Bot commented Aug 31, 2026

Copy link
Copy Markdown

Deploy Preview for vjs10-site ready!

Name Link
🔨 Latest commit 931d1b5
🔍 Latest deploy log https://app.netlify.com/projects/vjs10-site/deploys/6aa0ec2da3f2100007f9ca00
😎 Deploy Preview https://deploy-preview-2563--vjs10-site.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@pkg-pr-new

pkg-pr-new Bot commented Aug 31, 2026

Copy link
Copy Markdown
@videojs/cdn

pnpm add https://pkg.pr.new/videojs/v10/@videojs/cdn@2563

@videojs/core

pnpm add https://pkg.pr.new/videojs/v10/@videojs/core@2563

@videojs/element

pnpm add https://pkg.pr.new/videojs/v10/@videojs/element@2563

@videojs/html

pnpm add https://pkg.pr.new/videojs/v10/@videojs/html@2563

@videojs/media

pnpm add https://pkg.pr.new/videojs/v10/@videojs/media@2563

@videojs/react

pnpm add https://pkg.pr.new/videojs/v10/@videojs/react@2563

@videojs/spf

pnpm add https://pkg.pr.new/videojs/v10/@videojs/spf@2563

@videojs/store

pnpm add https://pkg.pr.new/videojs/v10/@videojs/store@2563

@videojs/utils

pnpm add https://pkg.pr.new/videojs/v10/@videojs/utils@2563

@videojs/cloudflare-video

pnpm add https://pkg.pr.new/videojs/v10/@videojs/cloudflare-video@2563

@videojs/dash-video

pnpm add https://pkg.pr.new/videojs/v10/@videojs/dash-video@2563

@videojs/hlsjs-video

pnpm add https://pkg.pr.new/videojs/v10/@videojs/hlsjs-video@2563

@videojs/mux-audio

pnpm add https://pkg.pr.new/videojs/v10/@videojs/mux-audio@2563

@videojs/mux-video

pnpm add https://pkg.pr.new/videojs/v10/@videojs/mux-video@2563

@videojs/native-hls-video

pnpm add https://pkg.pr.new/videojs/v10/@videojs/native-hls-video@2563

@videojs/shaka-video

pnpm add https://pkg.pr.new/videojs/v10/@videojs/shaka-video@2563

@videojs/spotify-audio

pnpm add https://pkg.pr.new/videojs/v10/@videojs/spotify-audio@2563

@videojs/tiktok-video

pnpm add https://pkg.pr.new/videojs/v10/@videojs/tiktok-video@2563

@videojs/twitch-video

pnpm add https://pkg.pr.new/videojs/v10/@videojs/twitch-video@2563

@videojs/vimeo-video

pnpm add https://pkg.pr.new/videojs/v10/@videojs/vimeo-video@2563

@videojs/wistia-video

pnpm add https://pkg.pr.new/videojs/v10/@videojs/wistia-video@2563

@videojs/youtube-video

pnpm add https://pkg.pr.new/videojs/v10/@videojs/youtube-video@2563

@videojs/google-cast

pnpm add https://pkg.pr.new/videojs/v10/@videojs/google-cast@2563

@videojs/mux-data

pnpm add https://pkg.pr.new/videojs/v10/@videojs/mux-data@2563

commit: bf73eb2

@github-actions

github-actions Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle Size Report

🎨 @videojs/html — 17 small size changes
Path Base initial PR initial Initial diff Lazy diff
/video/minimal-skin.css 7.68 kB 7.67 kB -16 B (-0.2%)
/video/skin.css 7.50 kB 7.48 kB -16 B (-0.2%)
/video/minimal-skin 61.55 kB 61.64 kB +90 B (+0.1%) 0 B
/video/minimal-skin2 8.15 kB 8.12 kB -25 B (-0.3%)
/video/skin 61.31 kB 61.41 kB +104 B (+0.2%) 0 B
/video/skin2 8.00 kB 7.99 kB -13 B (-0.2%)
/live-video/minimal-skin.css 6.23 kB 6.21 kB -13 B (-0.2%)
/live-video/skin.css 6.20 kB 6.21 kB +4 B (+0.1%)
/live-video/minimal-skin 49.39 kB 49.47 kB +83 B (+0.2%) 0 B
/live-video/minimal-skin2 6.58 kB 6.56 kB -18 B (-0.3%)
/live-video/skin 49.40 kB 49.66 kB +265 B (+0.5%) 0 B
/live-video/skin2 6.61 kB 6.61 kB -7 B (-0.1%)
/ui/poster 8.67 kB 8.89 kB +219 B (+2.5%) 0 B
/video (default) 62.82 kB 62.91 kB +90 B (+0.1%) 0 B
/video (default + hls) 64.55 kB 64.63 kB +89 B (+0.1%) 0 B
/video (minimal) 62.97 kB 63.18 kB +212 B (+0.3%) 0 B
/video (minimal + hls) 64.91 kB 64.90 kB -6 B (-0.0%) 0 B
Presets (7)
Entry Initial Lazy
/video (default) 62.91 kB 56.87 kB
/video (default + hls) 64.63 kB 56.87 kB
/video (minimal) 63.18 kB 56.87 kB
/video (minimal + hls) 64.90 kB 56.87 kB
/audio (default) 46.99 kB 56.87 kB
/audio (minimal) 47.29 kB 56.87 kB
/background 11.57 kB 56.87 kB
Media (18)
Entry Initial
/media/mux-video 3.02 kB
/media/mux-audio 2.69 kB
/media/background-video 1.16 kB
/media/cloudflare-video 2.90 kB
/media/dash-video 2.63 kB
/media/hls-audio 22.05 kB
/media/hls-background-video 16.86 kB
/media/hls-video 28.00 kB
/media/hlsjs-video 2.63 kB
/media/mux-background-video 16.86 kB
/media/native-hls-video 7.57 kB
/media/shaka-video 2.63 kB
/media/spotify-audio 3.02 kB
/media/tiktok-video 3.13 kB
/media/twitch-video 2.93 kB
/media/vimeo-video 2.90 kB
/media/wistia-video 607 B
/media/youtube-video 2.90 kB
Players (5)
Entry Initial Lazy
/video/player 9.69 kB 56.87 kB
/audio/player 6.14 kB 56.87 kB
/background/player 4.46 kB 56.87 kB
/live-video/player 9.06 kB 56.87 kB
/live-audio/player 6.15 kB 56.87 kB
Skins (29)
Entry Type Initial Lazy
/video/minimal-skin.css css 7.67 kB
/video/skin.css css 7.48 kB
/video/minimal-skin js 61.64 kB 56.87 kB
/video/minimal-skin2 js 8.12 kB
/video/skin js 61.41 kB 56.87 kB
/video/skin2 js 7.99 kB
/audio/minimal-skin.css css 5.55 kB
/audio/skin.css css 5.37 kB
/audio/minimal-skin js 45.81 kB 56.87 kB
/audio/minimal-skin2 js 5.88 kB
/audio/skin js 45.54 kB 56.87 kB
/audio/skin2 js 5.69 kB
/background/skin.css css 139 B
/background/skin js 9.81 kB 56.87 kB
/background/skin2 js 187 B
/live-video/minimal-skin.css css 6.21 kB
/live-video/skin.css css 6.21 kB
/live-video/minimal-skin js 49.47 kB 56.87 kB
/live-video/minimal-skin2 js 6.56 kB
/live-video/skin js 49.66 kB 56.87 kB
/live-video/skin2 js 6.61 kB
/live-audio/minimal-skin.css css 4.42 kB
/live-audio/skin.css css 4.39 kB
/live-audio/minimal-skin js 35.47 kB 56.87 kB
/live-audio/minimal-skin2 js 4.68 kB
/live-audio/skin js 35.46 kB 56.87 kB
/live-audio/skin2 js 4.66 kB
/global.css css 190 B
/shared.css css 118 B
UI Components (62)
Entry Initial Lazy
/ui/airplay-button 11.26 kB 56.87 kB
/ui/alert-dialog 8.79 kB 56.87 kB
/ui/audio-track-radio-group 11.38 kB 56.87 kB
/ui/buffering-indicator 8.60 kB 56.87 kB
/ui/captions-button 11.51 kB 56.87 kB
/ui/captions-radio-group 11.45 kB 56.87 kB
/ui/cast-button 11.25 kB 56.87 kB
/ui/container 9.47 kB 56.87 kB
/ui/controls 8.77 kB 56.87 kB
/ui/controls-backdrop 1.69 kB 56.87 kB
/ui/controls-content 1.67 kB 56.87 kB
/ui/dialog 8.73 kB 56.87 kB
/ui/dialog-backdrop 1.70 kB 56.87 kB
/ui/dialog-close 2.06 kB 56.87 kB
/ui/dialog-description 1.69 kB 56.87 kB
/ui/dialog-popup 1.90 kB 56.87 kB
/ui/dialog-title 1.70 kB 56.87 kB
/ui/error-dialog 12.38 kB 56.87 kB
/ui/fullscreen-button 11.26 kB 56.87 kB
/ui/gesture 9.57 kB 56.87 kB
/ui/hotkey 9.97 kB 56.87 kB
/ui/live-button 9.74 kB 56.87 kB
/ui/menu 18.45 kB 56.87 kB
/ui/menu-checkbox-item 1.97 kB 56.87 kB
/ui/menu-content 7.52 kB 56.87 kB
/ui/menu-group 2.09 kB 56.87 kB
/ui/menu-group-label 1.64 kB
/ui/menu-item 2.29 kB 56.87 kB
/ui/menu-item-indicator 1.37 kB 56.87 kB
/ui/menu-radio-group 3.01 kB 56.87 kB
/ui/menu-radio-item 2.13 kB 56.87 kB
/ui/menu-separator 1.30 kB 56.87 kB
/ui/mute-button 11.28 kB 56.87 kB
/ui/pip-button 11.22 kB 56.87 kB
/ui/play-button 11.22 kB 56.87 kB
/ui/playback-rate-button 11.32 kB 56.87 kB
/ui/playback-rate-radio-group 11.38 kB 56.87 kB
/ui/popover 8.57 kB 56.87 kB
/ui/poster 8.89 kB 56.87 kB
/ui/quality-radio-group 11.97 kB 56.87 kB
/ui/seek-button 11.27 kB 56.87 kB
/ui/seek-indicator 12.69 kB 56.87 kB
/ui/seek-indicator-value 1.10 kB
/ui/slider 11.65 kB 56.87 kB
/ui/slider-preview 2.01 kB 56.87 kB
/ui/status-announcer 10.60 kB 56.87 kB
/ui/status-indicator 13.01 kB 56.87 kB
/ui/status-indicator-value 1.10 kB
/ui/thumbnail 9.84 kB 56.87 kB
/ui/time 10.80 kB 56.87 kB
/ui/time-slider 12.99 kB 56.87 kB
/ui/time-slider-chapter-title 8.79 kB 56.87 kB
/ui/time-slider-chapters 9.63 kB 56.87 kB
/ui/title 8.17 kB 56.87 kB
/ui/tooltip 9.95 kB 56.87 kB
/ui/tooltip-label 1.21 kB
/ui/tooltip-shortcut 1.18 kB
/ui/volume-indicator 13.02 kB 56.87 kB
/ui/volume-indicator-fill 1.11 kB
/ui/volume-indicator-value 1.10 kB
/ui/volume-popover 14.74 kB 56.87 kB
/ui/volume-slider 12.31 kB 56.87 kB

⚛️ @videojs/react

Path Base initial PR initial Initial diff Lazy diff
/ui/poster 7.57 kB 7.88 kB +314 B (+4.1%) 0 B
Small changes (12, ≤ 300 B)
Path Base initial PR initial Initial diff Lazy diff
/video/minimal-skin.css 7.53 kB 7.53 kB -2 B (-0.0%)
/video/skin.css 7.35 kB 7.34 kB -8 B (-0.1%)
/video/minimal-skin 50.63 kB 50.82 kB +200 B (+0.4%) 0 B
/video/skin 50.50 kB 50.70 kB +203 B (+0.4%) 0 B
/live-video/minimal-skin.css 6.07 kB 6.06 kB -7 B (-0.1%)
/live-video/skin.css 6.05 kB 6.05 kB -3 B (-0.0%)
/live-video/minimal-skin 42.57 kB 42.67 kB +107 B (+0.2%) 0 B
/live-video/skin 42.52 kB 42.69 kB +174 B (+0.4%) 0 B
/video (default) 50.61 kB 50.79 kB +188 B (+0.4%) 0 B
/video (default + hls) 50.83 kB 51.00 kB +174 B (+0.3%) 0 B
/video (minimal) 50.66 kB 50.93 kB +269 B (+0.5%) 0 B
/video (minimal + hls) 51.06 kB 51.25 kB +195 B (+0.4%) 0 B
Presets (7)
Entry Initial Lazy
/video (default) 50.79 kB 56.87 kB
/video (default + hls) 51.00 kB 56.87 kB
/video (minimal) 50.93 kB 56.87 kB
/video (minimal + hls) 51.25 kB 56.87 kB
/audio (default) 39.50 kB 56.87 kB
/audio (minimal) 39.56 kB 56.87 kB
/background 581 B 56.87 kB
Media (22)
Entry Initial
/media/mux-video/hls-js 958 B
/media/mux-video/spf 961 B
/media/mux-audio/hls-js 699 B
/media/mux-audio/spf 703 B
/media/background-video 402 B
/media/cloudflare-video 911 B
/media/dash-video 693 B
/media/hls-audio 20.36 kB
/media/hls-background-video 16.40 kB
/media/hls-video 26.35 kB
/media/hlsjs-video 695 B
/media/mux-audio 693 B
/media/mux-background-video 16.47 kB
/media/mux-video 950 B
/media/native-hls-video 5.77 kB
/media/shaka-video 704 B
/media/spotify-audio 901 B
/media/tiktok-video 906 B
/media/twitch-video 929 B
/media/vimeo-video 888 B
/media/wistia-video 619 B
/media/youtube-video 897 B
Extensions (2)
Entry Initial
/extensions/google-cast 1.59 kB
/extensions/mux-data 1.64 kB
Players (5)
Entry Initial Lazy
/video/player 7.56 kB 56.87 kB
/audio/player 4.00 kB 56.87 kB
/background/player 2.27 kB 56.87 kB
/live-video/player 6.95 kB 56.87 kB
/live-audio/player 3.99 kB 56.87 kB
Skins (18)
Entry Type Initial Lazy
/video/minimal-skin.css css 7.53 kB
/video/skin.css css 7.34 kB
/video/minimal-skin js 50.82 kB 56.87 kB
/video/skin js 50.70 kB 56.87 kB
/audio/minimal-skin.css css 5.37 kB
/audio/skin.css css 5.18 kB
/audio/minimal-skin js 39.46 kB 56.87 kB
/audio/skin js 39.40 kB 56.87 kB
/background/skin.css css 90 B
/background/skin js 277 B
/live-video/minimal-skin.css css 6.06 kB
/live-video/skin.css css 6.05 kB
/live-video/minimal-skin js 42.67 kB 56.87 kB
/live-video/skin js 42.69 kB 56.87 kB
/live-audio/minimal-skin.css css 4.24 kB
/live-audio/skin.css css 4.21 kB
/live-audio/minimal-skin js 30.35 kB 56.87 kB
/live-audio/skin js 30.36 kB 56.87 kB
UI Components (39)
Entry Initial Lazy
/ui/airplay-button 10.45 kB 56.87 kB
/ui/alert-dialog 4.73 kB 56.87 kB
/ui/audio-track 7.92 kB 56.87 kB
/ui/audio-track-radio-group 9.48 kB 56.87 kB
/ui/buffering-indicator 7.79 kB 56.87 kB
/ui/captions-button 10.43 kB 56.87 kB
/ui/captions-radio-group 9.59 kB 56.87 kB
/ui/cast-button 10.43 kB 56.87 kB
/ui/controls 7.71 kB 56.87 kB
/ui/dialog 4.75 kB 56.87 kB
/ui/error-dialog 11.80 kB 56.87 kB
/ui/fullscreen-button 10.39 kB 56.87 kB
/ui/gesture 7.98 kB 56.87 kB
/ui/hotkey 8.38 kB 56.87 kB
/ui/live-button 8.80 kB 56.87 kB
/ui/menu 19.45 kB 56.87 kB
/ui/mute-button 10.44 kB 56.87 kB
/ui/pip-button 10.39 kB 56.87 kB
/ui/play-button 10.37 kB 56.87 kB
/ui/playback-rate 7.99 kB 56.87 kB
/ui/playback-rate-button 10.37 kB 56.87 kB
/ui/playback-rate-radio-group 9.53 kB 56.87 kB
/ui/popover 8.16 kB 56.87 kB
/ui/poster 7.88 kB 56.87 kB
/ui/quality 8.42 kB 56.87 kB
/ui/quality-radio-group 9.96 kB 56.87 kB
/ui/seek-button 10.41 kB 56.87 kB
/ui/seek-indicator 11.98 kB 56.87 kB
/ui/slider 12.74 kB 56.87 kB
/ui/status-announcer 10.10 kB 56.87 kB
/ui/status-indicator 12.29 kB 56.87 kB
/ui/thumbnail 8.73 kB 56.87 kB
/ui/time 9.77 kB 56.87 kB
/ui/time-slider 14.17 kB 56.87 kB
/ui/title 7.28 kB 56.87 kB
/ui/tooltip 8.93 kB 56.87 kB
/ui/volume-indicator 12.34 kB 56.87 kB
/ui/volume-popover 14.20 kB 56.87 kB
/ui/volume-slider 12.13 kB 56.87 kB
🧩 @videojs/core — 1 small size change
Path Base initial PR initial Initial diff Lazy diff
/vjsc 1.82 kB 1.84 kB +25 B (+1.3%)
Entries (76)
Entry Initial Lazy
. 13.15 kB
/dom 22.53 kB 56.87 kB
/vjsc 1.84 kB
/i18n 3.23 kB 56.87 kB
/i18n/locales/all 34.97 kB
/i18n/locales/ar 1.22 kB
/i18n/locales/az 1.09 kB
/i18n/locales/bg 1.29 kB
/i18n/locales/bn 1.23 kB
/i18n/locales/bs 1.00 kB
/i18n/locales/ca 1.07 kB
/i18n/locales/cs 1.07 kB
/i18n/locales/cy 1.02 kB
/i18n/locales/da 1006 B
/i18n/locales/de 1.09 kB
/i18n/locales/el 1.49 kB
/i18n/locales/en 785 B
/i18n/locales/es 1018 B
/i18n/locales/et 1.05 kB
/i18n/locales/eu 1.03 kB
/i18n/locales/fa 1.22 kB
/i18n/locales/fi 1.04 kB
/i18n/locales/fr 1.07 kB
/i18n/locales/gd 1.10 kB
/i18n/locales/gl 1022 B
/i18n/locales/he 1.14 kB
/i18n/locales/hi 1.30 kB
/i18n/locales/hr 1.02 kB
/i18n/locales/hu 1.10 kB
/i18n/locales/id 919 B
/i18n/locales/it 1.01 kB
/i18n/locales/ja 1.18 kB
/i18n/locales/ko 1.14 kB
/i18n/locales/lt 1.02 kB
/i18n/locales/lv 1.09 kB
/i18n/locales/mr 1.30 kB
/i18n/locales/nb 987 B
/i18n/locales/ne 1.29 kB
/i18n/locales/nl 1.00 kB
/i18n/locales/nn 987 B
/i18n/locales/oc 1.08 kB
/i18n/locales/pl 1.14 kB
/i18n/locales/pt 1.03 kB
/i18n/locales/pt-BR 1.03 kB
/i18n/locales/pt-PT 1.01 kB
/i18n/locales/ro 1.07 kB
/i18n/locales/ru 1.40 kB
/i18n/locales/sk 1.10 kB
/i18n/locales/sl 1.04 kB
/i18n/locales/sr 1.07 kB
/i18n/locales/sv 1.01 kB
/i18n/locales/te 1.32 kB
/i18n/locales/th 1.29 kB
/i18n/locales/tr 1.08 kB
/i18n/locales/uk 1.41 kB
/i18n/locales/vi 1.11 kB
/i18n/locales/zh 1.02 kB
/i18n/locales/zh-CN 1.02 kB
/i18n/locales/zh-TW 1.02 kB
/i18n/text/airplay 101 B
/i18n/text/buttons 137 B
/i18n/text/captions 93 B
/i18n/text/cast 114 B
/i18n/text/common 90 B
/i18n/text/container 75 B
/i18n/text/errors 285 B
/i18n/text/fullscreen 98 B
/i18n/text/live 126 B
/i18n/text/menu 249 B
/i18n/text/pip 101 B
/i18n/text/playback 80 B
/i18n/text/seek 105 B
/i18n/text/slider 65 B
/i18n/text/status 222 B
/i18n/text/time 280 B
/i18n/text/volume 133 B
🏷️ @videojs/element — no changes
Entries (2)
Entry Initial
. 1.05 kB
/context 943 B
📦 @videojs/store — no changes
Entries (3)
Entry Initial
. 1.72 kB
/html 703 B
/react 365 B
🔧 @videojs/utils — no changes
Entries (13)
Entry Initial
/array 271 B
/dom 5.02 kB
/events 320 B
/function 386 B
/i18n 249 B
/jwt 176 B
/object 535 B
/predicate 297 B
/percent 281 B
/string 239 B
/style 212 B
/time 976 B
/number 199 B
📦 @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 changes
Entries (3)
Entry Initial
. 1.14 kB
/dom 4.12 kB
/media-tracks 2.05 kB
📦 @videojs/mux — no changes
📦 @videojs/mux-audio — no changes
Entries (2)
Entry Initial
. 135.58 kB
/spf 20.84 kB
📦 @videojs/mux-data — no changes
📦 @videojs/mux-video — no changes
Entries (2)
Entry Initial
. 135.56 kB
/spf 26.75 kB
📦 @videojs/native-hls-video — no changes
📦 @videojs/shaka-video — no changes
📦 @videojs/spf — no changes
Entries (7)
Entry Initial
. 4.55 kB
/dom 6.60 kB
/hls 22.06 kB
/media-tracks 504 B
/hls-background-video 15.84 kB
/hls-audio 19.84 kB
/hls-video 25.89 kB
📦 @videojs/spotify-audio — no changes
📦 @videojs/tiktok-video — no changes
📦 @videojs/twitch-video — no changes
📦 @videojs/vimeo-video — no changes
📦 @videojs/wistia-video — no changes
Entries (2)
Entry Initial
. 1.91 kB
/helpers 1.77 kB
📦 @videojs/youtube-video — no changes

ℹ️ How to interpret

Each 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 pnpm size locally to check current initial sizes.

Comment thread packages/react/src/presets/video/skin.tsx Outdated
Comment thread packages/react/src/ui/poster/poster-image.tsx Outdated
Comment thread site/src/content/docs/reference/poster.mdx Outdated
Comment thread packages/react/src/ui/poster/poster-image.tsx Outdated
Comment thread packages/react/src/ui/poster/poster-root.tsx Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/react/src/ui/poster/poster-image.tsx Outdated
@netlify

netlify Bot commented Sep 3, 2026

Copy link
Copy Markdown

Deploy Preview for vjs-registry ready!

Name Link
🔨 Latest commit 931d1b5
🔍 Latest deploy log https://app.netlify.com/projects/vjs-registry/deploys/6aa0ec2d469e4c0008e83706
😎 Deploy Preview https://deploy-preview-2563--vjs-registry.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

mihar-22 and others added 5 commits September 3, 2026 16:49
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>
@mihar-22
mihar-22 merged commit def9bf2 into main Sep 9, 2026
18 of 21 checks passed
@mihar-22
mihar-22 deleted the t3code/align-poster-html-api branch September 9, 2026 05:19
@videojs-release videojs-release Bot mentioned this pull request Sep 9, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ 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' });
}
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 931d1b5. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants