Skip to content

feat(packages): add playback rate menu#1527

Open
sampotts wants to merge 2 commits intomainfrom
feat/playback-rate-menu
Open

feat(packages): add playback rate menu#1527
sampotts wants to merge 2 commits intomainfrom
feat/playback-rate-menu

Conversation

@sampotts
Copy link
Copy Markdown
Collaborator

@sampotts sampotts commented May 8, 2026

Summary

  • Add shared playback-rate menu logic and data attributes for dynamic rate labels/options.
  • Add React and HTML playback-rate menu components, including trigger/content composition and presets for the existing skins.
  • Replace playback-rate buttons in the shipped skins and add default/minimal CSS and Tailwind menu styling.
  • Improve shared menu/popover behavior for keyboard handling, container boundaries, controls visibility, scrolling, and grouped top-level popups.

Validation

  • pnpm typecheck
  • pnpm -F @videojs/core test src/core/ui/playback-rate-menu/tests/playback-rate-menu-core.test.ts src/dom/ui/menu/tests/create-menu.test.ts src/dom/ui/popover/tests/popover.test.ts
  • pnpm -F @videojs/html test src/ui/playback-rate-menu/tests/playback-rate-menu-element.test.ts src/ui/menu/tests/menu-element.test.ts
  • pnpm -F @videojs/react test src/ui/playback-rate-menu/tests/playback-rate-menu.test.tsx src/ui/menu/tests/menu.test.tsx src/player/tests/context.test.tsx

Closes #623.


Note

Medium Risk
Medium risk because it changes core Popover/Menu interaction semantics (grouped auto-close, focus restoration, keyboard handling) and introduces new positioning boundary logic that could affect all popups.

Overview
Adds a new playback-rate menu primitive (PlaybackRateMenuCore + data attrs) and wires it into new HTML custom elements (media-playback-rate-menu*) and React exports/hooks, including dynamic option rendering and selection.

Updates shipped skins to replace the playback-rate button+tooltip with the new menu (radio options) and sets popup/tooltip boundary="viewport" in several presets.

Improves shared popup infrastructure: introduces PopupGroup to ensure only one top-level popup is open at a time, adds controls-hidden close handling, refines menu highlighting (prefer checked/selected item; allow highlight without focusing on pointer hover), swallows trigger navigation keys while open, and enhances positioning with configurable boundary + boundaryOffset CSS vars and shared DOMRect/boundary utilities.

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

@vercel
Copy link
Copy Markdown

vercel Bot commented May 8, 2026

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

Project Deployment Actions Updated (UTC)
v10-sandbox Ready Ready Preview, Comment May 8, 2026 1:24am

Request Review

@netlify
Copy link
Copy Markdown

netlify Bot commented May 8, 2026

Deploy Preview for vjs10-site ready!

Name Link
🔨 Latest commit 2339556
🔍 Latest deploy log https://app.netlify.com/projects/vjs10-site/deploys/69fd3b1ede0d9b000827112f
😎 Deploy Preview https://deploy-preview-1527--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.

@sampotts sampotts changed the title Add playback rate menu feat(packages): add playback rate menu May 8, 2026
@sampotts sampotts marked this pull request as ready for review May 8, 2026 01:09
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 8, 2026

📦 Bundle Size Report

🎨 @videojs/html

