a11y: VOD cards keyboard-activatable — opens VOD on Twitch via Enter/Space

Following the chip + row + nav a11y passes, the VOD cards in the
main grid were the last big mouse-only surface in the sidebar +
main panel flow. Each card already delegated click via a single
vodGrid handler — but the card div itself was unfocusable, so a
keyboard user could only reach the +Queue / Trim VOD buttons
inside, never the card thumbnail click that opens the VOD page on
Twitch.

Added on each .vod-card:
- role="button" + tabindex="0"
- aria-label set to the VOD title so AT announces it correctly
  ("32h37m9s VOD: Cyborg Watchparty button") instead of reading
  the whole card content row by row

Added to the existing delegated vodGrid handler:
- A keydown branch that opens the VOD on Twitch when Enter / Space
  fires on a focused .vod-card and the event target is the card
  itself (not a child action button or checkbox — those have
  their own native button / checkbox semantics that handle Enter
  / Space correctly already)

CSS adds a 3px purple focus-visible ring + accent-coloured border
on the focused card, mirroring the hover state's purple glow.

Tab order through the VOD grid now goes: VOD card -> checkbox -> Trim
button -> +Queue button -> next VOD card. Predictable enough for
keyboard navigation through a 38-VOD streamer profile.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
xRangerDE 2026-05-11 03:37:14 +02:00
parent 0b99014de3
commit 76be8d3949
2 changed files with 25 additions and 0 deletions

View File

@ -248,6 +248,9 @@ function buildVodCardHtml(vod: VOD, streamer: string, downloadedIds?: Set<string
// titles containing backslashes / HTML entities like &apos;. // titles containing backslashes / HTML entities like &apos;.
return ` return `
<div class="vod-card${isChecked ? ' selected' : ''}${isAlreadyDownloaded ? ' already-downloaded' : ''}" <div class="vod-card${isChecked ? ' selected' : ''}${isAlreadyDownloaded ? ' already-downloaded' : ''}"
role="button"
tabindex="0"
aria-label="${safeTitleAttr}"
data-vod-id="${safeIdAttr}" data-vod-id="${safeIdAttr}"
data-vod-url="${safeUrlAttr}" data-vod-url="${safeUrlAttr}"
data-vod-title="${safeTitleAttr}" data-vod-title="${safeTitleAttr}"
@ -892,6 +895,22 @@ function initVodGridSelectionDelegation(): void {
e.preventDefault(); e.preventDefault();
showVodContextMenu(e.clientX, e.clientY, ctx); showVodContextMenu(e.clientX, e.clientY, ctx);
}); });
// Enter / Space on a focused VOD card opens the VOD on Twitch — same
// outcome as a mouse click on the thumbnail. Skip when focus is on a
// child (action button, checkbox) because those have their own
// keyboard handlers (native button + checkbox semantics).
grid.addEventListener('keydown', (e) => {
if (e.key !== 'Enter' && e.key !== ' ') return;
const target = e.target as HTMLElement | null;
if (!target) return;
const card = target.closest('.vod-card') as HTMLElement | null;
if (!card || card !== target) return;
const ctx = readVodCardContext(card);
if (!ctx) return;
e.preventDefault();
void window.api.openExternal(ctx.url);
});
} }
let activeVodContextMenu: HTMLElement | null = null; let activeVodContextMenu: HTMLElement | null = null;

View File

@ -1288,6 +1288,12 @@ select option {
border-color: rgba(145, 70, 255, 0.35); border-color: rgba(145, 70, 255, 0.35);
} }
.vod-card:focus-visible {
outline: none;
border-color: rgba(145, 70, 255, 0.7);
box-shadow: 0 0 0 3px rgba(145, 70, 255, 0.35);
}
.vod-card.selected { .vod-card.selected {
box-shadow: 0 0 0 2px #9146FF, 0 8px 25px rgba(145, 70, 255, 0.25); box-shadow: 0 0 0 2px #9146FF, 0 8px 25px rgba(145, 70, 255, 0.25);
} }