feat(web/m7-364): drawer focus management + grip Enter/Space handling
This commit is contained in:
@@ -3,12 +3,27 @@
|
||||
import { player, closeQueueDrawer } from '$lib/player/store.svelte';
|
||||
import QueueTrackRow from './QueueTrackRow.svelte';
|
||||
|
||||
let previouslyFocused: HTMLElement | null = null;
|
||||
let closeButton: HTMLButtonElement | undefined = $state();
|
||||
|
||||
function totalDurationLabel(tracks: { duration_sec: number }[]): string {
|
||||
const totalSec = tracks.reduce((s, tr) => s + (tr.duration_sec ?? 0), 0);
|
||||
const m = Math.floor(totalSec / 60);
|
||||
const h = Math.floor(m / 60);
|
||||
return h > 0 ? `${h}h ${m % 60}m` : `${m} min`;
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (player.queueDrawerOpen) {
|
||||
previouslyFocused = document.activeElement as HTMLElement | null;
|
||||
// Defer to next microtask so the close button is rendered + visible
|
||||
// before we focus (the drawer may still be transitioning in).
|
||||
Promise.resolve().then(() => closeButton?.focus());
|
||||
} else if (previouslyFocused) {
|
||||
previouslyFocused.focus();
|
||||
previouslyFocused = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if player.queueDrawerOpen}
|
||||
@@ -38,6 +53,7 @@
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
bind:this={closeButton}
|
||||
aria-label="Close queue"
|
||||
onclick={() => closeQueueDrawer()}
|
||||
class="text-text-secondary hover:text-text-primary"
|
||||
|
||||
@@ -41,6 +41,11 @@
|
||||
} else if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
moveQueueItem(index, index + 1);
|
||||
} else if (e.key === ' ' || e.key === 'Enter') {
|
||||
// Reorder activates via arrow keys, not Space/Enter. Prevent
|
||||
// default so Space doesn't scroll the queue list and Enter doesn't
|
||||
// fire the button's no-op click.
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -53,6 +58,7 @@
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Reorder track (use arrow keys)"
|
||||
aria-keyshortcuts="ArrowUp ArrowDown"
|
||||
onkeydown={handleHandleKeydown}
|
||||
class="cursor-grab text-text-secondary hover:text-text-primary flex-shrink-0"
|
||||
>
|
||||
|
||||
@@ -77,4 +77,18 @@ describe('QueueTrackRow', () => {
|
||||
await fireEvent.keyDown(handle, { key: 'ArrowUp' });
|
||||
expect(moveQueueItem).toHaveBeenCalledWith(3, 2);
|
||||
});
|
||||
|
||||
it('Enter on the drag handle prevents default but does not call moveQueueItem', async () => {
|
||||
render(QueueTrackRow, { props: { track: sampleTrack, index: 3, isCurrent: false } });
|
||||
const handle = screen.getByLabelText(/reorder track/i);
|
||||
await fireEvent.keyDown(handle, { key: 'Enter' });
|
||||
expect(moveQueueItem).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Space on the drag handle prevents default but does not call moveQueueItem', async () => {
|
||||
render(QueueTrackRow, { props: { track: sampleTrack, index: 3, isCurrent: false } });
|
||||
const handle = screen.getByLabelText(/reorder track/i);
|
||||
await fireEvent.keyDown(handle, { key: ' ' });
|
||||
expect(moveQueueItem).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user