refactor(web): converge PlaylistTrackRow drag-reorder onto @neodrag/svelte (B4)

This commit is contained in:
2026-05-08 11:22:23 -04:00
parent 3e34f1f7b3
commit 5a174c9ef0
2 changed files with 40 additions and 26 deletions
+29 -10
View File
@@ -1,25 +1,23 @@
<script lang="ts">
import { GripVertical, X } from 'lucide-svelte';
import { draggable, type DragEventData } from '@neodrag/svelte';
import LikeButton from './LikeButton.svelte';
import TrackMenu from './TrackMenu.svelte';
import type { PlaylistTrack, TrackRef } from '$lib/api/types';
import { offsetToDelta } from './queue-row-math';
let {
row,
isOwner,
onRemove,
onPlay,
onDragStart,
onDragOver,
onDrop
onMove
}: {
row: PlaylistTrack;
isOwner: boolean;
onRemove: (position: number) => void;
onPlay: (position: number) => void;
onDragStart?: (position: number) => void;
onDragOver?: (e: DragEvent, position: number) => void;
onDrop?: (e: DragEvent, position: number) => void;
onMove?: (fromPos: number, toPos: number) => void;
} = $props();
const isUnavailable = $derived(row.track_id === null);
@@ -41,21 +39,42 @@
: null
);
let measuredRowHeight = 56; // sensible default; replaced on dragStart
function handleDragStart(data: DragEventData) {
const rect = data.rootNode.getBoundingClientRect();
if (rect.height > 0) measuredRowHeight = rect.height;
}
function handleDragEnd(data: DragEventData) {
const delta = offsetToDelta(data.offsetY, measuredRowHeight);
if (delta === 0) return;
onMove?.(row.position, row.position + delta);
}
function format(sec: number): string {
const m = Math.floor(sec / 60);
const s = sec % 60;
return `${m}:${s.toString().padStart(2, '0')}`;
}
// Drag is gated on owner+available. When neither, the action runs but
// the dragEnd handler skips by returning before onMove fires; visually
// the user never sees the row move.
const canDrag = $derived(isOwner && !isUnavailable);
</script>
<div
role="listitem"
class="flex items-center gap-3 px-3 py-2 text-sm transition-colors hover:bg-surface-hover
{isUnavailable ? 'text-text-muted' : 'text-text-primary'}"
draggable={isOwner && !isUnavailable}
ondragstart={() => onDragStart?.(row.position)}
ondragover={(e) => { e.preventDefault(); onDragOver?.(e, row.position); }}
ondrop={(e) => { e.preventDefault(); onDrop?.(e, row.position); }}
use:draggable={{
axis: 'y',
bounds: 'parent',
disabled: !canDrag,
onDragStart: handleDragStart,
onDragEnd: handleDragEnd
}}
>
{#if isOwner}
<span