a11y: update-banner progress bar role=progressbar + aria-valuenow

Third progress bar in this a11y pass — the download-progress bar
shown in the update banner during an auto-update download. Same
pattern as 4.6.64 (queue) + 4.6.65 (cut/merge): bare div with
JS-driven width, no semantic role.

Promoted the .update-banner-progress-track to role="progressbar"
with aria-valuemin / max / now + a localized aria-label
(updateProgressAria: "Update download progress" / "Update-Download-
Fortschritt").

Three call sites in renderer-updates.ts that drive bar.style.width
now also stamp aria-valuenow on the gauge:
- onUpdateProgress event handler (per-tick percent)
- setDownloadPendingUi (initial 30% indeterminate placeholder)
- setDownloadReadyUi (100% on finish)

renderer-texts.applyText sets the localized aria-label at boot +
on language switch.

That's all three application-level progress bars now AT-friendly.
The same pattern would extend to any future progress UI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
xRangerDE 2026-05-11 04:36:42 +02:00
parent 01acbcc47f
commit c393457492
5 changed files with 11 additions and 4 deletions

View File

@ -11,7 +11,7 @@
<div class="update-banner" id="updateBanner"> <div class="update-banner" id="updateBanner">
<span id="updateText">Neue Version verfügbar!</span> <span id="updateText">Neue Version verfügbar!</span>
<div id="updateProgress" class="update-banner-progress-wrap" style="display: none;"> <div id="updateProgress" class="update-banner-progress-wrap" style="display: none;">
<div class="update-banner-progress-track"> <div class="update-banner-progress-track" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" aria-label="Update download" id="updateProgressGauge">
<div id="updateProgressBar" class="update-banner-progress-bar"></div> <div id="updateProgressBar" class="update-banner-progress-bar"></div>
</div> </div>
</div> </div>

View File

@ -359,7 +359,8 @@ const UI_TEXT_DE = {
sidebarEmpty: 'Noch keine Streamer. Fuege oben rechts einen hinzu.', sidebarEmpty: 'Noch keine Streamer. Fuege oben rechts einen hinzu.',
removeAria: 'Entfernen', removeAria: 'Entfernen',
cutProgressAria: 'Schnitt-Fortschritt', cutProgressAria: 'Schnitt-Fortschritt',
mergeProgressAria: 'Merge-Fortschritt' mergeProgressAria: 'Merge-Fortschritt',
updateProgressAria: 'Update-Download-Fortschritt'
}, },
vods: { vods: {
noneTitle: 'Keine VODs', noneTitle: 'Keine VODs',

View File

@ -359,7 +359,8 @@ const UI_TEXT_EN = {
sidebarEmpty: 'No streamers yet. Add one via the input at the top right.', sidebarEmpty: 'No streamers yet. Add one via the input at the top right.',
removeAria: 'Remove', removeAria: 'Remove',
cutProgressAria: 'Cut progress', cutProgressAria: 'Cut progress',
mergeProgressAria: 'Merge progress' mergeProgressAria: 'Merge progress',
updateProgressAria: 'Update download progress'
}, },
vods: { vods: {
noneTitle: 'No VODs', noneTitle: 'No VODs',

View File

@ -255,6 +255,7 @@ function applyLanguageToStaticUI(): void {
setAriaLabelAll('.modal-close-localizable', UI_TEXT.streamers.modalCloseAria); setAriaLabelAll('.modal-close-localizable', UI_TEXT.streamers.modalCloseAria);
document.getElementById('cutProgressGauge')?.setAttribute('aria-label', UI_TEXT.streamers.cutProgressAria); document.getElementById('cutProgressGauge')?.setAttribute('aria-label', UI_TEXT.streamers.cutProgressAria);
document.getElementById('mergeProgressGauge')?.setAttribute('aria-label', UI_TEXT.streamers.mergeProgressAria); document.getElementById('mergeProgressGauge')?.setAttribute('aria-label', UI_TEXT.streamers.mergeProgressAria);
document.getElementById('updateProgressGauge')?.setAttribute('aria-label', UI_TEXT.streamers.updateProgressAria);
setText('backupCardTitle', UI_TEXT.static.backupCardTitle); setText('backupCardTitle', UI_TEXT.static.backupCardTitle);
setText('backupCardIntro', UI_TEXT.static.backupCardIntro); setText('backupCardIntro', UI_TEXT.static.backupCardIntro);
setText('btnExportConfig', UI_TEXT.static.exportConfig); setText('btnExportConfig', UI_TEXT.static.exportConfig);

View File

@ -127,7 +127,9 @@ function setDownloadPendingUi(): void {
const bar = byId('updateProgressBar'); const bar = byId('updateProgressBar');
bar.classList.add('downloading'); bar.classList.add('downloading');
bar.style.width = latestDownloadProgress ? `${latestDownloadProgress.percent}%` : '30%'; const pendingPct = latestDownloadProgress ? latestDownloadProgress.percent : 30;
bar.style.width = `${pendingPct}%`;
byId('updateProgressGauge').setAttribute('aria-valuenow', String(Math.round(pendingPct)));
if (!latestDownloadProgress) { if (!latestDownloadProgress) {
byId('updateText').textContent = `Version ${latestUpdateVersion || '?'} ${UI_TEXT.updates.downloading}`; byId('updateText').textContent = `Version ${latestUpdateVersion || '?'} ${UI_TEXT.updates.downloading}`;
@ -145,6 +147,7 @@ function setDownloadReadyUi(info?: UpdateInfo): void {
const bar = byId('updateProgressBar'); const bar = byId('updateProgressBar');
bar.classList.remove('downloading'); bar.classList.remove('downloading');
bar.style.width = '100%'; bar.style.width = '100%';
byId('updateProgressGauge').setAttribute('aria-valuenow', '100');
byId('updateProgress').style.display = 'block'; byId('updateProgress').style.display = 'block';
byId('updateText').textContent = `Version ${activeInfo.version} ${UI_TEXT.updates.ready}`; byId('updateText').textContent = `Version ${activeInfo.version} ${UI_TEXT.updates.ready}`;
@ -574,6 +577,7 @@ window.api.onUpdateDownloadProgress((progress: UpdateDownloadProgress) => {
const bar = byId('updateProgressBar'); const bar = byId('updateProgressBar');
bar.classList.remove('downloading'); bar.classList.remove('downloading');
bar.style.width = progress.percent + '%'; bar.style.width = progress.percent + '%';
byId('updateProgressGauge').setAttribute('aria-valuenow', String(Math.round(progress.percent)));
showUpdateBanner(); showUpdateBanner();
byId('updateProgress').style.display = 'block'; byId('updateProgress').style.display = 'block';