diff --git a/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-dock-panel.component.spec.ts b/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-dock-panel.component.spec.ts index 66cfe05bd..ba51e7313 100644 --- a/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-dock-panel.component.spec.ts +++ b/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-dock-panel.component.spec.ts @@ -114,6 +114,26 @@ describe('EmbeddedMpvDockPanelComponent', () => { document.removeEventListener('keydown', documentKeydown); }); + it('lets Space/Enter activate a chip without leaking to global shortcuts', () => { + // The global shortcut handler's Space case calls preventDefault() + + // togglePaused(); preventing the keydown default would suppress the + // chip button's native activation. The panel must stop Space/Enter + // from reaching the document without cancelling their default action. + const documentKeydown = jest.fn(); + document.addEventListener('keydown', documentKeydown); + + const space = dispatchPanelKey(' '); + const enter = dispatchPanelKey('Enter'); + + // Not defaultPrevented → the focused chip button still activates. + expect(space.defaultPrevented).toBe(false); + expect(enter.defaultPrevented).toBe(false); + // stopPropagation → the global handler never sees them. + expect(documentKeydown).not.toHaveBeenCalled(); + + document.removeEventListener('keydown', documentKeydown); + }); + it('maps vertical wheel deltas to horizontal ribbon scrolling', () => { const ribbonEl = ribbon(); ribbonEl.scrollLeft = 0; diff --git a/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-dock-panel.component.ts b/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-dock-panel.component.ts index 13c15f9c5..712bb1e01 100644 --- a/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-dock-panel.component.ts +++ b/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-dock-panel.component.ts @@ -106,6 +106,15 @@ export class EmbeddedMpvDockPanelComponent implements OnDestroy { onPanelKeydown(event: KeyboardEvent): void { const key = event.key; + if (key === ' ' || key === 'Enter') { + // Let the focused chip button activate natively (Space/Enter → + // click → chipSelected), but stop the keydown before it reaches + // the global shortcut handler, whose Space case would otherwise + // preventDefault() the activation and toggle playback instead. + // No preventDefault here — that would re-suppress the click. + event.stopPropagation(); + return; + } if (key === 'ArrowUp' || key === 'ArrowDown') { // The panel owns the keyboard: never let the global volume // shortcuts fire while a chip panel is open. diff --git a/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-dock-panels.ts b/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-dock-panels.ts index 183471bb8..e12e0c5ae 100644 --- a/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-dock-panels.ts +++ b/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-dock-panels.ts @@ -66,7 +66,11 @@ export class EmbeddedMpvDockPanelState { } toggle(kind: EmbeddedMpvDockPanelKind): void { - this.openerKind = this.deps.menus.dockPanelOpen() ? null : kind; + // Always remember the button that opened a panel so focus returns to + // it when the panel closes. restoreOpenerFocus() only acts when focus + // has fallen to document.body, so re-toggling a panel with focus still + // on its button never steals focus. + this.openerKind = kind; this.deps.menus.toggle(kind); this.deps.revealControls(); } diff --git a/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-player.component.scss b/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-player.component.scss index b08afd11d..3c4151932 100644 --- a/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-player.component.scss +++ b/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-player.component.scss @@ -294,11 +294,17 @@ display: flex; align-items: center; gap: 8px; + // Let the actions cell shrink below its content so the inline volume + // (a scroll container) clips its own slider instead of pushing the + // fullscreen button past the overflow:hidden player edge on narrow + // sidebar-constrained widths (viewport wider than the 720px breakpoint). + min-width: 0; } .embedded-mpv-player__volume-group { display: flex; align-items: center; + min-width: 0; } .embedded-mpv-player__control-button {