fix: resolve all Svelte a11y warnings

- Associate labels with controls in CaptionSettingsPanel (9 warnings)
- Add tabindex and keyboard handlers to dialog overlays (8 warnings)
- Add <track> to video element for caption accessibility
- Add keyboard support to resize handle separator

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-22 13:30:27 -04:00
parent 3eebe01d63
commit 9d1f1cfcf3
7 changed files with 116 additions and 36 deletions

View File

@@ -188,9 +188,23 @@
<div
class="resize-handle"
class:active={isResizing}
role="separator"
role="slider"
aria-orientation="horizontal"
aria-label="Resize timeline and clip list"
aria-valuenow={timelineHeight}
aria-valuemin={40}
aria-valuemax={500}
tabindex="0"
onmousedown={handleResizeStart}
onkeydown={(e) => {
if (e.key === 'ArrowDown') {
e.preventDefault();
timelineHeight = Math.min(500, timelineHeight + 10);
} else if (e.key === 'ArrowUp') {
e.preventDefault();
timelineHeight = Math.max(40, timelineHeight - 10);
}
}}
></div>
<div class="cliplist-pane">
<ClipList onExport={() => (showExportDialog = true)} />

View File

@@ -12,8 +12,25 @@
});
</script>
<div class="overlay" role="presentation" onclick={onClose}>
<div class="dialog" role="dialog" onclick={(e) => e.stopPropagation()}>
<div
class="overlay"
role="presentation"
tabindex="-1"
onclick={onClose}
onkeydown={(e) => {
if (e.key === 'Escape' || e.key === 'Enter' || e.key === ' ') {
if (e.key !== 'Escape') e.preventDefault();
onClose();
}
}}
>
<div
class="dialog"
role="dialog"
tabindex="-1"
onclick={(e) => e.stopPropagation()}
onkeydown={(e) => e.stopPropagation()}
>
<img class="app-icon" src="/favicon.svg" alt="GUI Video Clipper icon" />
<h2>GUI Video Clipper</h2>
{#if version}

View File

@@ -45,8 +45,9 @@
<div class="scroll-area">
<!-- Font Family -->
<div class="control">
<label>Font</label>
<label for="caption-font">Font</label>
<select
id="caption-font"
value={settings.fontFamily}
onchange={(e) => update({ fontFamily: (e.target as HTMLSelectElement).value })}
>
@@ -61,19 +62,20 @@
<label>
Font Size
<span class="value">{settings.fontSize}px</span>
</label>
<input
type="range" min="12" max="36" step="1"
value={settings.fontSize}
oninput={(e) => update({ fontSize: parseInt((e.target as HTMLInputElement).value, 10) })}
/>
</label>
</div>
<!-- Text Color + Bold -->
<div class="control row">
<label>Text Color</label>
<label for="caption-text-color">Text Color</label>
<div class="row-controls">
<input
id="caption-text-color"
type="color" value={settings.textColor}
oninput={(e) => update({ textColor: (e.target as HTMLInputElement).value })}
/>
@@ -87,10 +89,10 @@
<!-- Dimmed Text Color -->
<div class="control">
<label>Dimmed Text</label>
<label for="caption-dimmed-auto">Dimmed Text</label>
<div class="radio-row">
<label>
<input type="radio" name="dimmed-mode" value="auto"
<input id="caption-dimmed-auto" type="radio" name="dimmed-mode" value="auto"
checked={settings.dimmedColorMode === 'auto'}
onchange={() => update({ dimmedColorMode: 'auto' })} />
Auto
@@ -108,18 +110,19 @@
<label>
Dim Opacity
<span class="value">{Math.round(settings.dimmedOpacity * 100)}%</span>
</label>
<input
type="range" min="10" max="90" step="5"
value={Math.round(settings.dimmedOpacity * 100)}
oninput={(e) => update({ dimmedOpacity: parseInt((e.target as HTMLInputElement).value, 10) / 100 })}
/>
</label>
</div>
{:else}
<div class="control row">
<label>Dimmed Color</label>
<label for="caption-dimmed-color">Dimmed Color</label>
<div class="row-controls">
<input
id="caption-dimmed-color"
type="color" value={settings.dimmedColor}
oninput={(e) => update({ dimmedColor: (e.target as HTMLInputElement).value })}
/>
@@ -165,12 +168,12 @@
<label>
BG Opacity
<span class="value">{Math.round(settings.backgroundOpacity * 100)}%</span>
</label>
<input
type="range" min="0" max="100" step="5"
value={Math.round(settings.backgroundOpacity * 100)}
oninput={(e) => update({ backgroundOpacity: parseInt((e.target as HTMLInputElement).value, 10) / 100 })}
/>
</label>
</div>
{/if}
@@ -195,21 +198,21 @@
<label>
Shadow Depth
<span class="value">{settings.shadowDepth}px</span>
</label>
<input
type="range" min="1" max="5" step="1"
value={settings.shadowDepth}
oninput={(e) => update({ shadowDepth: parseInt((e.target as HTMLInputElement).value, 10) })}
/>
</label>
</div>
{/if}
<!-- Position -->
<div class="control">
<label>Position</label>
<label for="caption-pos-bottom">Position</label>
<div class="radio-row">
<label>
<input type="radio" name="caption-position" value="bottom"
<input id="caption-pos-bottom" type="radio" name="caption-position" value="bottom"
checked={settings.position === 'bottom'}
onchange={() => update({ position: 'bottom' })} />
Bottom

View File

@@ -43,7 +43,17 @@
}
</script>
<div class="clip-list" role="listbox" onclick={handleContainerClick}>
<div
class="clip-list"
role="listbox"
tabindex="0"
onclick={handleContainerClick}
onkeydown={(e) => {
if (e.key === 'Escape') {
selectClip(null);
}
}}
>
{#if clips.length === 0}
<div class="empty">No clips yet — press I to mark in-point, O to mark out-point</div>
{:else}

View File

@@ -136,8 +136,25 @@
}
</script>
<div class="overlay" role="presentation" onclick={onClose}>
<div class="dialog" role="dialog" onclick={(e) => e.stopPropagation()}>
<div
class="overlay"
role="presentation"
tabindex="-1"
onclick={onClose}
onkeydown={(e) => {
if (e.key === 'Escape' || e.key === 'Enter' || e.key === ' ') {
if (e.key !== 'Escape') e.preventDefault();
onClose();
}
}}
>
<div
class="dialog"
role="dialog"
tabindex="-1"
onclick={(e) => e.stopPropagation()}
onkeydown={(e) => e.stopPropagation()}
>
<h2>Export Clips</h2>
{#if !isExportReady}

View File

@@ -57,8 +57,25 @@
}
</script>
<div class="overlay" role="presentation" onclick={onClose}>
<div class="panel" role="dialog" onclick={(e) => e.stopPropagation()}>
<div
class="overlay"
role="presentation"
tabindex="-1"
onclick={onClose}
onkeydown={(e) => {
if (e.key === 'Escape' || e.key === 'Enter' || e.key === ' ') {
if (e.key !== 'Escape') e.preventDefault();
onClose();
}
}}
>
<div
class="panel"
role="dialog"
tabindex="-1"
onclick={(e) => e.stopPropagation()}
onkeydown={(e) => e.stopPropagation()}
>
<h2>Preferences</h2>
<section>

View File

@@ -194,7 +194,9 @@
onloadeddata={handleLoadedData}
preload="metadata"
playsinline
></video>
>
<track kind="captions" />
</video>
{#if activeCues.length > 0}
<div class="caption-overlay" style={captionPosition}>
{#each activeCues as cue}