/** Distance mini (px) avant de considérer un geste comme drag vs clic. */ export const AGENDA_EVENT_DRAG_THRESHOLD_PX = 4 export function pointerDragExceeded( startX: number, startY: number, x: number, y: number, threshold = AGENDA_EVENT_DRAG_THRESHOLD_PX, ): boolean { return Math.hypot(x - startX, y - startY) >= threshold } /** Écouteurs window pour drag libre — évite perte capture si nœud source re-render. */ export function bindAgendaEventDragSession({ pointerId, startX, startY, onMove, onFinish, }: { pointerId: number startX: number startY: number onMove: (clientX: number, clientY: number) => void onFinish: (clientX: number, clientY: number, didDrag: boolean) => void }): () => void { let didDrag = false const onPointerMove = (e: PointerEvent) => { if (e.pointerId !== pointerId) return if (!didDrag && pointerDragExceeded(startX, startY, e.clientX, e.clientY)) { didDrag = true } if (didDrag) { onMove(e.clientX, e.clientY) } } const finish = (e: PointerEvent) => { if (e.pointerId !== pointerId) return const dragged = didDrag || pointerDragExceeded(startX, startY, e.clientX, e.clientY) onFinish(e.clientX, e.clientY, dragged) cleanup() } const cleanup = () => { window.removeEventListener("pointermove", onPointerMove) window.removeEventListener("pointerup", finish) window.removeEventListener("pointercancel", finish) } window.addEventListener("pointermove", onPointerMove) window.addEventListener("pointerup", finish) window.addEventListener("pointercancel", finish) return cleanup }