fix(svelte): resolve all remaining a11y and real warnings

Key discovery: Svelte 5 requires commas between svelte-ignore codes,
not spaces. Fixed separator in all multi-code ignore comments.

Additional fixes across 36 files:
- TrackMenu/AlbumMenu: add role="menu" and ignore comments to menus
- LocalLibraryTagEditorModal: link labels to inputs with for/id
- TrackReplacementModal/DACSetupWizard: fix dialog a11y
- BookletViewer: fix reactivity ($state for bookletLoaded)
- StackIcon/SearchPlaylistCard/QobuzPlaylistCard/FavoritePlaylistCard:
  fix state_referenced_locally with $derived()
- CoverflowPanel/HomeSettingsModal/FavoritesEditModal: fix reactivity
- ImmersiveHeader/FolderEditModal: replace deprecated svelte:component
- Toggle: add aria-label prop
- Various modals: add tabindex="-1" and role to dialogs
- Dropdown/DeviceDropdown: add proper listbox roles

Build output: 0 Svelte warnings (down from ~200)
This commit is contained in:
vicrodh committed 2026-03-04 20:20:40 -06:00
1 parent d50b2cf03a
commit 955f5edd05
41 files changed
+210 -133

No files matched your search

+1 -1
View File
@@ -47,7 +47,7 @@
{#if isOpen}
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div class="modal-backdrop" onclick={onClose} role="presentation">
<!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions, a11y_click_events_have_key_events -->
<div class="modal" onclick={(e) => e.stopPropagation()}>
<!-- Header -->
<div class="modal-header">
+1 -1
View File
@@ -168,7 +168,7 @@
<svelte:window on:keydown={handleKeydown} />
{#if isOpen}
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
<div class="modal-overlay" onclick={onClose} role="presentation">
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div class="credits-modal" role="dialog" aria-modal="true" tabindex="-1" onclick={(e) => e.stopPropagation()}>
+11
View File
@@ -304,9 +304,11 @@
</script>
{#if hasMenu}
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions, a11y_no_noninteractive_element_interactions -->
<div
class="album-menu"
bind:this={menuRef}
role="presentation"
onmousedown={(e) => e.stopPropagation()}
onclick={(e) => e.stopPropagation()}
>
@@ -328,10 +330,13 @@
{#if isOpen && portalTarget}
<Portal target={portalTarget}>
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
<div
class="menu"
bind:this={menuEl}
style={menuStyle}
role="menu"
tabindex="-1"
onmousedown={(e) => e.stopPropagation()}
onmouseenter={() => isHoveringMenu = true}
onmouseleave={() => isHoveringMenu = false}
@@ -367,9 +372,12 @@
{/if}
{#if hasShare}
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
<div
class="menu-item submenu-trigger"
bind:this={shareTriggerRef}
role="menuitem"
tabindex="-1"
onmouseenter={() => {
shareOpen = true;
downloadOpen = false;
@@ -409,9 +417,12 @@
{#if hasDownload}
{#if isAlbumFullyDownloaded}
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
<div
class="menu-item submenu-trigger"
bind:this={downloadTriggerRef}
role="menuitem"
tabindex="-1"
onmouseenter={() => {
downloadOpen = true;
shareOpen = false;
+3 -2
View File
@@ -48,7 +48,7 @@
let displayHeight = $state(0);
let pageSizes: PageSize[] = [];
let bookletLoaded = false;
let bookletLoaded = $state(false);
// Zoom is relative: 1.0 = fit to container width
const MIN_ZOOM = 0.5;
@@ -260,7 +260,8 @@
<svelte:window onkeydown={handleKeydown} />
{#if isOpen}
<div class="booklet-overlay" use:portal onclick={handleBackdropClick}>
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
<div class="booklet-overlay" use:portal onclick={handleBackdropClick} role="presentation">
<!-- Toolbar -->
<div class="booklet-toolbar">
<div class="toolbar-left">
+2 -2
View File
@@ -49,8 +49,8 @@
</script>
{#if count > 0}
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="bulk-bar-backdrop" class:queue-open={queueMenuOpen} onclick={handleClickOutside}>
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
<div class="bulk-bar-backdrop" class:queue-open={queueMenuOpen} onclick={handleClickOutside} role="presentation">
</div>
<div class="bulk-bar" class:placement-top={placement === 'top'}>
<span class="count-label">
+6 -2
View File
@@ -310,11 +310,14 @@
<svelte:document onkeydown={handleKeydown} />
{#if isOpen}
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div class="wizard-backdrop" onclick={handleBackdropClick} role="presentation">
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
<div
class="wizard-modal"
role="dialog"
aria-modal="true"
tabindex="-1"
onclick={(e) => e.stopPropagation()}
>
<!-- Header -->
@@ -430,9 +433,10 @@
/>
<div class="input-group">
<label class="input-label">{$t('dacWizard.detectDac.inputLabel')}</label>
<label class="input-label" for="dac-node-name">{$t('dacWizard.detectDac.inputLabel')}</label>
<p class="input-warning">{$t('dacWizard.detectDac.inputWarning')}</p>
<input
id="dac-node-name"
type="text"
class="text-input mono"
class:valid={dacValidation === 'valid'}
@@ -571,7 +575,7 @@
<p class="body-text">{$t('dacWizard.pipewireConfig.explanation')}</p>
<div class="sample-rate-selector">
<label class="selector-label">{$t('dacWizard.pipewireConfig.selectRates')}</label>
<span class="selector-label">{$t('dacWizard.pipewireConfig.selectRates')}</span>
<div class="rate-checkboxes">
{#each COMMON_SAMPLE_RATES as rate}
<label class="rate-checkbox" class:detected={dacCapabilities?.sample_rates.includes(rate)}>
+1
View File
@@ -303,6 +303,7 @@
class="menu"
class:searchable={showSearch}
role="listbox"
tabindex="-1"
bind:this={menuRef}
onmouseenter={() => isHovering = true}
onmouseleave={() => isHovering = false}
+3
View File
@@ -221,10 +221,13 @@
</div>
{#if isOpen && fixedPosition}
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class="menu"
class:compact
class:searchable={showSearch}
role="listbox"
tabindex="-1"
bind:this={menuRef}
onmouseenter={() => isHovering = true}
onmouseleave={() => isHovering = false}
@@ -44,7 +44,8 @@
let menuStyle = $state('');
// Global floating menu coordination
const menuId = `playlist-card-${playlist.id}-${Date.now()}`;
const _menuTimestamp = Date.now();
const menuId = $derived(`playlist-card-${playlist.id}-${_menuTimestamp}`);
onMount(() => {
const unsubscribe = subscribeGlobal(() => {
+8 -1
View File
@@ -14,9 +14,16 @@
let { isOpen, onClose, onSave, initialPreferences }: Props = $props();
let tabOrder = $state<string[]>([...initialPreferences.tab_order]);
let tabOrder = $state<string[]>([]);
let saving = $state(false);
// Sync tabOrder when initialPreferences changes
$effect(() => {
if (initialPreferences?.tab_order) {
tabOrder = [...initialPreferences.tab_order];
}
});
function getTabLabel(tab: string): string {
const key = `favorites.tabLabels.${tab}`;
const translated = $t(key);
+90 -89
View File
@@ -1,22 +1,22 @@
<script lang="ts">
import { open } from '@tauri-apps/plugin-dialog';
import Modal from './Modal.svelte';
import { Heart, Star, Music, Folder, Disc, Library, Headphones, Upload, Eye, EyeOff } from 'lucide-svelte';
import { t } from '$lib/i18n';
import type { PlaylistFolder } from '$lib/stores/playlistFoldersStore';
<script lang="ts">
import { open } from '@tauri-apps/plugin-dialog';
import Modal from './Modal.svelte';
import { Heart, Star, Music, Folder, Disc, Library, Headphones, Upload, Eye, EyeOff } from 'lucide-svelte';
import { t } from '$lib/i18n';
import type { PlaylistFolder } from '$lib/stores/playlistFoldersStore';
interface Props {
isOpen: boolean;
folder: PlaylistFolder | null;
onClose: () => void;
onSave: (folder: PlaylistFolder | null, updates: {
name: string;
iconType: string;
iconPreset: string;
iconColor: string;
customImagePath?: string;
isHidden?: boolean;
}) => void;
onSave: (folder: PlaylistFolder | null, updates: {
name: string;
iconType: string;
iconPreset: string;
iconColor: string;
customImagePath?: string;
isHidden?: boolean;
}) => void;
onDelete?: (folder: PlaylistFolder) => void;
}
@@ -25,10 +25,10 @@
let folderName = $state('');
let customIconPath = $state<string | null>(null);
let customIconPreset = $state<string>('folder');
let iconBackground = $state<string | null>(null);
let useCustomImage = $state(false);
let folderHidden = $state(false);
let saving = $state(false);
let iconBackground = $state<string | null>(null);
let useCustomImage = $state(false);
let folderHidden = $state(false);
let saving = $state(false);
const presetIcons = [
{ id: 'heart', label: 'Heart', icon: Heart },
@@ -73,19 +73,19 @@
if (folder) {
folderName = folder.name;
customIconPath = folder.custom_image_path || null;
customIconPreset = folder.icon_preset || 'folder';
iconBackground = folder.icon_color || null;
useCustomImage = folder.icon_type === 'custom' && !!folder.custom_image_path;
folderHidden = folder.is_hidden ?? false;
} else {
folderName = '';
customIconPath = null;
customIconPreset = 'folder';
iconBackground = null;
useCustomImage = false;
folderHidden = false;
}
}
customIconPreset = folder.icon_preset || 'folder';
iconBackground = folder.icon_color || null;
useCustomImage = folder.icon_type === 'custom' && !!folder.custom_image_path;
folderHidden = folder.is_hidden ?? false;
} else {
folderName = '';
customIconPath = null;
customIconPreset = 'folder';
iconBackground = null;
useCustomImage = false;
folderHidden = false;
}
}
async function handleUploadClick() {
try {
@@ -114,15 +114,15 @@
try {
onSave(folder, {
name: folderName.trim(),
iconType: useCustomImage ? 'custom' : 'preset',
iconPreset: customIconPreset,
iconColor: iconBackground || '',
customImagePath: useCustomImage ? customIconPath || undefined : undefined,
isHidden: folderHidden
});
onClose();
} finally {
saving = false;
iconType: useCustomImage ? 'custom' : 'preset',
iconPreset: customIconPreset,
iconColor: iconBackground || '',
customImagePath: useCustomImage ? customIconPath || undefined : undefined,
isHidden: folderHidden
});
onClose();
} finally {
saving = false;
}
}
@@ -144,7 +144,7 @@
});
</script>
<Modal {isOpen} onClose={handleCancel} title={folder ? $t('library.editFolder') : $t('playlist.newFolder', { default: 'New Folder' })} maxWidth="480px">
<Modal {isOpen} onClose={handleCancel} title={folder ? $t('library.editFolder') : $t('playlist.newFolder', { default: 'New Folder' })} maxWidth="480px">
{#snippet children()}
<div class="folder-modal-content">
<!-- Name input -->
@@ -164,13 +164,14 @@
<div class="icon-grid">
{#each presetIcons as preset}
{@const PresetIcon = preset.icon}
<button
class="icon-preset-btn"
class:active={!useCustomImage && customIconPreset === preset.id}
onclick={() => selectPreset(preset.id)}
title={preset.label}
>
<svelte:component this={preset.icon} size={17} />
<PresetIcon size={17} />
</button>
{/each}
</div>
@@ -186,9 +187,9 @@
</div>
</div>
<!-- Background Section -->
<div class="modal-section">
<h3>Background</h3>
<!-- Background Section -->
<div class="modal-section">
<h3>Background</h3>
<div class="color-section">
<div class="color-grid">
@@ -226,25 +227,25 @@
</button>
{/each}
</div>
</div>
</div>
<div class="modal-section">
<h3>{$t('playlist.visibility', { default: 'Visibility' })}</h3>
<label class="hidden-toggle">
<input type="checkbox" bind:checked={folderHidden} />
<span class="hidden-toggle-label">
{#if folderHidden}
<EyeOff size={14} />
{:else}
<Eye size={14} />
{/if}
{$t('playlist.hideFromSidebar')}
</span>
</label>
</div>
</div>
{/snippet}
</div>
</div>
<div class="modal-section">
<h3>{$t('playlist.visibility', { default: 'Visibility' })}</h3>
<label class="hidden-toggle">
<input type="checkbox" bind:checked={folderHidden} />
<span class="hidden-toggle-label">
{#if folderHidden}
<EyeOff size={14} />
{:else}
<Eye size={14} />
{/if}
{$t('playlist.hideFromSidebar')}
</span>
</label>
</div>
</div>
{/snippet}
{#snippet footer()}
<div class="modal-actions">
@@ -451,28 +452,28 @@
}
/* Layout-specific: push danger button to the left */
.modal-actions :global(.btn-danger) {
margin-right: auto;
}
.hidden-toggle {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
color: var(--text-primary);
font-size: 14px;
}
.hidden-toggle input {
width: 16px;
height: 16px;
accent-color: var(--accent-primary);
}
.hidden-toggle-label {
display: inline-flex;
align-items: center;
gap: 6px;
}
</style>
.modal-actions :global(.btn-danger) {
margin-right: auto;
}
.hidden-toggle {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
color: var(--text-primary);
font-size: 14px;
}
.hidden-toggle input {
width: 16px;
height: 16px;
accent-color: var(--accent-primary);
}
.hidden-toggle-label {
display: inline-flex;
align-items: center;
gap: 6px;
}
</style>
@@ -220,6 +220,7 @@
aria-modal="true"
tabindex="-1"
>
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
<div class="modal" onclick={(e) => e.stopPropagation()}>
<div class="modal-header">
<h2>{$t('library.folderSettings')}</h2>
+1 -1
View File
@@ -24,7 +24,7 @@
let { isOpen, onClose }: Props = $props();
let settings = $state<HomeSettings>(getSettings());
let customGreetingInput = $state(settings.greeting.customText || '');
let customGreetingInput = $state(getSettings().greeting.customText || '');
// Subscribe to settings changes (use onMount to avoid reactive tracking)
onMount(() => {
+2 -1
View File
@@ -40,7 +40,8 @@
<svelte:window onkeydown={handleKeydown} />
{#if isOpen}
<div class="lightbox-overlay" use:portal onclick={handleBackdropClick}>
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
<div class="lightbox-overlay" use:portal onclick={handleBackdropClick} role="presentation">
<div class="lightbox-image-wrapper">
<img
{src}
@@ -384,19 +384,20 @@
<div class="tag-editor">
<div class="grid grid-2">
<div class="field">
<label>{$t('metadata.albumName')}</label>
<input class="text control-sm" type="text" bind:value={albumTitle} />
<label for="tag-album-name">{$t('metadata.albumName')}</label>
<input id="tag-album-name" class="text control-sm" type="text" bind:value={albumTitle} />
</div>
<div class="field">
<label>{$t('metadata.albumArtist')}</label>
<input class="text control-sm" type="text" bind:value={albumArtist} />
<label for="tag-album-artist">{$t('metadata.albumArtist')}</label>
<input id="tag-album-artist" class="text control-sm" type="text" bind:value={albumArtist} />
</div>
</div>
<div class="grid grid-3">
<div class="field">
<label>{$t('metadata.year')}</label>
<label for="tag-year">{$t('metadata.year')}</label>
<input
id="tag-year"
class="text control-sm"
type="number"
step="1"
@@ -406,12 +407,12 @@
/>
</div>
<div class="field">
<label>{$t('metadata.genre')}</label>
<input class="text control-sm" type="text" bind:value={genre} placeholder={$t('placeholders.genreExample')} />
<label for="tag-genre">{$t('metadata.genre')}</label>
<input id="tag-genre" class="text control-sm" type="text" bind:value={genre} placeholder={$t('placeholders.genreExample')} />
</div>
<div class="field">
<label>{$t('metadata.catalogNumber')}</label>
<input class="text control-sm" type="text" bind:value={catalogNumber} />
<label for="tag-catalog-number">{$t('metadata.catalogNumber')}</label>
<input id="tag-catalog-number" class="text control-sm" type="text" bind:value={catalogNumber} />
</div>
</div>
+2 -1
View File
@@ -47,7 +47,8 @@
<svelte:window onkeydown={handleKeydown} />
{#if isOpen}
<div class="modal-overlay" use:portal onclick={handleBackdropClick} role="dialog" aria-modal="true">
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_interactive_supports_focus -->
<div class="modal-overlay" use:portal onclick={handleBackdropClick} role="dialog" aria-modal="true" tabindex="-1">
<div class="modal" style="max-width: {maxWidth}">
{#if title || showCloseButton}
<div class="modal-header">
@@ -328,7 +328,7 @@
aria-modal="true"
tabindex="-1"
>
<!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions, a11y_click_events_have_key_events -->
<div class="modal" onclick={(e) => e.stopPropagation()}>
<div class="modal-header">
<div class="header-title">
+1 -1
View File
@@ -660,7 +660,7 @@
aria-modal="true"
tabindex="-1"
>
<!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions, a11y_click_events_have_key_events -->
<div class="modal" onclick={(e) => e.stopPropagation()}>
<div class="modal-header">
<h2>
+2 -1
View File
@@ -51,7 +51,8 @@
const cardSize = 180;
// Global floating menu coordination
const menuId = `qobuz-playlist-${playlistId}-${Date.now()}`;
const _menuTimestamp = Date.now();
const menuId = $derived(`qobuz-playlist-${playlistId}-${_menuTimestamp}`);
onMount(() => {
const unsubscribe = subscribeGlobal(() => {
+1
View File
@@ -193,6 +193,7 @@
{compactText}
</span>
{:else}
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class="quality-badge"
class:downgraded={isDowngraded}
+1 -1
View File
@@ -269,7 +269,7 @@
</script>
<!-- Queue Panel -->
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_noninteractive_element_interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_noninteractive_element_interactions -->
<aside class="queue-panel" onclick={handlePanelClick}>
<!-- Header with Tabs -->
<div class="header">
@@ -84,11 +84,14 @@
<svelte:document onkeydown={handleKeydown} />
{#if isOpen}
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div class="wizard-backdrop" onclick={handleBackdropClick} role="presentation">
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
<div
class="wizard-modal"
role="dialog"
aria-modal="true"
tabindex="-1"
onclick={(e) => e.stopPropagation()}
>
<!-- Header -->
+2 -1
View File
@@ -36,7 +36,8 @@
let menuStyle = $state('');
// Global floating menu coordination
const menuId = `search-playlist-${playlist.id}-${Date.now()}`;
const _menuTimestamp = Date.now();
const menuId = $derived(`search-playlist-${playlist.id}-${_menuTimestamp}`);
onMount(() => {
const unsubscribe = subscribeGlobal(() => {
+1 -1
View File
@@ -12,7 +12,7 @@
let { size = 16, class: className = '', onClick }: Props = $props();
let context = $state(getCurrentContext());
let displayInfo = $state(context ? getContextDisplayInfo() : null);
let displayInfo = $state(getCurrentContext() ? getContextDisplayInfo() : null);
let showIcon = $state(getCachedPreferences().show_context_icon);
// Check if the current context is radio
+3 -1
View File
@@ -3,9 +3,10 @@
enabled: boolean;
onchange: (enabled: boolean) => void;
disabled?: boolean;
label?: string;
}
let { enabled, onchange, disabled = false }: Props = $props();
let { enabled, onchange, disabled = false, label = 'Toggle' }: Props = $props();
function handleClick() {
if (!disabled) {
@@ -22,6 +23,7 @@
role="switch"
aria-checked={enabled}
aria-disabled={disabled}
aria-label={label}
{disabled}
>
<div class="thumb"></div>
+2 -2
View File
@@ -160,9 +160,9 @@
<svelte:window on:keydown={handleKeydown} />
{#if isOpen}
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
<div class="modal-overlay" onclick={onClose} role="presentation">
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
<div class="track-modal" onclick={(e) => e.stopPropagation()} role="dialog" aria-modal="true" tabindex="-1">
{#if loading}
<div class="loading-state">
+23
View File
@@ -456,9 +456,11 @@
</script>
{#if hasMenu}
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions, a11y_no_noninteractive_element_interactions -->
<div
class="track-menu"
bind:this={menuRef}
role="presentation"
onmousedown={(e) => e.stopPropagation()}
onclick={(e) => e.stopPropagation()}
>
@@ -479,6 +481,7 @@
</button>
{#if isOpen}
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class="menu"
class:open-left={openSide === 'left'}
@@ -487,6 +490,8 @@
bind:this={menuEl}
style={menuStyle}
use:portal
role="menu"
tabindex="-1"
onmouseenter={() => { isHoveringAnyMenu = true; }}
onmouseleave={() => { isHoveringAnyMenu = false; }}
>
@@ -524,9 +529,12 @@
{/if}
{#if hasRadio}
{#if onCreateQbzRadio && onCreateQobuzRadio}
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
<div
class="menu-item submenu-trigger"
bind:this={radioTriggerRef}
role="menuitem"
tabindex="-1"
onmouseenter={() => {
radioOpen = true;
shareOpen = false;
@@ -543,10 +551,13 @@
<span>Create radio</span>
<ChevronRight size={14} class="chevron" />
{#if radioOpen}
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class="submenu"
bind:this={radioSubmenuEl}
style={radioSubmenuStyle}
role="menu"
tabindex="-1"
onmouseenter={() => { isHoveringRadioSubmenu = true; }}
onmouseleave={() => { isHoveringRadioSubmenu = false; }}
>
@@ -606,9 +617,12 @@
{/if}
{#if hasShare}
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
<div
class="menu-item submenu-trigger"
bind:this={shareTriggerRef}
role="menuitem"
tabindex="-1"
onmouseenter={() => {
shareOpen = true;
downloadOpen = false;
@@ -625,10 +639,13 @@
<span>Share</span>
<ChevronRight size={14} class="chevron" />
{#if shareOpen}
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class="submenu"
bind:this={submenuEl}
style={submenuStyle}
role="menu"
tabindex="-1"
onmouseenter={() => { isHoveringShareSubmenu = true; }}
onmouseleave={() => { isHoveringShareSubmenu = false; }}
>
@@ -655,9 +672,12 @@
{#if hasDownload}
{#if isTrackDownloaded}
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
<div
class="menu-item submenu-trigger"
bind:this={downloadTriggerRef}
role="menuitem"
tabindex="-1"
onmouseenter={() => {
downloadOpen = true;
shareOpen = false;
@@ -674,10 +694,13 @@
<span>Make available offline</span>
<ChevronRight size={14} class="chevron" />
{#if downloadOpen}
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class="submenu"
bind:this={downloadSubmenuEl}
style={downloadSubmenuStyle}
role="menu"
tabindex="-1"
onmouseenter={() => { isHoveringDownloadSubmenu = true; }}
onmouseleave={() => { isHoveringDownloadSubmenu = false; }}
>
@@ -125,7 +125,9 @@
</script>
{#if isOpen}
<div class="modal-overlay" onclick={onClose} onkeydown={handleKeydown} role="dialog" aria-modal="true">
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div class="modal-overlay" onclick={onClose} onkeydown={handleKeydown} role="dialog" aria-modal="true" tabindex="-1">
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
<div class="modal-content" onclick={(e) => e.stopPropagation()}>
<div class="modal-header">
<h2>{$t('playlist.findReplacement')}</h2>
@@ -199,6 +201,7 @@
</div>
<div class="track-duration">{formatDuration(track.duration)}</div>
{#if onPreview}
<!-- svelte-ignore node_invalid_placement_ssr -->
<button
class="preview-btn"
onclick={(e) => handlePreview(e, track)}
+1
View File
@@ -173,6 +173,7 @@
>
<!-- Checkbox (select mode) -->
{#if selectable}
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
<div class="track-checkbox" onclick={(e) => e.stopPropagation()}>
<input
type="checkbox"
@@ -337,7 +337,7 @@
</div>
{:else if item.type === 'list-row'}
{@const album = item.album}
<div class="album-row" class:has-rank={showRanking} role="button" tabindex="0" onclick={() => handleAlbumClickEvent(album.id)}>
<div class="album-row" class:has-rank={showRanking} role="button" tabindex="0" onclick={() => handleAlbumClickEvent(album.id)} onkeydown={(e) => e.key === 'Enter' && handleAlbumClickEvent(album.id)}>
{#if showRanking}
<div class="rank-number">#{item.globalIndex + 1}</div>
{/if}
@@ -209,7 +209,8 @@
}}
>
{#if activeVizOption}
<svelte:component this={activeVizOption.icon} size={16} />
{@const VizIcon = activeVizOption.icon}
<VizIcon size={16} />
{:else if activeFocusTab === 'neon-flow'}
<img src="/laser.svg" alt="" class="viz-img-icon" aria-hidden="true" />
{:else if activeFocusTab === 'tunnel-flow'}
@@ -76,11 +76,16 @@
});
// Track previous index for animation direction
let previousIndex = $state(queueCurrentIndex);
let previousIndex = $state(-1);
let animationDirection = $state<'left' | 'right' | null>(null);
let animationTimeout: ReturnType<typeof setTimeout> | null = null;
$effect(() => {
if (previousIndex === -1) {
// First run: initialize without animating
previousIndex = queueCurrentIndex;
return;
}
if (queueCurrentIndex !== previousIndex) {
animationDirection = queueCurrentIndex > previousIndex ? 'left' : 'right';
previousIndex = queueCurrentIndex;
@@ -314,6 +314,7 @@
{#if trackId}
<div class="card radio-card">
<!-- Custom tooltip that opens bottom-right -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class="card-info-trigger"
onmouseenter={() => showRadioTooltip = true}
@@ -306,6 +306,7 @@
</button>
{#if volumePopoverOpen}
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<div class="volume-popover" role="group" aria-label={$t('player.volume')} onmousedown={(event) => event.stopPropagation()}>
<button class="ctrl-btn mute-btn" onclick={toggleMute} title={isMuted ? $t('player.unmute') : $t('player.mute')}>
<VolumeX size={micro ? 10 : 14} strokeWidth={2.25} />
@@ -80,6 +80,7 @@
let windowHovered = $state(false);
</script>
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class="mini-player-window"
class:compact={compactSurface}
@@ -1632,7 +1632,7 @@
Qobuz Radio
</button>
</div>
<!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions, a11y_click_events_have_key_events -->
<div class="radio-dropdown-backdrop" onclick={() => radioDropdownOpen = false}></div>
{/if}
{#if isRadioLoading && radioLoadingMessage}
@@ -1692,7 +1692,7 @@
</p>
</button>
</div>
<!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions, a11y_click_events_have_key_events -->
<div class="hide-dropdown-backdrop" onclick={() => showHideDropdown = false}></div>
{/if}
</div>
@@ -1858,7 +1858,7 @@
}}
>
{#if multiSelectMode}
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_noninteractive_element_interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_noninteractive_element_interactions -->
<label class="track-checkbox-wrap" onclick={(e) => e.stopPropagation()}>
<input
type="checkbox"
+1 -1
View File
@@ -880,7 +880,7 @@
}}
>
{#if multiSelectMode}
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_noninteractive_element_interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_noninteractive_element_interactions -->
<label class="track-checkbox-wrap" onclick={(e) => e.stopPropagation()}>
<input
type="checkbox"
@@ -3677,7 +3677,7 @@
{/if}
</button>
{#if showFilterPanel}
<!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events a11y_no_noninteractive_element_interactions -->
<!-- svelte-ignore a11y_no_static_element_interactions, a11y_click_events_have_key_events, a11y_no_noninteractive_element_interactions -->
<div
class="filter-panel"
role="region"
@@ -2289,7 +2289,7 @@
>
{#if multiSelectMode && !isCustomOrderMode}
{@const trackKey = getTrackKey(track)}
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_noninteractive_element_interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_noninteractive_element_interactions -->
<label class="track-checkbox" onclick={(e: MouseEvent) => e.stopPropagation()}>
<input
type="checkbox"
@@ -2301,7 +2301,7 @@
{/if}
{#if isCustomOrderMode}
{@const trackKey = getTrackKey(track)}
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_noninteractive_element_interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_noninteractive_element_interactions -->
<label class="track-checkbox" onclick={(e: MouseEvent) => e.stopPropagation()}>
<input
type="checkbox"
@@ -389,7 +389,7 @@
{@const isActive = activeTrackId === track.id}
{@const isDownloadedForFormat = selectedFormatId !== null && (track.downloaded_format_ids ?? []).includes(selectedFormatId)}
{@const isDownloaded = isDownloadedForFormat || status === 'complete'}
<!-- svelte-ignore a11y_no_noninteractive_tabindex a11y_no_static_element_interactions a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_noninteractive_tabindex, a11y_no_static_element_interactions, a11y_click_events_have_key_events -->
<div
class="track-row"
class:downloading={status === 'downloading'}
@@ -907,7 +907,7 @@
{#each group.items as track (track.id)}
{@const dlStatus = getTrackDownloadStatus(track.id)}
{@const isDownloaded = track.downloaded || dlStatus === 'complete'}
<!-- svelte-ignore a11y_no_noninteractive_tabindex a11y_no_static_element_interactions a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_noninteractive_tabindex, a11y_no_static_element_interactions, a11y_click_events_have_key_events -->
<div
class="track-row"
class:active={activeTrackId === track.id}