UNPKG

@adaptabletools/adaptable

Version:

Powerful AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements

113 lines (112 loc) 4.63 kB
import { useRef, useCallback, useEffect } from 'react'; function clamp(num, min, max) { return num <= min ? min : num >= max ? max : num; } const getTranslationBounds = (targetRect, boundingRect) => { const left = boundingRect.x - targetRect.x; const right = left + boundingRect.width - targetRect.width; const top = boundingRect.y - targetRect.y; const bottom = top + boundingRect.height - targetRect.height; return { left, right, top, bottom }; }; const applyEdgeOffsetToBoundingRect = (boundingRect, edgeOffset) => { if (!edgeOffset) { return boundingRect; } return new DOMRect(boundingRect.x + edgeOffset, boundingRect.y + edgeOffset, boundingRect.width - 2 * edgeOffset, boundingRect.height - 2 * edgeOffset); }; export default function useDraggable({ onMove, onDrop, getBoundingRect = () => document.body.getBoundingClientRect(), handleSelector, edgeOffset = 0, }) { const startRef = useRef(null); const handleRef = useRef(null); const targetRef = useRef(null); const onMoveRef = useRef(onMove); const onDropRef = useRef(onDrop); const getBoundingRectRef = useRef(getBoundingRect); const edgeOffsetRef = useRef(edgeOffset); onMoveRef.current = onMove; onDropRef.current = onDrop; getBoundingRectRef.current = getBoundingRect; edgeOffsetRef.current = edgeOffset; const getEffectiveBoundingRect = () => { return applyEdgeOffsetToBoundingRect(getBoundingRectRef.current(), edgeOffsetRef.current); }; const applyTransform = (dx, dy) => { if (!targetRef.current) return; targetRef.current.style.transform = `translate3d(${dx}px, ${dy}px, 0)`; }; const getTranslation = (event) => { if (!startRef.current) return { dx: 0, dy: 0 }; const { pageX, pageY, bounds } = startRef.current; return { dx: clamp(event.pageX - pageX, bounds.left, bounds.right), dy: clamp(event.pageY - pageY, bounds.top, bounds.bottom), }; }; const handleMouseUp = (event) => { event.preventDefault(); document.removeEventListener('mouseup', handleMouseUp); document.removeEventListener('mousemove', handleMouseMove); const { dx, dy } = getTranslation(event); applyTransform(0, 0); onDropRef.current?.(dx, dy); }; const handleMouseMove = (event) => { event.preventDefault(); const { dx, dy } = getTranslation(event); applyTransform(dx, dy); onMoveRef.current?.(event); }; const handleMouseDown = (event) => { if (!targetRef.current) return; event.preventDefault(); document.addEventListener('mouseup', handleMouseUp); document.addEventListener('mousemove', handleMouseMove); const targetRect = targetRef.current.getBoundingClientRect(); const boundingRect = getEffectiveBoundingRect(); startRef.current = { pageX: event.pageX, pageY: event.pageY, bounds: getTranslationBounds(targetRect, boundingRect), }; }; const boundInitialPosition = () => { if (!targetRef.current) return; const targetRect = targetRef.current.getBoundingClientRect(); const boundingRect = getEffectiveBoundingRect(); const bounds = getTranslationBounds(targetRect, boundingRect); const dx = clamp(0, bounds.left, bounds.right); const dy = clamp(0, bounds.top, bounds.bottom); if (onDropRef.current && (dx !== 0 || dy !== 0)) { onDropRef.current(dx, dy); } }; const handleMouseDownRef = useRef(handleMouseDown); handleMouseDownRef.current = handleMouseDown; const stableHandleMouseDown = useCallback((event) => { handleMouseDownRef.current(event); }, []); const handleRefCallback = useCallback((newNode) => { const oldNode = handleRef.current; if (oldNode) { oldNode.removeEventListener('mousedown', stableHandleMouseDown); } if (newNode) { newNode.addEventListener('mousedown', stableHandleMouseDown); boundInitialPosition(); } handleRef.current = newNode; }, [stableHandleMouseDown]); useEffect(() => { if (handleSelector) { setTimeout(() => { const node = targetRef?.current?.querySelector(handleSelector); node && handleRefCallback(node); }, 16); } }, [handleSelector, handleRefCallback]); return { handleRef: handleRefCallback, targetRef, applyTransform }; }