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:
+13
@@ -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>
|
||||
Generated
+2
-2
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user