import { useCallback, useEffect, useState } from "react"; type DragInfo = { startX: number; startY: number; top: number; left: number; width: number; height: number; }; type Position = { x: number; y: number; }; interface DragProps { ref: React.RefObject; position: Position; calculateFor?: "topLeft" | "bottomRight"; } export const useDrag = ({ ref, position, calculateFor = "topLeft", }: DragProps) => { const [dragInfo, setDragInfo] = useState(); const [finalPosition, setFinalPosition] = useState({ x: position.x, y: position.y, }); const [isDragging, setIsDragging] = useState(false); const updateFinalPosition = useCallback( (width: number, height: number, x: number, y: number) => { if (calculateFor === "bottomRight") { setFinalPosition({ x: Math.max( Math.min( window.innerWidth - width, window.innerWidth - (x + width) ), 0 ), y: Math.max( Math.min( window.innerHeight - height, window.innerHeight - (y + height) ), 0 ), }); return; } setFinalPosition({ x: Math.min(Math.max(0, x), window.innerWidth - width), y: Math.min(Math.max(0, y), window.innerHeight - height), }); }, [calculateFor] ); const handleMouseUp = (event: MouseEvent) => { event.preventDefault(); setIsDragging(false); }; const handleMouseDown = (event: MouseEvent) => { event.preventDefault(); const { clientX, clientY } = event; const { current: draggableElement } = ref; if (!draggableElement) { return; } const { top, left, width, height } = draggableElement.getBoundingClientRect(); setIsDragging(true); setDragInfo({ startX: clientX, startY: clientY, top, left, width, height, }); }; const handleMouseMove = useCallback( (event: MouseEvent) => { const { current: draggableElement } = ref; if (!isDragging || !draggableElement || !dragInfo) return; event.preventDefault(); const { clientX, clientY } = event; const position = { x: dragInfo.startX - clientX, y: dragInfo.startY - clientY, }; const { top, left, width, height } = dragInfo; updateFinalPosition(width, height, left - position.x, top - position.y); }, [isDragging, dragInfo, ref, updateFinalPosition] ); const recalculate = (width: number, height: number) => { const { current: draggableElement } = ref; if (!draggableElement) return; const { top, left, width: boundingWidth, height: boundingHeight, } = draggableElement.getBoundingClientRect(); updateFinalPosition( width ?? boundingWidth, height ?? boundingHeight, left, top ); }; useEffect(() => { document.addEventListener("mousemove", handleMouseMove); document.addEventListener("mouseup", handleMouseUp); return () => { document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseup", handleMouseUp); }; }, [handleMouseMove]); return { position: finalPosition, handleMouseDown, recalculate, }; }; /** * How to use? * Detail to see: * https://stackblitz-starters-51mnfq.stackblitz.io */