yay accessibility!
aria tags + better touchscreen support
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { TAG_WEIGHT_NAMES } from '$lib/stores.svelte.ts';
|
||||
import type { TagWeights } from '$lib/types';
|
||||
|
||||
let { tags, highlighted = {} }: { tags: TagWeights; highlighted?: Record<string, number> } =
|
||||
@@ -9,7 +10,8 @@
|
||||
|
||||
{#each sorted as [tag, weight], i}{i ? ', ' : ''}<span
|
||||
class="weight-{weight}"
|
||||
class:highlighted={tag in highlighted}>{tag}</span
|
||||
class:highlighted={tag in highlighted}
|
||||
>{tag}<span class="screen-reader-only"> ({TAG_WEIGHT_NAMES[weight]})</span></span
|
||||
>{/each}
|
||||
|
||||
<style>
|
||||
@@ -30,4 +32,13 @@
|
||||
.highlighted {
|
||||
background: rgba(255, 220, 0, 0.18);
|
||||
}
|
||||
|
||||
.screen-reader-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
clip-path: inset(50%);
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -9,6 +9,7 @@ $effect.root(() => {
|
||||
});
|
||||
|
||||
export const MAX_TAG_WEIGHT = 3;
|
||||
export const TAG_WEIGHT_NAMES = ['off', 'light', 'medium', 'heavy'];
|
||||
|
||||
export const tagList = [
|
||||
'acoustic',
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
|
||||
a,
|
||||
a:visited {
|
||||
outline: none;
|
||||
color: black;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -102,12 +102,16 @@
|
||||
/>
|
||||
<button
|
||||
onclick={() => (cachedOnly = !cachedOnly)}
|
||||
aria-label="Downloaded only"
|
||||
aria-pressed={cachedOnly}
|
||||
class="material-icons filter-download {cachedOnly
|
||||
? 'select-faves-star'
|
||||
: 'unselect-faves-star'}">file_download</button
|
||||
>
|
||||
<button
|
||||
onclick={() => (favoritesOnly = !favoritesOnly)}
|
||||
aria-label="Favorites only"
|
||||
aria-pressed={favoritesOnly}
|
||||
class="material-icons {favoritesOnly ? '' : 'un'}select-faves-star">star</button
|
||||
>
|
||||
</div>
|
||||
@@ -125,7 +129,11 @@
|
||||
class="stream-item {current ? 'current-stream' : ''}"
|
||||
id="stream-{stream.id}"
|
||||
>
|
||||
<button class="stream-item-button" onclick={() => goto('/streams/' + stream.id)}>
|
||||
<button
|
||||
class="stream-item-button"
|
||||
aria-current={current ? 'page' : undefined}
|
||||
onclick={() => goto('/streams/' + stream.id)}
|
||||
>
|
||||
<span class="stream-item-id">
|
||||
ID:
|
||||
{shorthandCode(stream.id)}</span
|
||||
@@ -135,12 +143,12 @@
|
||||
|
||||
<span class="stream-item-length">{formatSecondsToHms(stream.length_seconds)}</span>
|
||||
|
||||
<p class="stream-item-tags" hidden={!Object.keys(stream.tags).length}>
|
||||
<span class="stream-item-tags" hidden={!Object.keys(stream.tags).length}>
|
||||
<span class="stream-item-tags-label">Tags:</span>
|
||||
<span class="stream-item-tags-list"
|
||||
><WeightedTags tags={stream.tags} highlighted={filteredTags} /></span
|
||||
>
|
||||
</p>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
@@ -148,6 +156,8 @@
|
||||
e.stopPropagation();
|
||||
updateFavorites(stream);
|
||||
}}
|
||||
aria-label="Favorite"
|
||||
aria-pressed={favorited}
|
||||
class="material-icons stream-item-star {favorited ? 'stream-item-star-faved' : ''}"
|
||||
>{favorited ? 'star' : 'star_border'}</button
|
||||
>
|
||||
@@ -159,6 +169,7 @@
|
||||
e.stopPropagation();
|
||||
streamCache.remove(stream.id);
|
||||
}}
|
||||
aria-label="Delete download"
|
||||
class="material-icons stream-item-cached stream-item-cached-btn">delete</button
|
||||
>
|
||||
{:else}
|
||||
@@ -168,6 +179,7 @@
|
||||
e.stopPropagation();
|
||||
streamCache.download(stream);
|
||||
}}
|
||||
aria-label={partial ? 'Resume download' : 'Download'}
|
||||
title={partial
|
||||
? `Partially downloaded (${Math.round((partial.received / partial.total) * 100)}%). Click to resume.`
|
||||
: undefined}
|
||||
@@ -350,6 +362,22 @@
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.stream-item .stream-item-star:focus-visible,
|
||||
.stream-item .stream-item-cached-btn:focus-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* touch screens can't hover, so always display */
|
||||
@media (hover: none) {
|
||||
.stream-item-star {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.stream-item-cached {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 575px) {
|
||||
.stream-item-star {
|
||||
opacity: 0.5;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import Select from 'svelte-select';
|
||||
import { tagList, MAX_TAG_WEIGHT } from '$lib/stores.svelte.ts';
|
||||
import { tagList, MAX_TAG_WEIGHT, TAG_WEIGHT_NAMES } from '$lib/stores.svelte.ts';
|
||||
|
||||
// type not exported in the lib itself
|
||||
type SelectItem = Record<string, unknown>;
|
||||
@@ -115,6 +115,7 @@
|
||||
<button
|
||||
type="button"
|
||||
class="weight"
|
||||
aria-label="{selection.label}, at least {TAG_WEIGHT_NAMES[min]}, press to change"
|
||||
onpointerup={(e) => e.stopPropagation()}
|
||||
onclick={(e) => cycle(tag, e.shiftKey)}
|
||||
>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { tagList, MAX_TAG_WEIGHT } from '$lib/stores.svelte.ts';
|
||||
import { tagList, MAX_TAG_WEIGHT, TAG_WEIGHT_NAMES } from '$lib/stores.svelte.ts';
|
||||
import type { TagWeights } from '$lib/types';
|
||||
|
||||
let { original = $bindable() } = $props();
|
||||
@@ -42,6 +42,7 @@
|
||||
type="button"
|
||||
class="tag-pill"
|
||||
class:off={!weight}
|
||||
aria-label="{tag}, {TAG_WEIGHT_NAMES[weight]}"
|
||||
onclick={(e) => cycle(tag, e.shiftKey)}
|
||||
>
|
||||
{tag}<span class="dots">{'·'.repeat(weight)}</span>
|
||||
|
||||
@@ -246,7 +246,7 @@
|
||||
seekText = formatSeconds(seekValue);
|
||||
}
|
||||
|
||||
function trackMouse(event: MouseEvent) {
|
||||
function trackPointer(event: MouseEvent) {
|
||||
if (seeking) updateSeekVisual(event);
|
||||
if (showTooltip && !disableTooltip) seekTooltip(event);
|
||||
if (volumeSeeking) seekVolume(event);
|
||||
@@ -265,7 +265,7 @@
|
||||
<svelte:window
|
||||
bind:innerWidth
|
||||
bind:innerHeight
|
||||
onmouseup={() => {
|
||||
onpointerup={() => {
|
||||
if (seeking && pendingSeekTime != null) {
|
||||
audio.currentTime = pendingSeekTime;
|
||||
currentTime = pendingSeekTime;
|
||||
@@ -274,7 +274,11 @@
|
||||
}
|
||||
seeking = volumeSeeking = false;
|
||||
}}
|
||||
onmousemove={trackMouse}
|
||||
onpointercancel={() => {
|
||||
pendingSeekTime = null;
|
||||
seeking = volumeSeeking = false;
|
||||
}}
|
||||
onpointermove={trackPointer}
|
||||
/>
|
||||
|
||||
{#if display}
|
||||
@@ -282,6 +286,7 @@
|
||||
<button
|
||||
class="material-icons"
|
||||
style="--icon-color:{iconColor}"
|
||||
aria-label={paused ? 'Play' : 'Pause'}
|
||||
onclick={() => (audio.paused ? audio.play() : audio.pause())}
|
||||
>
|
||||
{#if paused}
|
||||
@@ -294,12 +299,12 @@
|
||||
bind:this={songBar}
|
||||
value={seeking && pendingSeekTime != null ? pendingSeekTime : currentTime || 0}
|
||||
max={duration}
|
||||
onmousedown={(e) => {
|
||||
onpointerdown={(e) => {
|
||||
seeking = true;
|
||||
updateSeekVisual(e);
|
||||
}}
|
||||
onmouseenter={() => (showTooltip = true)}
|
||||
onmouseleave={() => (showTooltip = false)}
|
||||
onpointerenter={() => (showTooltip = true)}
|
||||
onpointerleave={() => (showTooltip = false)}
|
||||
style="--primary-color:{barPrimaryColor}; --secondary-color:{barSecondaryColor}"
|
||||
class="song-progress"
|
||||
></progress>
|
||||
@@ -309,6 +314,8 @@
|
||||
<button
|
||||
style="--icon-color:{iconColor}"
|
||||
class="material-icons"
|
||||
aria-label="Mute"
|
||||
aria-pressed={muted}
|
||||
onclick={() => (muted = !muted)}
|
||||
>
|
||||
{#if muted}
|
||||
@@ -324,7 +331,7 @@
|
||||
<progress
|
||||
bind:this={volumeBar}
|
||||
value={volume}
|
||||
onmousedown={() => (volumeSeeking = true)}
|
||||
onpointerdown={() => (volumeSeeking = true)}
|
||||
onclick={seekVolume}
|
||||
style="--primary-color:{barPrimaryColor}; --secondary-color:{barSecondaryColor}"
|
||||
class="volume-progress"
|
||||
@@ -448,6 +455,8 @@
|
||||
margin: auto;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
/* make finger drags seek */
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
progress::-webkit-progress-bar {
|
||||
|
||||
@@ -31,12 +31,16 @@
|
||||
<tbody>
|
||||
<tr><th>Timestamp</th><th>Artist</th><th>Title</th></tr>
|
||||
{#each ctx.current.tracks as track, i}
|
||||
<tr class:current={i == ctx.songIndex}>
|
||||
<tr
|
||||
class:current={i == ctx.songIndex}
|
||||
aria-current={i == ctx.songIndex ? 'true' : undefined}
|
||||
>
|
||||
<td class="timestamp-field"
|
||||
><div class="timestamp-field-flex">
|
||||
{formatTrackTime(track[0])}
|
||||
<button
|
||||
onclick={() => jumpToTrack(track[0])}
|
||||
aria-label="Play from {formatTrackTime(track[0])}"
|
||||
class="material-icons"
|
||||
style="padding: 4px 6px; margin-top: -4px; margin-bottom: -4px; margin-right: -6px"
|
||||
>fast_forward</button
|
||||
@@ -180,6 +184,17 @@
|
||||
border: 0;
|
||||
}
|
||||
|
||||
tr .material-icons:focus-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* touch screens can't hover, so always display */
|
||||
@media (hover: none) {
|
||||
.material-icons {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 575px) {
|
||||
.material-icons {
|
||||
opacity: 0.5;
|
||||
|
||||
Reference in New Issue
Block a user