diff --git a/frontend/src/lib/components/TranscriptionEditView.svelte b/frontend/src/lib/components/TranscriptionEditView.svelte index edad6189..44a21288 100644 --- a/frontend/src/lib/components/TranscriptionEditView.svelte +++ b/frontend/src/lib/components/TranscriptionEditView.svelte @@ -160,45 +160,63 @@ function handleMoveDown(blockId: string) { reorder(sorted.map((b) => b.id)); } -// ── Drag and drop ──────────────────────────────────────────────────────── +// ── Pointer-based drag and drop ────────────────────────────────────────── let draggedBlockId: string | null = $state(null); +let dropTargetIdx: number | null = $state(null); +let dragOffsetY: number = $state(0); +let dragStartY = 0; +let capturedEl: HTMLElement | null = null; +let listEl: HTMLElement | null = null; -function handleDragStart(e: DragEvent, blockId: string) { - if (!(e.target as HTMLElement).closest('[data-drag-handle]')) { - e.preventDefault(); - return; - } +function handleGripDown(e: PointerEvent, blockId: string) { + if (!(e.target as HTMLElement).closest('[data-drag-handle]')) return; + e.preventDefault(); draggedBlockId = blockId; - e.dataTransfer!.effectAllowed = 'move'; + dragStartY = e.clientY; + dragOffsetY = 0; + capturedEl = (e.target as HTMLElement).closest('[data-block-wrapper]') as HTMLElement; + capturedEl?.setPointerCapture(e.pointerId); } -function handleDragOver(e: DragEvent) { - e.preventDefault(); - e.dataTransfer!.dropEffect = 'move'; -} +function handlePointerMove(e: PointerEvent) { + if (!draggedBlockId || !listEl) return; + dragOffsetY = e.clientY - dragStartY; -function handleDrop(e: DragEvent, targetBlockId: string) { - e.preventDefault(); - if (!draggedBlockId || draggedBlockId === targetBlockId) { - draggedBlockId = null; - return; + const wrappers = Array.from(listEl.querySelectorAll('[data-block-wrapper]')); + const dragIdx = sortedBlocks.findIndex((b) => b.id === draggedBlockId); + let target: number | null = null; + + for (let i = 0; i < wrappers.length; i++) { + const rect = wrappers[i].getBoundingClientRect(); + if (e.clientY < rect.top + rect.height / 2) { + target = i; + break; + } } - const sorted = [...sortedBlocks]; - const fromIdx = sorted.findIndex((b) => b.id === draggedBlockId); - const toIdx = sorted.findIndex((b) => b.id === targetBlockId); - if (fromIdx < 0 || toIdx < 0) { - draggedBlockId = null; - return; - } - const [moved] = sorted.splice(fromIdx, 1); - sorted.splice(toIdx, 0, moved); - draggedBlockId = null; - reorder(sorted.map((b) => b.id)); + if (target === null) target = wrappers.length; + if (target === dragIdx || target === dragIdx + 1) target = null; + dropTargetIdx = target; } -function handleDragEnd() { +function handlePointerUp() { + if (!draggedBlockId) return; + + if (dropTargetIdx !== null) { + const sorted = [...sortedBlocks]; + const fromIdx = sorted.findIndex((b) => b.id === draggedBlockId); + if (fromIdx >= 0) { + const [moved] = sorted.splice(fromIdx, 1); + const insertAt = dropTargetIdx > fromIdx ? dropTargetIdx - 1 : dropTargetIdx; + sorted.splice(insertAt, 0, moved); + reorder(sorted.map((b) => b.id)); + } + } + draggedBlockId = null; + dropTargetIdx = null; + dragOffsetY = 0; + capturedEl = null; } function flushViaBeacon() { @@ -229,17 +247,24 @@ $effect(() => {