91 lines
2.5 KiB
TypeScript
91 lines
2.5 KiB
TypeScript
import { useCallback, useRef, useState } from "react"
|
|
|
|
export type ResizeEdge = "left" | "right"
|
|
|
|
interface UseSegmentResizeOptions {
|
|
pixelsPerSecond: number
|
|
onResize: (index: number, edge: ResizeEdge, deltaSec: number) => void
|
|
onResizeEnd: (index: number, edge: ResizeEdge) => void
|
|
}
|
|
|
|
export function useSegmentResize({
|
|
pixelsPerSecond,
|
|
onResize,
|
|
onResizeEnd,
|
|
}: UseSegmentResizeOptions) {
|
|
const [resizingIndex, setResizingIndex] = useState(-1)
|
|
const [resizingEdge, setResizingEdge] = useState<ResizeEdge | null>(null)
|
|
|
|
const startXRef = useRef(0)
|
|
const accDeltaRef = useRef(0)
|
|
const indexRef = useRef(-1)
|
|
const edgeRef = useRef<ResizeEdge>("left")
|
|
const ppsRef = useRef(pixelsPerSecond)
|
|
ppsRef.current = pixelsPerSecond
|
|
|
|
// Use refs for callbacks to avoid stale closures in event listeners
|
|
const onResizeRef = useRef(onResize)
|
|
onResizeRef.current = onResize
|
|
const onResizeEndRef = useRef(onResizeEnd)
|
|
onResizeEndRef.current = onResizeEnd
|
|
|
|
const justResizedRef = useRef(false)
|
|
|
|
// Stable references that never change — safe for addEventListener/removeEventListener
|
|
const moveHandlerRef = useRef<((e: PointerEvent) => void) | null>(null)
|
|
const upHandlerRef = useRef<((e: PointerEvent) => void) | null>(null)
|
|
|
|
if (!moveHandlerRef.current) {
|
|
moveHandlerRef.current = (e: PointerEvent) => {
|
|
const dx = e.clientX - startXRef.current
|
|
const deltaSec = dx / ppsRef.current
|
|
const prevDelta = accDeltaRef.current
|
|
const incrementalDelta = deltaSec - prevDelta
|
|
accDeltaRef.current = deltaSec
|
|
onResizeRef.current(indexRef.current, edgeRef.current, incrementalDelta)
|
|
}
|
|
}
|
|
|
|
if (!upHandlerRef.current) {
|
|
upHandlerRef.current = (e: PointerEvent) => {
|
|
document.removeEventListener("pointermove", moveHandlerRef.current!)
|
|
document.removeEventListener("pointerup", upHandlerRef.current!)
|
|
|
|
onResizeEndRef.current(indexRef.current, edgeRef.current)
|
|
setResizingIndex(-1)
|
|
setResizingEdge(null)
|
|
|
|
justResizedRef.current = true
|
|
setTimeout(() => {
|
|
justResizedRef.current = false
|
|
}, 200)
|
|
}
|
|
}
|
|
|
|
const handlePointerDown = useCallback(
|
|
(e: React.PointerEvent, index: number, edge: ResizeEdge) => {
|
|
e.stopPropagation()
|
|
e.preventDefault()
|
|
|
|
startXRef.current = e.clientX
|
|
accDeltaRef.current = 0
|
|
indexRef.current = index
|
|
edgeRef.current = edge
|
|
|
|
setResizingIndex(index)
|
|
setResizingEdge(edge)
|
|
|
|
document.addEventListener("pointermove", moveHandlerRef.current!)
|
|
document.addEventListener("pointerup", upHandlerRef.current!)
|
|
},
|
|
[],
|
|
)
|
|
|
|
return {
|
|
handlePointerDown,
|
|
resizingIndex,
|
|
resizingEdge,
|
|
justResizedRef,
|
|
}
|
|
}
|