Skip to content

fix(packages): keep controls visible during active interactions - #1900

Open
mihar-22 wants to merge 2 commits into
mainfrom
t3code/fix-controls-autohide-open-menu
Open

fix(packages): keep controls visible during active interactions#1900
mihar-22 wants to merge 2 commits into
mainfrom
t3code/fix-controls-autohide-open-menu

Conversation

@mihar-22

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

Copy link
Copy Markdown
Member

Closes #1890

Summary

Prevent player controls from auto-hiding while a menu is open or a slider drag is active. The behavior is shared by the HTML and React renderers, and the normal idle delay resumes after the final interaction ends.

vjs-menu-no-close.mp4

Changes

  • Add reference-counted controls visibility locks to suspend auto-hide during sustained interactions
  • Hold locks for root menus and generic, time, and volume slider pointer drags
  • Release locks on interaction completion or component teardown, then restart a full idle window
  • Add core, renderer, and Chromium regression coverage for menus and stationary time-slider drags

Testing

  • Core focused tests: 42 passed
  • HTML focused tests: 64 passed
  • React focused tests: 59 passed
  • Chromium HTML and React interaction tests: 4 passed
  • Media, core, HTML, and React package builds passed
  • pnpm check:workspace passed

Known unrelated failure: pnpm typecheck reports packages/html/src/icons/tests/index.test.ts:18 because playIcon is not exported.


Note

Medium Risk
Changes shared auto-hide logic in controlsFeature and every menu/slider path; behavior is well covered by unit and e2e tests but regressions could affect all players using these controls.

Overview
Fixes controls auto-hiding mid-interaction by adding requestControlsLock() on player controls state: a reference-counted hold that forces controlsVisible, pauses the idle timer, and after the last release restarts a full inactivity window instead of hiding immediately.

controlsFeature treats active locks like paused/cast when computing visibility; detach clears outstanding locks. Menus and sliders call the API for the whole interaction (open/close or drag start/end), including teardown on disconnect.

HTML root MenuElement and generic, time, and volume slider elements acquire via PlayerController. React mirrors this in MenuRoot and useSlider. Design docs now describe locks; they are no longer listed as alpha descope.

E2E asserts the bar stays visible with settings open and during a stationary time-slider drag past the normal hide delay.

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

@netlify

netlify Bot commented Jul 31, 2026

Copy link
Copy Markdown

Deploy Preview for vjs10-site ready!

Name Link
🔨 Latest commit 90390c9
🔍 Latest deploy log https://app.netlify.com/projects/vjs10-site/deploys/6a6d1c3c16bee7000895d2e4
😎 Deploy Preview https://deploy-preview-1900--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.

@vercel

vercel Bot commented Jul 31, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
v10-sandbox Ready Ready Preview Jul 31, 2026 10:06pm

Request Review

@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle Size Report

🎨 @videojs/html

