diff --git a/src/components/TabBar.tsx b/src/components/TabBar.tsx index 73ef729a..f0a30826 100644 --- a/src/components/TabBar.tsx +++ b/src/components/TabBar.tsx @@ -100,16 +100,25 @@ function computeInsertIndex(e: React.DragEvent, index: number): function useTabDrag(onReorderTabs?: (from: number, to: number) => void) { const [dragIndex, setDragIndex] = useState(null) const [dropIndex, setDropIndex] = useState(null) + // Refs mirror state so event handlers always read the latest values, + // avoiding stale closures when dragover and drop fire in the same frame. + const dragIndexRef = useRef(null) + const dropIndexRef = useRef(null) const dragNodeRef = useRef(null) + const onReorderRef = useRef(onReorderTabs) + useEffect(() => { onReorderRef.current = onReorderTabs }) const resetDrag = useCallback(() => { if (dragNodeRef.current) dragNodeRef.current.style.opacity = '' dragNodeRef.current = null + dragIndexRef.current = null + dropIndexRef.current = null setDragIndex(null) setDropIndex(null) }, []) const handleDragStart = useCallback((e: React.DragEvent, index: number) => { + dragIndexRef.current = index setDragIndex(index) e.dataTransfer.effectAllowed = 'move' e.dataTransfer.setData('text/plain', String(index)) @@ -122,20 +131,32 @@ function useTabDrag(onReorderTabs?: (from: number, to: number) => void) { const handleDragOver = useCallback((e: React.DragEvent, index: number) => { e.preventDefault() e.dataTransfer.dropEffect = 'move' - if (dragIndex === null || dragIndex === index) { setDropIndex(null); return } - setDropIndex(computeInsertIndex(e, index)) - }, [dragIndex]) + const currentDrag = dragIndexRef.current + if (currentDrag === null || currentDrag === index) { + dropIndexRef.current = null + setDropIndex(null) + return + } + const idx = computeInsertIndex(e, index) + dropIndexRef.current = idx + setDropIndex(idx) + }, []) const handleDrop = useCallback((e: React.DragEvent) => { e.preventDefault() - const toIndex = computeDropTarget(dragIndex, dropIndex) - if (toIndex !== null && onReorderTabs) onReorderTabs(dragIndex!, toIndex) + const toIndex = computeDropTarget(dragIndexRef.current, dropIndexRef.current) + if (toIndex !== null && onReorderRef.current) { + onReorderRef.current(dragIndexRef.current!, toIndex) + } resetDrag() - }, [dragIndex, dropIndex, onReorderTabs, resetDrag]) + }, [resetDrag]) const handleBarDragLeave = useCallback((e: React.DragEvent) => { const related = e.relatedTarget as HTMLElement | null - if (!e.currentTarget.contains(related)) setDropIndex(null) + if (!e.currentTarget.contains(related)) { + dropIndexRef.current = null + setDropIndex(null) + } }, []) return { dragIndex, dropIndex, handleDragStart, handleDragEnd: resetDrag, handleDragOver, handleDrop, handleBarDragLeave }