@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
JavaScript
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 };
}