Path Base PR Diff %
/video/minimal-skin 32.45 kB 38.04 kB +5.59 kB +17.2% 🔴
/video/minimal-skin.tailwind 32.82 kB 38.43 kB +5.61 kB +17.1% 🔴
/video/skin 32.82 kB 38.58 kB +5.76 kB +17.6% 🔴
/video/skin.tailwind 33.22 kB 39.06 kB +5.84 kB +17.6% 🔴
/audio/minimal-skin 24.48 kB 30.05 kB +5.58 kB +22.8% 🔴
/audio/minimal-skin.tailwind 24.62 kB 30.22 kB +5.60 kB +22.7% 🔴
/audio/skin 27.33 kB 33.12 kB +5.79 kB +21.2% 🔴
/audio/skin.tailwind 27.52 kB 33.32 kB +5.79 kB +21.1% 🔴
/live-video/minimal-skin 32.17 kB 33.14 kB +992 B +3.0% 🔺
/live-video/minimal-skin.tailwind 32.47 kB 33.17 kB +723 B +2.2% 🔺
/live-video/skin 32.24 kB 33.20 kB +983 B +3.0% 🔺
/live-video/skin.tailwind 32.49 kB 33.23 kB +757 B +2.3% 🔺
/live-audio/minimal-skin 24.35 kB 25.21 kB +878 B +3.5% 🔺
/live-audio/minimal-skin.tailwind 24.28 kB 24.98 kB +717 B +2.9% 🔺
/live-audio/skin 26.85 kB 27.88 kB +1.03 kB +3.8% 🔺
/live-audio/skin.tailwind 26.86 kB 27.64 kB +798 B +2.9% 🔺
/ui/compounds 5.19 kB 5.48 kB +292 B +5.5% 🔺
/ui/menu 1.99 kB 2.73 kB +758 B +37.3% 🔴
/ui/playback-rate-menu 4.01 kB 🆕
/ui/popover 1.51 kB 1.80 kB +295 B +19.1% 🔴
/ui/tooltip 1.63 kB 1.75 kB +129 B +7.7% 🔺
/video (default) 32.84 kB 38.59 kB +5.74 kB +17.5% 🔴
/video (default + hls) 166.51 kB 172.06 kB +5.55 kB +3.3% 🔺
/video (minimal) 32.45 kB 38.09 kB +5.65 kB +17.4% 🔴
/video (minimal + hls) 166.01 kB 171.69 kB +5.67 kB +3.4% 🔺
/audio (default) 27.36 kB 33.11 kB +5.76 kB +21.0% 🔴
/audio (minimal) 24.45 kB 30.12 kB +5.68 kB +23.2% 🔴
Presets (7)
Entry Size
/video (default) 38.59 kB
/video (default + hls) 172.06 kB
/video (minimal) 38.09 kB
/video (minimal + hls) 171.69 kB
/audio (default) 33.11 kB
/audio (minimal) 30.12 kB
/background 4.22 kB
Media (8)
Entry Size
/media/background-video 1.04 kB
/media/container 1.72 kB
/media/dash-video 236.58 kB
/media/hls-video 134.87 kB
/media/mux-audio 160.89 kB
/media/mux-video 161.01 kB
/media/native-hls-video 4.62 kB
/media/simple-hls-video 15.99 kB
Players (5)
Entry Size
/video/player 7.04 kB
/audio/player 5.18 kB
/background/player 3.92 kB
/live-video/player 7.07 kB
/live-audio/player 5.19 kB
Skins (30)
Entry Type Size
/video/minimal-skin.css css 4.67 kB
/video/skin.css css 4.73 kB
/video/minimal-skin js 38.04 kB
/video/minimal-skin.tailwind js 38.43 kB
/video/skin js 38.58 kB
/video/skin.tailwind js 39.06 kB
/audio/minimal-skin.css css 2.90 kB
/audio/skin.css css 2.93 kB
/audio/minimal-skin js 30.05 kB
/audio/minimal-skin.tailwind js 30.22 kB
/audio/skin js 33.12 kB
/audio/skin.tailwind js 33.32 kB
/background/skin.css css 115 B
/background/skin js 1.15 kB
/live-video/minimal-skin.css css 4.67 kB
/live-video/skin.css css 4.73 kB
/live-video/minimal-skin js 33.14 kB
/live-video/minimal-skin.tailwind js 33.17 kB
/live-video/skin js 33.20 kB
/live-video/skin.tailwind js 33.23 kB
/live-audio/minimal-skin.css css 2.90 kB
/live-audio/skin.css css 2.93 kB
/live-audio/minimal-skin js 25.21 kB
/live-audio/minimal-skin.tailwind js 24.98 kB
/live-audio/skin js 27.88 kB
/live-audio/skin.tailwind js 27.64 kB
/global.css css 176 B
/shared.css css 88 B
/tailwind.css css 228 B
/skin-element js 1.37 kB
UI Components (35)
Entry Size
/ui/alert-dialog 763 B
/ui/alert-dialog-close 307 B
/ui/alert-dialog-description 288 B
/ui/alert-dialog-title 292 B
/ui/buffering-indicator 2.06 kB
/ui/captions-button 2.05 kB
/ui/cast-button 2.11 kB
/ui/compounds 5.48 kB
/ui/controls 1.95 kB
/ui/error-dialog 2.50 kB
/ui/fullscreen-button 2.04 kB
/ui/hotkey 2.73 kB
/ui/menu 2.73 kB
/ui/mute-button 2.05 kB
/ui/pip-button 2.05 kB
/ui/play-button 2.01 kB
/ui/playback-rate-button 2.16 kB
/ui/playback-rate-menu 4.01 kB
/ui/popover 1.80 kB
/ui/poster 1.81 kB
/ui/seek-button 2.07 kB
/ui/seek-indicator 2.70 kB
/ui/seek-indicator-value 256 B
/ui/slider 1.16 kB
/ui/status-announcer 2.43 kB
/ui/status-indicator 2.48 kB
/ui/status-indicator-value 178 B
/ui/thumbnail 2.52 kB
/ui/time 1.92 kB
/ui/time-slider 3.01 kB
/ui/tooltip 1.75 kB
/ui/volume-indicator 2.70 kB
/ui/volume-indicator-fill 270 B
/ui/volume-indicator-value 268 B
/ui/volume-slider 3.48 kB

