fix(player): Space bar play/pause when focus is on sidebar or rail buttons (#593)

* fix(player): Space bar play/pause when focus is on sidebar or rail buttons

When any <button> in the player rail (viz, audio, mixer, etc.), a sidebar
nav link, or a popover control has keyboard focus, pressing Space was
blocked by _shortcutDispatchBlocked → _isInsideInteractiveControl, which
returns true for BUTTON elements. The Space shortcut never reached the
shortcut dispatcher and togglePlay() was never called.

The fix extends the same carve-out pattern already used for the section
practice bar: when the player screen is active, Space is always dispatched
through the shortcut system. The shortcut handler's preventDefault() stops
the focused element from also activating, so this is not a double-trigger.

* test(player): cover Space play/pause carve-out + add CHANGELOG entry

Adds two Playwright regression tests for #593 in
tests/browser/keyboard-shortcuts.spec.ts:
- Space toggles play/pause when a player rail <button> has focus, and
  the focused button does NOT also activate (dispatcher preventDefault).
  Fails on base (Space blocked, played=0), passes with the carve-out.
- Space in a player-screen text input still types a space and never
  reaches play/pause (locks the _isTextInput exemption ordering).

Also records the fix under CHANGELOG [Unreleased] -> Fixed, per the
project workflow that every PR updates the changelog.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(player): don't override Space inside modal dialogs over the player

The player-screen Space carve-out keyed off the active *screen*, so it
also hijacked Space inside a true modal dialog layered over the player
(e.g. the keyboard-shortcuts help modal, edit modal): Space toggled
playback behind the modal and preventDefault blocked the modal's focused
control (Close) from activating — contradicting aria-modal semantics.

Narrow the carve-out to skip focus inside a modal
(role="dialog" aria-modal="true" or .feedBack-modal). Non-modal player
popovers/toasts (loop A/B, arrangement pin, role=dialog aria-modal=false)
are not dialogs and stay covered, so the original fix is unchanged for
the cases it targeted. Adds a Playwright regression test (Space inside a
modal reaches the modal's button, not play/pause) and updates the
CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: byrongamatos <xasiklas@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
OmikronApex
2026-06-24 21:12:55 +02:00
committed by GitHub
co-authored by Claude Opus 4.8 byrongamatos
parent d2569cc2a8
commit 64801d5735
3 changed files with 147 additions and 0 deletions
+15
View File
@@ -445,6 +445,21 @@ function _shortcutDispatchBlocked(e) {
// the popover's own keydown listener) — suppress the player-scope
// "back to library" Esc so the user doesn't get bounced out of the player.
if (e.key === 'Escape' && _sectionPracticePopoverOpen()) return true;
// Space on the player screen should always play/pause, even if focus is on a
// sidebar nav link, player rail button, popover control, or any other
// interactive element — the shortcut dispatcher calls preventDefault so the
// focused element won't also activate. Two exceptions keep native Space:
// text inputs (already exempted above), and focus inside a true modal
// dialog (role="dialog" aria-modal="true", or a .feedBack-modal overlay)
// layered over the player — a modal traps interaction, so Space must reach
// its focused control (e.g. the Close button) rather than toggle playback
// behind it. Non-modal player popovers/toasts (loop A/B, arrangement pin,
// role="dialog" aria-modal="false") are not modals and stay covered.
if (_isSpaceKey(e) && _getCurrentContext().isPlayer &&
!(e.target && e.target.closest &&
e.target.closest('[role="dialog"][aria-modal="true"], .feedBack-modal'))) {
return false;
}
return _isInsideInteractiveControl(e.target);
}