Skip to content

fix: keep focus on play button on activation; scope keyboard shortcuts - #10

Closed
jeryj wants to merge 1 commit into
arraypress:mainfrom
jeryj:fix/stable-focus-shortcuts
Closed

fix: keep focus on play button on activation; scope keyboard shortcuts#10
jeryj wants to merge 1 commit into
arraypress:mainfrom
jeryj:fix/stable-focus-shortcuts

Conversation

@jeryj

@jeryj jeryj commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Problem

Activating the play button with Enter (or Space) moved focus to the player's wrapping <div> instead of leaving it on the button. This is an unexpected focus stealing event which seems unnecessary if we allow the shortcuts to work while the button is focused.

Changes

  • Don't steal focus from interactive controls. The container's click handler now only calls focus() when the click lands on a non-interactive area (button, a[href], input, [role="slider"]INTERACTIVE_ELEMENTS). Clicking/activating the play button or slider leaves focus on that control.
  • Scoped the keyboard-shortcut guard. The keydown guard now fires when the event target is the play button, waveform slider, or container (shortcutEnabledElements), instead of only when the bare container is focused. So Space toggles play from the waveform, while other controls (speed button, markers) keep their own keys — no Space-hijacking.
  • Container no longer becomes a tab stop. Removed the tabindex="0" promotion (it stays tabindex="-1" — focusable by script/click, never tabbable) since the keyboard shortcuts work while the play button and waveform are focused.

Behavior

Focus on Space Enter
Play button toggles (native activation; container's Space preventDefault avoids a double-fire) toggles (native button activation)
Waveform slider toggles play n/a (slider isn't a button)
Container (empty-area click) toggles play n/a
Speed/marker button activates that control (not hijacked) activates that control

Tests

test/player.test.js — added focus/shortcut coverage (focus stays on the play button on activation; Space starts/pauses with the play button focused; Space toggles from the slider; click-to-focus the container; mute via m). Full suite: 79 passing.

Follow-up (not in this PR)

The playback-speed control (showPlaybackSpeed) is a button that spawns a menu of buttons; it should become a proper dropdown.

🤖 Generated with Claude Code

Activating the play button (Space/Enter/click) no longer moves focus to the
player wrapper — the container's click handler only takes focus on
non-interactive clicks. The keyboard-shortcut guard now fires when focus is on
the play button, the waveform slider, or the container, so Space toggles play
from the waveform while other controls (speed button, markers) keep their own
keys. The container is no longer promoted into the tab order on click.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
arraypress added a commit that referenced this pull request Jul 1, 2026
Clicking/activating the play button — or any interactive control (slider,
link, input) — now keeps focus on that control. The container's click
handler only takes focus when the click lands on a non-interactive area,
via an `INTERACTIVE_ELEMENTS` closest() guard.

The keyboard-shortcut handler is intentionally left untouched: native
Space/Enter activation keeps owning the play button, so there's no
double-toggle risk. Added a focus-stability regression test.

Reported in #10 — took the focus-steal fix directly, left the broader
keyboard-shortcut rescoping (unverifiable Space double-fire path).

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

Copy link
Copy Markdown
Owner

Thanks for this, @jeryj — you flagged a genuine bug: activating the play button (or any interactive control) was stealing keyboard focus onto the wrapping container <div>.

I've landed that fix directly on main in 66bbd32, crediting the report in the changelog. The container's click handler now skips its focus grab when the click lands on an interactive control (button, a[href], input, [role="slider"]), so focus stays on the control you just activated. It'll ship in the next release.

I kept it to just the focus-steal guard and left the keyboard-shortcut rescoping out, for two reasons:

  1. The Space path is hard to verify. Adding the play button to the shortcut allowlist means Space gets handled by both the container's keydown (togglePlay() + preventDefault()) and the button's native activation. That leans on preventDefault suppressing the button's Space-activation, which is browser-dependent — and jsdom never synthesizes a click from a Space keydown, so the suite can't actually catch a double-toggle on that path. Leaving native activation to own Space/Enter on the button sidesteps the risk entirely.
  2. It overlapped in-flight work. The same keyboard/seek area was being reworked for the next minor (drag-to-scrub + an opt-in seek handle), so the branch had already diverged (CONFLICTING).

Really appreciate the detailed write-up and tests — the focus-stability case mapped straight onto the fix. Closing since the core issue is resolved on main. 🙏

@arraypress arraypress closed this Jul 1, 2026
@jeryj

jeryj commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Glad I could help out! Thanks for getting the main bug work merged in so fast.

@jeryj

jeryj commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

I tested it out, and there's functionality that got removed. I was intending for Spacebar activation to work to start Playing while focus is on the waveform as well. By removing the Space path work, spacebar does not begin playing the audio while focus is on the waveform.

arraypress added a commit that referenced this pull request Jul 1, 2026
…sion

1) The waveform slider swallowed Space (default: return) while the container Space handler only fires when the root is focused — so Space did nothing when the waveform itself had focus. Slider now toggles play on Space. Reported by @jeryj.

2) Media Session metadata was set once at load; iOS ignores metadata set before playback and playbackState was never set — blank lock-screen card. Now re-asserts metadata + sets playbackState + setPositionState on play/pause.

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

Copy link
Copy Markdown
Owner

Fixed in @arraypress/waveform-player@1.19.0, @jeryj — the waveform slider now toggles play on Space while it's focused (it had been swallowing the key, so play/pause only worked when the player root was focused). Thanks for catching the regression 🙏

@jeryj

jeryj commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

@arraypress Thank you! You're amazing!

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.

2 participants