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