89 lines
2.4 KiB
TypeScript
89 lines
2.4 KiB
TypeScript
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
|
|
};
|
|
}
|