feat(quick-3): add playlist-level multi-select state and selection handling
- Add selectedPlaylists Set and lastSelectedPlaylistIndex for multi-select state - Replace handleToggle with handlePlaylistHeaderClick supporting Ctrl/Shift+Click - Clear playlist selection when entering track selection scope - Clear playlist selection on outside clicks - Add .playlist-header.selected CSS with selection background color - Wire header click to new handler with selected class in template
This commit is contained in:
@@ -187,6 +187,12 @@ export class PlaylistView
|
||||
@state() private renamingPlaylistIndex = -1;
|
||||
@state() private renameValue = '';
|
||||
|
||||
/** Indices of playlists selected via Ctrl/Shift+Click. */
|
||||
@state() private selectedPlaylists: Set<number> = new Set();
|
||||
|
||||
/** Anchor index for Shift+Click range selection on playlists. */
|
||||
private lastSelectedPlaylistIndex: number | null = null;
|
||||
|
||||
/** Index of the playlist currently hovered during a drag. */
|
||||
@state() private dragOverPlaylistIndex = -1;
|
||||
|
||||
@@ -248,10 +254,21 @@ export class PlaylistView
|
||||
el.classList.contains('track-item') &&
|
||||
this.shadowRoot?.contains(el),
|
||||
);
|
||||
const isPlaylistHeaderClick = path.some(
|
||||
(el) =>
|
||||
el instanceof HTMLElement &&
|
||||
el.classList.contains('playlist-header') &&
|
||||
this.shadowRoot?.contains(el),
|
||||
);
|
||||
|
||||
if (!isTrackClick) {
|
||||
this.selection.clear();
|
||||
}
|
||||
|
||||
if (!isPlaylistHeaderClick && !isTrackClick) {
|
||||
this.selectedPlaylists = new Set();
|
||||
this.lastSelectedPlaylistIndex = null;
|
||||
}
|
||||
};
|
||||
|
||||
// =================================================================
|
||||
@@ -329,6 +346,12 @@ export class PlaylistView
|
||||
private ensureSelectionScope(
|
||||
playlistIndex: number,
|
||||
): void {
|
||||
// Clear playlist-level selection when entering track selection
|
||||
if (this.selectedPlaylists.size > 0) {
|
||||
this.selectedPlaylists = new Set();
|
||||
this.lastSelectedPlaylistIndex = null;
|
||||
}
|
||||
|
||||
if (
|
||||
this.activePlaylistIndex !== playlistIndex
|
||||
) {
|
||||
@@ -496,6 +519,10 @@ export class PlaylistView
|
||||
background-color: var(--yj-hover-overlay, rgba(255, 255, 255, 0.05));
|
||||
}
|
||||
|
||||
.playlist-header.selected {
|
||||
background-color: var(--yj-selection-bg, rgba(100, 160, 255, 0.15));
|
||||
}
|
||||
|
||||
.playlist-item.drag-over > .playlist-header {
|
||||
background-color: var(--yj-accent-bg-strong, rgba(255, 212, 59, 0.15));
|
||||
outline: 1px dashed var(--yj-accent, #ffd43b);
|
||||
@@ -1022,11 +1049,56 @@ export class PlaylistView
|
||||
}
|
||||
}
|
||||
|
||||
private handleToggle = (index: number) => {
|
||||
private handlePlaylistHeaderClick = (
|
||||
e: MouseEvent,
|
||||
index: number,
|
||||
) => {
|
||||
const entry = this.entries[index];
|
||||
|
||||
if (!entry) return;
|
||||
|
||||
const isCtrl = e.ctrlKey || e.metaKey;
|
||||
const isShift = e.shiftKey;
|
||||
|
||||
if (isCtrl) {
|
||||
// Ctrl/Cmd+Click: toggle playlist in selection
|
||||
const next = new Set(this.selectedPlaylists);
|
||||
|
||||
if (next.has(index)) {
|
||||
next.delete(index);
|
||||
} else {
|
||||
next.add(index);
|
||||
}
|
||||
|
||||
this.selectedPlaylists = next;
|
||||
this.lastSelectedPlaylistIndex = index;
|
||||
// Clear track-level selection
|
||||
this.selection.clear();
|
||||
this.activePlaylistIndex = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isShift && this.lastSelectedPlaylistIndex !== null) {
|
||||
// Shift+Click: range-select playlists
|
||||
const start = Math.min(this.lastSelectedPlaylistIndex, index);
|
||||
const end = Math.max(this.lastSelectedPlaylistIndex, index);
|
||||
const next = new Set(this.selectedPlaylists);
|
||||
|
||||
for (let i = start; i <= end; i++) {
|
||||
next.add(i);
|
||||
}
|
||||
|
||||
this.selectedPlaylists = next;
|
||||
// Clear track-level selection
|
||||
this.selection.clear();
|
||||
this.activePlaylistIndex = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
// Plain click: clear playlist selection, toggle expand/collapse
|
||||
this.selectedPlaylists = new Set();
|
||||
this.lastSelectedPlaylistIndex = null;
|
||||
|
||||
// If collapsing the active playlist, clear selection.
|
||||
if (
|
||||
entry.expanded &&
|
||||
@@ -2409,9 +2481,9 @@ export class PlaylistView
|
||||
this.onPlaylistDrop(e, index)}
|
||||
>
|
||||
<div
|
||||
class="playlist-header"
|
||||
@click=${() =>
|
||||
this.handleToggle(index)}
|
||||
class="playlist-header ${this.selectedPlaylists.has(index) ? 'selected' : ''}"
|
||||
@click=${(e: MouseEvent) =>
|
||||
this.handlePlaylistHeaderClick(e, index)}
|
||||
@contextmenu=${(e: MouseEvent) =>
|
||||
this.handlePlaylistContextMenu(
|
||||
e,
|
||||
|
||||
Reference in New Issue
Block a user