refactor(transcription): extract useBlockAutoSave and useBlockDragDrop from TranscriptionEditView (#199)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
88
frontend/src/lib/hooks/useBlockDragDrop.svelte.ts
Normal file
88
frontend/src/lib/hooks/useBlockDragDrop.svelte.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import type { TranscriptionBlockData } from '$lib/types';
|
||||
|
||||
type Options = {
|
||||
getSortedBlocks: () => TranscriptionBlockData[];
|
||||
onReorder: (blockIds: string[]) => void;
|
||||
};
|
||||
|
||||
export function createBlockDragDrop({ getSortedBlocks, onReorder }: Options) {
|
||||
let draggedBlockId = $state<string | null>(null);
|
||||
let dropTargetIdx = $state<number | null>(null);
|
||||
let dragOffsetY = $state(0);
|
||||
|
||||
// Internal mutable refs — not reactive
|
||||
let dragStartY = 0;
|
||||
let capturedEl: HTMLElement | null = null;
|
||||
let listEl: HTMLElement | null = null;
|
||||
|
||||
function setListElement(el: HTMLElement | null): void {
|
||||
listEl = el;
|
||||
}
|
||||
|
||||
function handleGripDown(e: PointerEvent, blockId: string): void {
|
||||
if (!(e.target as HTMLElement).closest('[data-drag-handle]')) return;
|
||||
e.preventDefault();
|
||||
draggedBlockId = blockId;
|
||||
dragStartY = e.clientY;
|
||||
dragOffsetY = 0;
|
||||
capturedEl = (e.target as HTMLElement).closest('[data-block-wrapper]') as HTMLElement;
|
||||
capturedEl?.setPointerCapture(e.pointerId);
|
||||
}
|
||||
|
||||
function handlePointerMove(e: PointerEvent): void {
|
||||
if (!draggedBlockId || !listEl) return;
|
||||
dragOffsetY = e.clientY - dragStartY;
|
||||
|
||||
const sortedBlocks = getSortedBlocks();
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (target === null) target = wrappers.length;
|
||||
if (target === dragIdx || target === dragIdx + 1) target = null;
|
||||
dropTargetIdx = target;
|
||||
}
|
||||
|
||||
function handlePointerUp(): void {
|
||||
if (!draggedBlockId) return;
|
||||
|
||||
if (dropTargetIdx !== null) {
|
||||
const sorted = [...getSortedBlocks()];
|
||||
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);
|
||||
onReorder(sorted.map((b) => b.id));
|
||||
}
|
||||
}
|
||||
|
||||
draggedBlockId = null;
|
||||
dropTargetIdx = null;
|
||||
dragOffsetY = 0;
|
||||
capturedEl = null;
|
||||
}
|
||||
|
||||
return {
|
||||
get draggedBlockId() {
|
||||
return draggedBlockId;
|
||||
},
|
||||
get dropTargetIdx() {
|
||||
return dropTargetIdx;
|
||||
},
|
||||
get dragOffsetY() {
|
||||
return dragOffsetY;
|
||||
},
|
||||
setListElement,
|
||||
handleGripDown,
|
||||
handlePointerMove,
|
||||
handlePointerUp
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user