fix(player): one menu at a time, and inside the screen it opens on

Two defects in the video control bar, reported together because they present
together: the menus cover each other, and in portrait they cover the edge of
the screen instead of the video.

DR-256 (a) — audio track, quality and subtitles each owned a `show…` boolean
and no toggle cleared the others. Opening a second menu stacked it over the
first in the same corner: the newer panel hid rows of the older, both stayed
live, and both kept taking clicks. A single `openMenu` value replaces the three
booleans, which makes "at most one menu is open" a property of the state rather
than something every handler has to remember to enforce. The desktop volume
popup was a fourth uncoordinated menu in the same row, so `VolumeControl` grew
optional controlled-open props and joined the group; without them it still
manages itself, which is how MiniPlayer and AudioPlayer keep it.

DR-256 (b) — every panel was `absolute right-0` against *its own icon button*.
Those icons sit mid-row, so a 200-220 px panel extended left from a point well
inside the bar and hung off the left edge of a phone in portrait: half the
tracks could not be read, let alone tapped. One shared panel now anchors to the
control ROW's right edge, clamped to `min(20rem, 100vw - 2rem)` wide and
`min(300px, 45vh)` tall. A full-screen dismiss layer inside the controls subtree
closes it on a tap elsewhere — inside, so the tap never reaches the container's
gesture layer and cannot toggle playback (DR-098).

The volume popup had the same placement bug from the other side: `left-full`
opened it rightward from an icon near the right end of every bar it appears in.
It opens upward, right-aligned, now. And the icon row wraps rather than
overflowing — in portrait the transport controls plus nine icons are wider than
the screen, which pushed fullscreen and close past the edge.

The test renders the real component and drives the toggles, because neither
fault is visible from a helper: both are properties of the composition. It was
written first and failed on both counts — `["Audio Track", "Subtitles"]` open at
once, and no shared panel to anchor.
This commit is contained in:
2026-08-23 17:53:16 +02:00
parent e579d4bff2
commit 2ff07bfa49
5 changed files with 494 additions and 220 deletions
+10
View File
@@ -93,6 +93,16 @@ fact about the platform rather than asking the thing that would know.
for was not discarded if you skipped onward first — so the next item began
wherever you had dragged to in the previous one. (DR-253)
- **The player's menus no longer cover each other, or the edge of the screen.**
Audio track, quality and subtitles could all be open at once, stacked in the
same corner with the newest panel hiding rows of the one underneath, and each
one was anchored to its own icon — which sits mid-row, so on a phone in
portrait the panel hung off the left edge and half the tracks could not be
read or tapped. One menu is open at a time now (the desktop volume slider
included), it opens against the edge of the control bar clamped to the screen
it is on, and tapping anywhere else dismisses it. The row of icons wraps
instead of pushing fullscreen and close past the edge. (DR-256)
### 🧹 Under the hood
- The conformance suite can be run on its own: `bun run test:player` for the