Sizes are marginal over the root entry point.

⚛️ @videojs/react

Path Base PR Diff %
/video/minimal-skin 26.31 kB 32.28 kB +5.97 kB +22.7% 🔴
/video/minimal-skin.tailwind 30.87 kB 37.09 kB +6.22 kB +20.2% 🔴
/video/skin 26.25 kB 32.27 kB +6.03 kB +23.0% 🔴
/video/skin.tailwind 30.82 kB 37.03 kB +6.22 kB +20.2% 🔴
/audio/minimal-skin 17.61 kB 26.55 kB +8.95 kB +50.8% 🔴
/audio/minimal-skin.tailwind 20.23 kB 26.28 kB +6.05 kB +29.9% 🔴
/audio/skin 19.11 kB 26.48 kB +7.37 kB +38.6% 🔴
/audio/skin.tailwind 20.24 kB 29.51 kB +9.27 kB +45.8% 🔴
/live-video/minimal-skin 22.92 kB 23.68 kB +782 B +3.3% 🔺
/live-video/minimal-skin.tailwind 27.39 kB 28.12 kB +745 B +2.7% 🔺
/live-video/skin 22.99 kB 23.77 kB +791 B +3.4% 🔺
/live-video/skin.tailwind 27.47 kB 28.14 kB +692 B +2.5% 🔺
/live-audio/minimal-skin 16.23 kB 19.95 kB +3.73 kB +23.0% 🔴
/live-audio/minimal-skin.tailwind 18.70 kB 22.42 kB +3.71 kB +19.9% 🔴
/live-audio/skin 17.85 kB 19.98 kB +2.13 kB +11.9% 🔴
/live-audio/skin.tailwind 18.81 kB 22.53 kB +3.71 kB +19.7% 🔴
/ui/menu 3.86 kB 4.39 kB +546 B +13.8% 🔴
/ui/playback-rate-menu 4.80 kB 🆕
/ui/popover 1.95 kB 2.31 kB +367 B +18.4% 🔴
/ui/status-announcer 1.74 kB 🆕
/ui/tooltip 2.33 kB 2.75 kB +432 B +18.1% 🔴
/video (default) 26.33 kB 32.31 kB +5.98 kB +22.7% 🔴
/video (default + hls) 158.76 kB 164.64 kB +5.88 kB +3.7% 🔺
/video (minimal) 26.34 kB 32.35 kB +6.01 kB +22.8% 🔴
/video (minimal + hls) 158.81 kB 164.56 kB +5.74 kB +3.6% 🔺
/audio (default) 19.20 kB 26.57 kB +7.37 kB +38.4% 🔴
/audio (minimal) 17.66 kB 26.65 kB +8.99 kB +50.9% 🔴
Presets (7)
Entry Size
/video (default) 32.31 kB
/video (default + hls) 164.64 kB
/video (minimal) 32.35 kB
/video (minimal + hls) 164.56 kB
/audio (default) 26.57 kB
/audio (minimal) 26.65 kB
/background 754 B
Media (7)
Entry Size
/media/background-video 575 B
/media/dash-video 235.21 kB
/media/hls-video 133.39 kB
/media/mux-audio 159.70 kB
/media/mux-video 159.68 kB
/media/native-hls-video 3.13 kB
/media/simple-hls-video 14.56 kB
Skins (27)
Entry Type Size
/tailwind.css css 228 B
/video/minimal-skin.css css 4.58 kB
/video/skin.css css 4.64 kB
/video/minimal-skin js 32.28 kB
/video/minimal-skin.tailwind js 37.09 kB
/video/skin js 32.27 kB
/video/skin.tailwind js 37.03 kB
/audio/minimal-skin.css css 2.77 kB
/audio/skin.css css 2.80 kB
/audio/minimal-skin js 26.55 kB
/audio/minimal-skin.tailwind js 26.28 kB
/audio/skin js 26.48 kB
/audio/skin.tailwind js 29.51 kB
/background/skin.css css 90 B
/background/skin js 272 B
/live-video/minimal-skin.css css 4.58 kB
/live-video/skin.css css 4.64 kB
/live-video/minimal-skin js 23.68 kB
/live-video/minimal-skin.tailwind js 28.12 kB
/live-video/skin js 23.77 kB
/live-video/skin.tailwind js 28.14 kB
/live-audio/minimal-skin.css css 2.77 kB
/live-audio/skin.css css 2.80 kB
/live-audio/minimal-skin js 19.95 kB
/live-audio/minimal-skin.tailwind js 22.42 kB
/live-audio/skin js 19.98 kB
/live-audio/skin.tailwind js 22.53 kB
UI Components (29)
Entry Size
/ui/alert-dialog 1.13 kB
/ui/buffering-indicator 1.89 kB
/ui/captions-button 2.13 kB
/ui/cast-button 2.08 kB
/ui/controls 1.91 kB
/ui/error-dialog 2.32 kB
/ui/fullscreen-button 2.13 kB
/ui/gesture 1.25 kB
/ui/hotkey 1.97 kB
/ui/live-button 2.11 kB
/ui/menu 4.39 kB
/ui/mute-button 2.09 kB
/ui/pip-button 2.09 kB
/ui/play-button 2.06 kB
/ui/playback-rate-button 2.10 kB
/ui/playback-rate-menu 4.80 kB
/ui/popover 2.31 kB
/ui/poster 1.74 kB
/ui/seek-button 2.09 kB
/ui/seek-indicator 1.90 kB
/ui/slider 3.28 kB
/ui/status-announcer 1.74 kB
/ui/status-indicator 1.97 kB
/ui/thumbnail 2.01 kB
/ui/time 2.53 kB
/ui/time-slider 2.98 kB
/ui/tooltip 2.75 kB
/ui/volume-indicator 2.02 kB
/ui/volume-slider 2.33 kB