Path Base initial PR initial Diff % Lazy
/ui/slider 5.77 kB 11.18 kB +5.41 kB +93.9% 0 B 🔴
Presets (7)
Entry Initial Lazy
/video (default) 53.90 kB 51.74 kB
/video (default + hls) 189.80 kB 51.74 kB
/video (minimal) 53.79 kB 51.74 kB
/video (minimal + hls) 189.70 kB 51.74 kB
/audio (default) 46.15 kB 51.74 kB
/audio (minimal) 42.57 kB 51.74 kB
/background 5.00 kB 51.74 kB
Media (12)
Entry Initial Lazy
/media/background-video 1.14 kB
/media/container 2.58 kB 51.74 kB
/media/dash-video 211.14 kB
/media/google-cast 6.46 kB
/media/hlsjs-video 137.92 kB
/media/mux-audio 138.88 kB
/media/mux-data 153.85 kB
/media/mux-video 139.06 kB
/media/native-hls-video 5.34 kB
/media/simple-hls-audio-only 19.92 kB
/media/simple-hls-video 24.27 kB
/media/vimeo-video 12.31 kB
Players (5)
Entry Initial Lazy
/video/player 9.07 kB 51.74 kB
/audio/player 6.20 kB 51.74 kB
/background/player 4.73 kB 51.74 kB
/live-video/player 8.47 kB 51.74 kB
/live-audio/player 6.22 kB 51.74 kB
Skins (30)
Entry Type Initial Lazy
/video/minimal-skin.css css 5.93 kB
/video/skin.css css 5.93 kB
/video/minimal-skin js 53.74 kB 51.74 kB
/video/minimal-skin.tailwind js 54.31 kB 51.74 kB
/video/skin js 53.82 kB 51.74 kB
/video/skin.tailwind js 54.56 kB 51.74 kB
/audio/minimal-skin.css css 4.12 kB
/audio/skin.css css 4.06 kB
/audio/minimal-skin js 42.53 kB 51.74 kB
/audio/minimal-skin.tailwind js 43.09 kB 51.74 kB
/audio/skin js 46.11 kB 51.74 kB
/audio/skin.tailwind js 46.59 kB 51.74 kB
/background/skin.css css 133 B
/background/skin js 1.14 kB
/live-video/minimal-skin.css css 5.93 kB
/live-video/skin.css css 5.93 kB
/live-video/minimal-skin js 50.14 kB 51.74 kB
/live-video/minimal-skin.tailwind js 50.63 kB 51.74 kB
/live-video/skin js 52.34 kB 51.74 kB
/live-video/skin.tailwind js 52.96 kB 51.74 kB
/live-audio/minimal-skin.css css 4.12 kB
/live-audio/skin.css css 4.06 kB
/live-audio/minimal-skin js 34.72 kB 51.74 kB
/live-audio/minimal-skin.tailwind js 34.13 kB 51.74 kB
/live-audio/skin js 38.34 kB 51.74 kB
/live-audio/skin.tailwind js 37.92 kB 51.74 kB
/global.css css 183 B
/shared.css css 104 B
/tailwind.css css 161 B
/skin-element js 1.46 kB
UI Components (39)
Entry Initial
/ui/airplay-button 2.65 kB
/ui/alert-dialog 3.01 kB
/ui/alert-dialog-close 2.56 kB
/ui/alert-dialog-description 2.49 kB
/ui/alert-dialog-title 2.54 kB
/ui/audio-track-radio-group 3.08 kB
/ui/buffering-indicator 2.84 kB
/ui/captions-button 2.68 kB
/ui/captions-radio-group 3.17 kB
/ui/cast-button 2.58 kB
/ui/compounds 3.36 kB
/ui/controls 3.05 kB
/ui/error-dialog 3.07 kB
/ui/fullscreen-button 2.60 kB
/ui/hotkey 2.70 kB
/ui/menu 3.09 kB
/ui/mute-button 2.68 kB
/ui/pip-button 2.67 kB
/ui/play-button 2.67 kB
/ui/playback-rate-button 2.72 kB
/ui/playback-rate-radio-group 2.94 kB
/ui/popover 3.25 kB
/ui/poster 2.54 kB
/ui/quality-radio-group 3.17 kB
/ui/seek-button 2.63 kB
/ui/seek-indicator 2.89 kB
/ui/seek-indicator-value 475 B
/ui/slider 3.08 kB
/ui/status-announcer 2.58 kB
/ui/status-indicator 2.66 kB
/ui/status-indicator-value 517 B
/ui/thumbnail 2.66 kB
/ui/time 3.05 kB
/ui/time-slider 3.08 kB
/ui/tooltip 3.04 kB
/ui/volume-indicator 2.57 kB
/ui/volume-indicator-fill 467 B
/ui/volume-indicator-value 530 B
/ui/volume-slider 3.08 kB

Sizes are marginal over the root entry point.

