a11y: role=menu / menuitem / separator on the two right-click context menus

Both context menus (queue row + VOD card) were built as plain <div> trees with no ARIA roles — screen readers couldn't tell they were menus, and the individual rows weren't exposed as menu items. Users on assistive tech got a generic "group of nested divs" with no menu semantics, despite the menus being visually and functionally menus.

Added the WAI-ARIA menu pattern roles:
- role="menu" on the container
- role="menuitem" on each clickable row
- aria-disabled="true" on disabled menu items
- role="separator" on the horizontal divider lines

Both renderer-queue.ts (queue right-click context menu) and renderer-streamers.ts (VOD card right-click context menu) get the same treatment so the two share both visual style (.context-menu — 4.6.120) and accessibility semantics.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
xRangerDE 2026-05-11 09:07:35 +02:00
parent 7de560f44c
commit 8d95a4a6a5
2 changed files with 6 additions and 0 deletions

View File

@ -186,11 +186,14 @@ function showQueueContextMenu(x: number, y: number, item: QueueItem): void {
const menu = document.createElement('div'); const menu = document.createElement('div');
menu.className = 'context-menu'; menu.className = 'context-menu';
menu.setAttribute('role', 'menu');
const makeItem = (label: string, onClick: () => void, disabled = false): HTMLElement => { const makeItem = (label: string, onClick: () => void, disabled = false): HTMLElement => {
const el = document.createElement('div'); const el = document.createElement('div');
el.textContent = label; el.textContent = label;
el.className = 'context-menu-item' + (disabled ? ' disabled' : ''); el.className = 'context-menu-item' + (disabled ? ' disabled' : '');
el.setAttribute('role', 'menuitem');
if (disabled) el.setAttribute('aria-disabled', 'true');
if (!disabled) { if (!disabled) {
el.addEventListener('click', () => { el.addEventListener('click', () => {
try { onClick(); } finally { closeQueueContextMenu(); } try { onClick(); } finally { closeQueueContextMenu(); }
@ -202,6 +205,7 @@ function showQueueContextMenu(x: number, y: number, item: QueueItem): void {
const makeSeparator = (): HTMLElement => { const makeSeparator = (): HTMLElement => {
const sep = document.createElement('div'); const sep = document.createElement('div');
sep.className = 'context-menu-separator'; sep.className = 'context-menu-separator';
sep.setAttribute('role', 'separator');
return sep; return sep;
}; };

View File

@ -925,6 +925,7 @@ function showVodContextMenu(x: number, y: number, ctx: VodCardContext): void {
const menu = document.createElement('div'); const menu = document.createElement('div');
menu.className = 'context-menu'; menu.className = 'context-menu';
menu.setAttribute('role', 'menu');
const downloadedIds = new Set( const downloadedIds = new Set(
Array.isArray(config.downloaded_vod_ids) Array.isArray(config.downloaded_vod_ids)
@ -937,6 +938,7 @@ function showVodContextMenu(x: number, y: number, ctx: VodCardContext): void {
const el = document.createElement('div'); const el = document.createElement('div');
el.textContent = label; el.textContent = label;
el.className = 'context-menu-item'; el.className = 'context-menu-item';
el.setAttribute('role', 'menuitem');
el.addEventListener('click', () => { el.addEventListener('click', () => {
try { onClick(); } finally { closeVodContextMenu(); } try { onClick(); } finally { closeVodContextMenu(); }
}); });