fix(player): the Plex bars hold while hovered, volume is the native slider, tooltips no longer re-layer the row (E28)

- F22: the control bar faded out from under a mouse parked on it; the hide timer
  now waits while the pointer is on either band and re-arms on leave (test seen
  RED by disabling the hold).
- The hand-drawn volume bar broke DESIGN.md A6 (native sliders): it had no hover,
  focus, keyboard or screen-reader state. It is the Slider primitive now; a
  pointer drag blurs it so Space and the arrows keep driving the player.
- The control tooltips are composited from mount (will-change: opacity): a fade
  that promoted one on hover re-layered its neighbours under the player's
  scale(1.25), and the whole row shimmered.
This commit is contained in:
2026-09-25 14:48:45 +02:00
parent e9ccd9bc98
commit 62dbf35ea3
8 changed files with 106 additions and 51 deletions
+15 -3
View File
@@ -70,24 +70,36 @@ export function useFullscreen(wrapRef: RefObject<HTMLElement | null>): {
// 3s hide timer. Reads the caller's live refs so the timer callback sees fresh state without a
// closure rebuild: barAutoHide (per-user "never auto-hide" setting), whether a menu is open (keep the
// bar up so it can't vanish mid-choice), and the <video> paused state (a paused video keeps the bar).
// `holdOnHover` is the pointer resting on the chrome: the bar stays while it is there (as on YouTube
// and Plex Web — E28 F22, it used to fade out from under a mouse parked on Next), and leaving it
// starts a fresh 3 s.
export function useControlsVisibility(opts: {
barAutoHideRef: RefObject<boolean>;
menuOpenRef: RefObject<boolean>;
videoRef: RefObject<HTMLVideoElement | null>;
}): { uiVisible: boolean; wake: () => void } {
}): { uiVisible: boolean; wake: () => void; holdOnHover: (over: boolean) => void } {
const { barAutoHideRef, menuOpenRef, videoRef } = opts;
const [uiVisible, setUiVisible] = useState(true);
const hideTimer = useRef<number | null>(null);
const hovered = useRef(false);
const wake = useCallback(() => {
setUiVisible(true);
if (hideTimer.current) window.clearTimeout(hideTimer.current);
if (!barAutoHideRef.current) return; // per-user setting: never auto-hide the control bar
hideTimer.current = window.setTimeout(() => {
// Keep the controls up while the audio/subtitle menu is open, else it would vanish mid-choice.
if (!videoRef.current?.paused && !menuOpenRef.current) setUiVisible(false);
if (!videoRef.current?.paused && !menuOpenRef.current && !hovered.current)
setUiVisible(false);
}, 3000);
}, [barAutoHideRef, menuOpenRef, videoRef]);
return { uiVisible, wake };
const holdOnHover = useCallback(
(over: boolean) => {
hovered.current = over;
if (!over) wake();
},
[wake],
);
return { uiVisible, wake, holdOnHover };
}
// Periodic + on-unmount progress checkpoint. Reads live position/duration through refs (owned by the