diff --git a/web/src/lib/components/PlaylistTrackRow.svelte b/web/src/lib/components/PlaylistTrackRow.svelte
index e14566ab..275aff1b 100644
--- a/web/src/lib/components/PlaylistTrackRow.svelte
+++ b/web/src/lib/components/PlaylistTrackRow.svelte
@@ -1,25 +1,23 @@
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}
(null);
-
- async function onDrop(toPos: number) {
- if (dragFromPos === null || !playlistQuery?.data) {
- return;
- }
- if (dragFromPos === toPos) {
- dragFromPos = null;
- return;
- }
- const cur = playlistQuery.data.tracks.map((t) => t.position);
- const moved = cur.splice(dragFromPos, 1)[0];
- cur.splice(toPos, 0, moved);
- dragFromPos = null;
+ async function onMove(fromPos: number, toPos: number) {
+ if (!playlistQuery?.data) return;
+ const tracks = playlistQuery.data.tracks;
+ // Clamp toPos so a drag past the list edges resolves cleanly to the
+ // first/last slot rather than producing an out-of-range index.
+ const clamped = Math.max(0, Math.min(tracks.length - 1, toPos));
+ if (fromPos === clamped) return;
+ const cur = tracks.map((t) => t.position);
+ const moved = cur.splice(fromPos, 1)[0];
+ cur.splice(clamped, 0, moved);
try {
await reorderPlaylist(id, cur);
await queryClient.invalidateQueries({ queryKey: qk.playlist(id) });
@@ -268,8 +264,7 @@
isOwner={isOwner ?? false}
onRemove={onRemove}
onPlay={onPlay}
- onDragStart={(p) => (dragFromPos = p)}
- onDrop={(_, p) => onDrop(p)}
+ onMove={onMove}
/>
{/each}
{/if}