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:
@@ -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)} />
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user