Sizes are marginal over the root entry point.

🧩 @videojs/core

Path Base PR Diff %
/dom 14.54 kB 15.33 kB +817 B +5.5% 🔺
Entries (9)
Entry Size
. 7.41 kB
/dom 15.33 kB
/dom/media/custom-media-element 1.90 kB
/dom/media/dash 234.36 kB
/dom/media/google-cast 4.07 kB
/dom/media/hls 132.98 kB
/dom/media/mux 159.10 kB
/dom/media/native-hls 2.52 kB
/dom/media/simple-hls 13.93 kB
🏷️ @videojs/element — no changes
Entries (2)
Entry Size
. 996 B
/context 943 B
📦 @videojs/store — no changes
Entries (3)
Entry Size
. 1.39 kB
/html 696 B
/react 360 B
🔧 @videojs/utils — no changes
Entries (10)
Entry Size
/array 104 B
/dom 1.96 kB
/events 319 B
/function 327 B
/object 275 B
/predicate 265 B
/string 192 B
/style 190 B
/time 478 B
/number 158 B
📦 @videojs/spf — no changes
Entries (3)
Entry Size
. 4.29 kB
/dom 7.48 kB
/hls 13.32 kB

ℹ️ How to interpret

All sizes are standalone totals (minified + brotli).

Icon Meaning
No change
🔺 Increased ≤ 10%
🔴 Increased > 10%
🔽 Decreased
🆕 New (no baseline)

Run pnpm size locally to check current sizes.

Copy link
Copy Markdown

@cursor cursor Bot left a comment

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 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 e726e5b. Configure here.

Comment thread packages/core/src/dom/utils/event.ts Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Feature: Playback Rate Menu Component

1 participant