Improve switch and settings tabs accessibility

We had a lot of a11y warnings on build. We still have some now, but not
that many any more. The settings tabs are now navigable by keyboard and
should be announced correctly. The solution is not perfect (yet), as
BeerCSS requires some things to be done that way, unfortunately.
This commit is contained in:
Lenny Angst
2025-08-25 22:18:06 +02:00
parent dc9f86f9a9
commit 0542d899a3
9 changed files with 155 additions and 79 deletions
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DeviceTable">
<option name="columnSorters">
<list>
<ColumnSorterState>
<option name="column" value="Name" />
<option name="order" value="ASCENDING" />
</ColumnSorterState>
</list>
</option>
</component>
</project>
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "materialious",
"version": "1.10.3",
"version": "1.10.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "materialious",
"version": "1.10.3",
"version": "1.10.4",
"hasInstallScript": true,
"dependencies": {
"@capacitor-community/electron": "^5.0.1",
@@ -29,11 +29,12 @@
<div class="max">
<p>{$_('enabled')}</p>
</div>
<label class="switch" tabindex="0" role="switch">
<label class="switch" tabindex="0">
<input
bind:checked={$synciousStore}
onclick={() => synciousStore.set(!$synciousStore)}
type="checkbox"
role="switch"
/>
<span></span>
</label>
@@ -43,11 +43,12 @@
<div class="max">
<p>{$_('layout.deArrow.titleOnly')}</p>
</div>
<label class="switch" tabindex="0" role="switch">
<label class="switch" tabindex="0">
<input
bind:checked={$deArrowTitlesOnly}
onclick={() => deArrowTitlesOnly.set(!$deArrowTitlesOnly)}
type="checkbox"
role="switch"
/>
<span></span>
</label>
@@ -57,11 +58,12 @@
<div class="max">
<p>{$_('enabled')}</p>
</div>
<label class="switch" tabindex="0" role="switch">
<label class="switch" tabindex="0">
<input
bind:checked={$deArrowEnabledStore}
onclick={() => deArrowEnabledStore.set(!$deArrowEnabledStore)}
type="checkbox"
role="switch"
/>
<span></span>
</label>
@@ -153,6 +153,7 @@
type="checkbox"
bind:checked={$interfaceAllowInsecureRequests}
onclick={allowInsecureRequests}
role="switch"
/>
<span></span>
</label>
@@ -184,11 +185,12 @@
<div class="max">
<div>{$_('layout.theme.AmoledTheme')}</div>
</div>
<label class="switch" tabindex="0" role="switch">
<label class="switch" tabindex="0">
<input
type="checkbox"
bind:checked={$interfaceAmoledTheme}
onclick={() => interfaceAmoledTheme.set(!$interfaceAmoledTheme)}
role="switch"
/>
<span></span>
</label>
@@ -201,7 +203,7 @@
<div class="max">
<div>{$_('layout.searchHistory')}</div>
</div>
<label class="switch" tabindex="0" role="switch">
<label class="switch" tabindex="0">
<input
type="checkbox"
bind:checked={$interfaceSearchHistoryEnabled}
@@ -209,6 +211,7 @@
interfaceSearchHistoryEnabled.set(!$interfaceSearchHistoryEnabled);
searchHistoryStore.set([]);
}}
role="switch"
/>
<span></span>
</label>
@@ -220,11 +223,12 @@
<div class="max">
<div>{$_('layout.lowBandwidthMode')}</div>
</div>
<label class="switch" tabindex="0" role="switch">
<label class="switch" tabindex="0">
<input
type="checkbox"
bind:checked={$interfaceLowBandwidthMode}
onclick={() => interfaceLowBandwidthMode.set(!$interfaceLowBandwidthMode)}
role="switch"
/>
<span></span>
</label>
@@ -236,11 +240,12 @@
<div class="max">
<div>{$_('layout.searchSuggestions')}</div>
</div>
<label class="switch" tabindex="0" role="switch">
<label class="switch" tabindex="0">
<input
type="checkbox"
bind:checked={$interfaceSearchSuggestionsStore}
onclick={() => interfaceSearchSuggestionsStore.set(!$interfaceSearchSuggestionsStore)}
role="switch"
/>
<span></span>
</label>
@@ -252,11 +257,12 @@
<div class="max">
<div>{$_('layout.expandDescription')}</div>
</div>
<label class="switch" tabindex="0" role="switch">
<label class="switch" tabindex="0">
<input
type="checkbox"
bind:checked={$interfaceAutoExpandDesc}
onclick={() => interfaceAutoExpandDesc.set(!$interfaceAutoExpandDesc)}
role="switch"
/>
<span></span>
</label>
@@ -268,11 +274,12 @@
<div class="max">
<div>{$_('layout.expandChapters')}</div>
</div>
<label class="switch" tabindex="0" role="switch">
<label class="switch" tabindex="0">
<input
type="checkbox"
bind:checked={$interfaceAutoExpandChapters}
onclick={() => interfaceAutoExpandChapters.set(!$interfaceAutoExpandChapters)}
role="switch"
/>
<span></span>
</label>
@@ -284,11 +291,12 @@
<div class="max">
<div>{$_('layout.expandComments')}</div>
</div>
<label class="switch" tabindex="0" role="switch">
<label class="switch" tabindex="0">
<input
type="checkbox"
bind:checked={$interfaceAutoExpandComments}
onclick={() => interfaceAutoExpandComments.set(!$interfaceAutoExpandComments)}
role="switch"
/>
<span></span>
</label>
@@ -301,11 +309,12 @@
<div class="max">
<div>{$_('layout.disableAutoUpdate')}</div>
</div>
<label class="switch" tabindex="0" role="switch">
<label class="switch" tabindex="0">
<input
type="checkbox"
bind:checked={$interfaceDisableAutoUpdate}
onclick={() => interfaceDisableAutoUpdate.set(!$interfaceDisableAutoUpdate)}
role="switch"
/>
<span></span>
</label>
@@ -141,11 +141,12 @@
<div class="max">
<div>{$_('layout.player.ccByDefault')}</div>
</div>
<label class="switch" tabindex="0" role="switch">
<label class="switch" tabindex="0">
<input
type="checkbox"
bind:checked={$playerCCByDefault}
onclick={() => playerCCByDefault.set(!$playerCCByDefault)}
role="switch"
/>
<span></span>
</label>
@@ -157,11 +158,12 @@
<div class="max">
<div>{$_('layout.player.autoPlay')}</div>
</div>
<label class="switch" tabindex="0" role="switch">
<label class="switch" tabindex="0">
<input
type="checkbox"
bind:checked={$playerAutoPlayStore}
onclick={() => playerAutoPlayStore.set(!$playerAutoPlayStore)}
role="switch"
/>
<span></span>
</label>
@@ -173,11 +175,12 @@
<div class="max">
<div>{$_('layout.player.alwaysLoopVideo')}</div>
</div>
<label class="switch" tabindex="0" role="switch">
<label class="switch" tabindex="0">
<input
type="checkbox"
bind:checked={$playerAlwaysLoopStore}
onclick={() => playerAlwaysLoopStore.set(!$playerAlwaysLoopStore)}
role="switch"
/>
<span></span>
</label>
@@ -190,11 +193,12 @@
<div class="max">
<div>{$_('layout.player.lockOrientation')}</div>
</div>
<label class="switch" tabindex="0" role="switch">
<label class="switch" tabindex="0">
<input
type="checkbox"
bind:checked={$playerAndroidLockOrientation}
onclick={() => playerAndroidLockOrientation.set(!$playerAndroidLockOrientation)}
role="switch"
/>
<span></span>
</label>
@@ -208,11 +212,12 @@
<div class="max">
<div>{$_('layout.player.proxyVideos')}</div>
</div>
<label class="switch" tabindex="0" role="switch">
<label class="switch" tabindex="0">
<input
type="checkbox"
bind:checked={$playerProxyVideosStore}
onclick={() => playerProxyVideosStore.set(!$playerProxyVideosStore)}
role="switch"
/>
<span></span>
</label>
@@ -225,11 +230,12 @@
<div class="max">
<div>{$_('layout.player.savePlaybackPosition')}</div>
</div>
<label class="switch" tabindex="0" role="switch">
<label class="switch" tabindex="0">
<input
type="checkbox"
bind:checked={$playerSavePlaybackPositionStore}
onclick={() => playerSavePlaybackPositionStore.set(!$playerSavePlaybackPositionStore)}
role="switch"
/>
<span></span>
</label>
@@ -241,11 +247,12 @@
<div class="max">
<div>{$_('layout.player.theatreModeByDefault')}</div>
</div>
<label class="switch" tabindex="0" role="switch">
<label class="switch" tabindex="0">
<input
type="checkbox"
bind:checked={$playerTheatreModeByDefaultStore}
onclick={() => playerTheatreModeByDefaultStore.set(!$playerTheatreModeByDefaultStore)}
role="switch"
/>
<span></span>
</label>
@@ -257,11 +264,12 @@
<div class="max">
<div>{$_('layout.player.autoPlayNextByDefault')}</div>
</div>
<label class="switch" tabindex="0" role="switch">
<label class="switch" tabindex="0">
<input
type="checkbox"
bind:checked={$playerAutoplayNextByDefaultStore}
onclick={() => playerAutoplayNextByDefaultStore.set(!$playerAutoplayNextByDefaultStore)}
role="switch"
/>
<span></span>
</label>
@@ -273,11 +281,12 @@
<div class="max">
<div>{$_('layout.player.playerStatistics')}</div>
</div>
<label class="switch" tabindex="0" role="switch">
<label class="switch" tabindex="0">
<input
type="checkbox"
bind:checked={$playerStatisticsByDefault}
onclick={() => playerStatisticsByDefault.set(!playerStatisticsByDefault)}
role="switch"
/>
<span></span>
</label>
@@ -29,11 +29,12 @@
<div class="max">
<p>{$_('enabled')}</p>
</div>
<label class="switch" tabindex="0" role="switch">
<label class="switch" tabindex="0">
<input
bind:checked={$returnYtDislikesStore}
onclick={() => returnYtDislikesStore.set(!$returnYtDislikesStore)}
type="checkbox"
role="switch"
/>
<span></span>
</label>
@@ -10,14 +10,17 @@
import SponsorBlock from './SponsorBlock.svelte';
import { isAndroidTvStore } from '$lib/store';
let activeTab:
| 'interface'
| 'player'
| 'data'
| 'ryd'
| 'api extended'
| 'sponsorblock'
| 'dearrow' = $state('interface');
let activeTab = $state('interface');
const isActive = (id: string) => activeTab === id;
const tabs = [
{ id: 'interface', label: $_('layout.interface'), icon: 'grid_view', component: Interface },
{ id: 'player', label: $_('layout.player.title'), icon: 'smart_display', component: Player },
{ id: 'ryd', label: 'RYD', icon: 'thumb_down', component: Ryd },
{ id: 'api extended', label: 'API Extended', icon: 'sync', component: ApiExtended },
{ id: 'sponsorblock', label: 'Sponsorblock', icon: 'block', component: SponsorBlock },
{ id: 'dearrow', label: $_('layout.deArrow.title'), icon: 'keyboard_double_arrow_down', component: DeArrow },
]
let dialogType = $state('');
@@ -29,10 +32,41 @@
}
}
function onKeydown(e: KeyboardEvent, idx: number) {
const keys = ["ArrowRight", "ArrowLeft", "Home", "End"] as const;
if (!keys.includes(e.key as typeof keys[number])) return;
e.preventDefault();
let next = idx;
switch (e.key) {
case "ArrowRight":
next = (idx + 1) % tabs.length;
break;
case "ArrowLeft":
next = (idx - 1 + tabs.length) % tabs.length;
break;
case "Home":
next = 0;
break;
case "End":
next = tabs.length - 1;
break;
}
activeTab = tabs[next].id;
const els = document.querySelectorAll<HTMLElement>('[role="tab"]');
els[next]?.focus();
}
onMount(() => {
checkWidth();
addEventListener('resize', () => checkWidth());
if ($isAndroidTvStore) {
document.getElementById(`tab-${activeTab}`)?.focus();
}
});
</script>
@@ -43,33 +77,32 @@
</nav>
<div>
<nav tabindex="0" class="tabbed small" style="outline: none" role="region">
<a class:active={activeTab === 'interface'} onclick={() => (activeTab = 'interface')}>
<i>grid_view</i>
<span>{$_('layout.interface')}</span>
</a>
<a class:active={activeTab === 'player'} onclick={() => (activeTab = 'player')}>
<i>smart_display</i>
<span>{$_('layout.player.title')}</span>
</a>
<a tabindex="0" class:active={activeTab === 'ryd'} onclick={() => (activeTab = 'ryd')}>
<i>thumb_down</i>
<span>RYD</span>
</a>
<a class:active={activeTab === 'api extended'} onclick={() => (activeTab = 'api extended')}>
<i>sync</i>
<span>API Extended</span>
</a>
<a class:active={activeTab === 'sponsorblock'} onclick={() => (activeTab = 'sponsorblock')}>
<i>block</i>
<span>Sponsorblock</span>
</a>
<a class:active={activeTab === 'dearrow'} onclick={() => (activeTab = 'dearrow')}>
<i>keyboard_double_arrow_down</i>
<span>{$_('layout.deArrow.title')}</span>
</a>
<nav class="tabbed small" style="outline: none" role="tablist" tabindex="0">
{#each tabs as tab, i}
<a
role="tab"
class:active={isActive(tab.id)}
aria-selected={isActive(tab.id)}
id={`tab-${tab.id}`}
aria-controls={`panel-${tab.id}`}
tabindex={isActive(tab.id) ? 0 : -1}
onclick={() => (activeTab = tab.id)}
onkeydown={(e) => onKeydown(e, i)}
>
<i>{tab.icon}</i>
<span>{tab.label}</span>
</a>
{/each}
{#if !$isAndroidTvStore}
<a class:active={activeTab === 'data'} onclick={() => (activeTab = 'data')}>
<a
role="tab"
class:active={isActive('data')}
aria-selected={isActive('data')}
id="tab-data"
aria-controls="panel-data"
tabindex="0"
onclick={() => (activeTab = 'data')}
>
<i>save</i>
<span>{$_('layout.dataPreferences.dataPreferences')}</span>
</a>
@@ -91,27 +124,32 @@
</a>
{/if}
</nav>
<div class="page padding" class:active={activeTab === 'interface'}>
<Interface />
</div>
<div class="page padding" class:active={activeTab === 'player'}>
<Player />
</div>
<div class="page padding" class:active={activeTab === 'data'}>
{#each tabs as t, _}
<div
class="page padding"
id={`panel-${t.id}`}
role="tabpanel"
aria-labelledby={`tab-${t.id}`}
hidden={!isActive(t.id)}
inert={!$isAndroidTvStore && !isActive(t.id)}
aria-hidden={!isActive(t.id)}
class:active={isActive(t.id)}
>
<t.component />
</div>
{/each}
<div
class="page padding"
id="panel-data"
role="tabpanel"
aria-labelledby="tab-data"
hidden={!isActive('data')}
inert={!$isAndroidTvStore && !isActive('data')}
aria-hidden={!isActive('data')}
class:active={isActive('data')}
>
<DataPreferences />
</div>
<div class="page padding" class:active={activeTab === 'ryd'}>
<Ryd />
</div>
<div class="page padding" class:active={activeTab === 'api extended'}>
<ApiExtended />
</div>
<div class="page padding" class:active={activeTab === 'sponsorblock'}>
<SponsorBlock />
</div>
<div class="page padding" class:active={activeTab === 'dearrow'}>
<DeArrow />
</div>
</div>
</dialog>
@@ -61,11 +61,12 @@
<div class="max">
<p>{$_('enabled')}</p>
</div>
<label class="switch" tabindex="0" role="switch">
<label class="switch" tabindex="0">
<input
bind:checked={$sponsorBlockStore}
onclick={() => sponsorBlockStore.set(!$sponsorBlockStore)}
type="checkbox"
role="switch"
/>
<span></span>
</label>
@@ -75,11 +76,12 @@
<div class="max">
<p>{$_('layout.sponsors.disableToast')}</p>
</div>
<label class="switch" tabindex="0" role="switch">
<label class="switch" tabindex="0">
<input
bind:checked={$sponsorBlockDisplayToastStore}
onclick={() => sponsorBlockDisplayToastStore.set(!$sponsorBlockDisplayToastStore)}
type="checkbox"
role="switch"
/>
<span></span>
</label>
@@ -109,11 +111,12 @@
<div class="max">
<p>{sponsor.name}</p>
</div>
<label class="switch" tabindex="0" role="switch">
<label class="switch" tabindex="0">
<input
type="checkbox"
checked={sponsorCategoriesList.includes(sponsor.category)}
onclick={() => toggleSponsor(sponsor.category)}
role="switch"
/>
<span></span>
</label>