⚛️ @videojs/react — no changes
Presets (7)
Entry Initial Lazy
/video (default) 42.51 kB 51.74 kB
/video (default + hls) 177.14 kB 51.74 kB
/video (minimal) 42.86 kB 51.74 kB
/video (minimal + hls) 177.65 kB 51.74 kB
/audio (default) 34.79 kB 51.74 kB
/audio (minimal) 34.86 kB 51.74 kB
/background 581 B
Media (11)
Entry Initial
/media/background-video 394 B
/media/dash-video 209.54 kB
/media/google-cast 5.35 kB
/media/hlsjs-video 136.40 kB
/media/mux-audio 137.23 kB
/media/mux-data 152.83 kB
/media/mux-video 137.38 kB
/media/native-hls-video 3.54 kB
/media/simple-hls-audio-only 18.18 kB
/media/simple-hls-video 22.64 kB
/media/vimeo-video 10.46 kB
Skins (27)
Entry Type Initial Lazy
/tailwind.css css 161 B
/video/minimal-skin.css css 5.82 kB
/video/skin.css css 5.82 kB
/video/minimal-skin js 42.73 kB 51.74 kB
/video/minimal-skin.tailwind js 48.97 kB 51.74 kB
/video/skin js 42.41 kB 51.74 kB
/video/skin.tailwind js 48.75 kB 51.74 kB
/audio/minimal-skin.css css 3.98 kB
/audio/skin.css css 3.92 kB
/audio/minimal-skin js 34.75 kB 51.74 kB
/audio/minimal-skin.tailwind js 37.28 kB 51.74 kB
/audio/skin js 34.68 kB 51.74 kB
/audio/skin.tailwind js 39.07 kB 51.74 kB
/background/skin.css css 90 B
/background/skin js 272 B
/live-video/minimal-skin.css css 5.82 kB
/live-video/skin.css css 5.82 kB
/live-video/minimal-skin js 37.76 kB 51.74 kB
/live-video/minimal-skin.tailwind js 44.02 kB 51.74 kB
/live-video/skin js 37.72 kB 51.74 kB
/live-video/skin.tailwind js 44.06 kB 51.74 kB
/live-audio/minimal-skin.css css 3.98 kB
/live-audio/skin.css css 3.92 kB
/live-audio/minimal-skin js 25.20 kB 51.74 kB
/live-audio/minimal-skin.tailwind js 28.48 kB 51.74 kB
/live-audio/skin js 25.27 kB 51.74 kB
/live-audio/skin.tailwind js 28.66 kB 51.74 kB
UI Components (33)
Entry Initial
/ui/airplay-button 2.61 kB
/ui/alert-dialog 2.81 kB
/ui/audio-track 2.53 kB
/ui/buffering-indicator 2.77 kB
/ui/captions-button 2.65 kB
/ui/captions-radio-group 2.49 kB
/ui/cast-button 2.67 kB
/ui/controls 2.77 kB
/ui/error-dialog 2.65 kB
/ui/fullscreen-button 2.67 kB
/ui/gesture 2.61 kB
/ui/hotkey 2.62 kB
/ui/live-button 2.50 kB
/ui/menu 2.85 kB
/ui/mute-button 2.70 kB
/ui/pip-button 2.61 kB
/ui/play-button 2.62 kB
/ui/playback-rate 2.63 kB
/ui/playback-rate-button 2.67 kB
/ui/popover 3.34 kB
/ui/poster 2.72 kB
/ui/quality 2.48 kB
/ui/seek-button 2.54 kB
/ui/seek-indicator 2.76 kB
/ui/slider 2.64 kB
/ui/status-announcer 2.58 kB
/ui/status-indicator 2.48 kB
/ui/thumbnail 2.69 kB
/ui/time 2.45 kB
/ui/time-slider 2.71 kB
/ui/tooltip 3.25 kB
/ui/volume-indicator 2.54 kB
/ui/volume-slider 2.64 kB

Sizes are marginal over the root entry point.

