Compare commits

...

2 Commits

Author SHA1 Message Date
xRangerDE
6213134a27 release: 4.6.64 queue progress bar role=progressbar + aria-valuenow
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 04:27:14 +02:00
xRangerDE
58e52399c5 a11y: queue progress bar — role=progressbar + aria-valuenow
The download progress bar inside each queue item was a plain
<div class="queue-progress-bar" style="width: 73%;"> with no
semantic indication that it represented progress. Screen readers
just announced the surrounding text ("Downloading...") with no
running value.

Added role="progressbar" + aria-valuemin=0 + aria-valuemax=100 +
aria-valuenow on the wrapping .queue-progress-wrap (since the bar
itself is just the visual fill — the wrap is the semantic gauge
region). aria-label is the status label so AT announces "VOD title
75 percent" instead of an unlabeled gauge.

updateQueueItemProgress also re-stamps aria-valuenow as the
percentage advances, so AT live regions can pick up the running
update without needing a full re-render.

For indeterminate progress (pre-rolling, before the first byte
event arrives) aria-valuenow stays at 0 — the screen reader still
gets a coherent reading even if visually the bar is in
indeterminate-pulse mode.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 04:27:13 +02:00
3 changed files with 6 additions and 4 deletions

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "twitch-vod-manager",
"version": "4.6.63",
"version": "4.6.64",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "twitch-vod-manager",
"version": "4.6.63",
"version": "4.6.64",
"license": "MIT",
"dependencies": {
"axios": "^1.6.0",

View File

@ -1,6 +1,6 @@
{
"name": "twitch-vod-manager",
"version": "4.6.63",
"version": "4.6.64",
"description": "Twitch VOD Manager - Download Twitch VODs easily",
"main": "dist/main.js",
"author": "xRangerDE",

View File

@ -412,6 +412,7 @@ function updateQueueItemProgress(progress: DownloadProgress): void {
if (!item) return;
const bar = el.querySelector('.queue-progress-bar') as HTMLElement | null;
const wrap = el.querySelector('.queue-progress-wrap') as HTMLElement | null;
const text = el.querySelector('.queue-progress-text') as HTMLElement | null;
const meta = el.querySelector('.queue-meta') as HTMLElement | null;
@ -420,6 +421,7 @@ function updateQueueItemProgress(progress: DownloadProgress): void {
const pct = isDeterminate ? Math.min(100, progress.progress) : 0;
bar.style.width = `${pct}%`;
bar.className = `queue-progress-bar${isDeterminate ? '' : ' indeterminate'}`;
if (wrap) wrap.setAttribute('aria-valuenow', String(Math.round(pct)));
}
if (text) text.textContent = getQueueProgressText(item);
if (meta) meta.textContent = getQueueMetaText(item);
@ -559,7 +561,7 @@ function renderQueue(): void {
<div class="queue-status-label">${safeStatusLabel}</div>
</div>
<div class="queue-meta">${safeMeta}${mergeMetaExtra}</div>
<div class="queue-progress-wrap">
<div class="queue-progress-wrap" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${Math.round(progressValue)}" aria-label="${escapeHtml(safeStatusLabel)}">
<div class="queue-progress-bar${progressClass}" style="width: ${progressValue}%;"></div>
</div>
<div class="queue-progress-text">${safeProgressText}</div>