🧩 @videojs/core — no changes
Entries (73)
Entry Initial Lazy
. 11.38 kB
/dom 18.39 kB
/i18n 3.03 kB 51.74 kB
/i18n/locales/all 31.40 kB
/i18n/locales/ar 1.14 kB
/i18n/locales/az 1.04 kB
/i18n/locales/bg 1.21 kB
/i18n/locales/bn 1.23 kB
/i18n/locales/bs 951 B
/i18n/locales/ca 1.00 kB
/i18n/locales/cs 1022 B
/i18n/locales/cy 977 B
/i18n/locales/da 943 B
/i18n/locales/de 1.02 kB
/i18n/locales/el 1.40 kB
/i18n/locales/en 732 B
/i18n/locales/es 959 B
/i18n/locales/et 1019 B
/i18n/locales/eu 983 B
/i18n/locales/fa 1.14 kB
/i18n/locales/fi 997 B
/i18n/locales/fr 1.01 kB
/i18n/locales/gd 1.03 kB
/i18n/locales/gl 960 B
/i18n/locales/he 1.06 kB
/i18n/locales/hi 1.24 kB
/i18n/locales/hr 986 B
/i18n/locales/hu 1.02 kB
/i18n/locales/it 997 B
/i18n/locales/ja 1.13 kB
/i18n/locales/ko 1.08 kB
/i18n/locales/lv 1.02 kB
/i18n/locales/mr 1.24 kB
/i18n/locales/nb 928 B
/i18n/locales/ne 1.23 kB
/i18n/locales/nl 967 B
/i18n/locales/nn 931 B
/i18n/locales/oc 1.01 kB
/i18n/locales/pl 1.06 kB
/i18n/locales/pt 988 B
/i18n/locales/pt-BR 988 B
/i18n/locales/pt-PT 953 B
/i18n/locales/ro 1.01 kB
/i18n/locales/ru 1.29 kB
/i18n/locales/sk 1.03 kB
/i18n/locales/sl 987 B
/i18n/locales/sr 1018 B
/i18n/locales/sv 962 B
/i18n/locales/te 1.26 kB
/i18n/locales/th 1.22 kB
/i18n/locales/tr 1.01 kB
/i18n/locales/uk 1.32 kB
/i18n/locales/vi 1.03 kB
/i18n/locales/zh 977 B
/i18n/locales/zh-CN 977 B
/i18n/locales/zh-TW 972 B
/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 277 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 214 B
/i18n/text/volume 133 B
🏷️ @videojs/element — no changes
Entries (2)
Entry Initial
. 996 B
/context 943 B
📦 @videojs/store — no changes
Entries (3)
Entry Initial
. 1.39 kB
/html 696 B
/react 361 B
🔧 @videojs/utils — no changes
Entries (12)
Entry Initial
/array 104 B
/dom 3.16 kB
/events 319 B
/function 327 B
/jwt 176 B
/object 505 B
/predicate 265 B
/percent 281 B
/string 239 B
/style 190 B
/time 813 B
/number 158 B
📦 @videojs/media — no changes
Entries (14)
Entry Initial
. 959 B
/dom/audio-host 1.11 kB
/dom/custom-media-element 2.09 kB
/dom/dash 208.99 kB
/dom/google-cast 4.06 kB
/dom/hls-js 136.00 kB
/dom/media-host 1.21 kB
/dom/media-played-ranges 576 B
/dom/mux 159.41 kB
/dom/native-hls 3.02 kB
/dom/simple-hls 22.09 kB
/dom/simple-hls-audio-only 17.73 kB
/dom/video-host 1.40 kB
/dom/vimeo 9.88 kB
📦 @videojs/spf — no changes
Entries (5)
Entry Initial
. 4.46 kB
/dom 6.50 kB
/hls 18.74 kB
/media-tracks 504 B
/background-video 13.42 kB

ℹ️ How to interpret

JS sizes are initial static graph totals (minified + brotli). Lazy dynamic chunks are shown separately when present.

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

Run pnpm size locally to check current initial sizes.

@mihar-22
mihar-22 marked this pull request as ready for review July 31, 2026 22:17

@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.

Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 90390c9. Configure here.

this.#releaseControlsLock ??= this.#controlsState.value?.requestControlsLock() ?? null;
} else {
this.#releaseControlsVisibilityLock();
}

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 controls locks after attach

Medium Severity

HTML menu and slider elements cache the first requestControlsLock() result with ??=. Before store.attach(), and again after detach/reattach, that API is the fallback no-op release. Once stored, later updates never call the real lock even though requestControlsLock was replaced, so controls can still auto-hide while a root menu stays open or a drag is active. React avoids this by depending on the function identity and re-running the effect.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 90390c9. 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.

Don't auto-hide controls while a menu is open

1 participant