feat(utils): add snapPosition function
This commit is contained in:
@@ -6,7 +6,7 @@ import useKeyPress from './useKeyPress';
|
|||||||
import useReactFlow from './useReactFlow';
|
import useReactFlow from './useReactFlow';
|
||||||
import { Edge, Node } from '../types';
|
import { Edge, Node } from '../types';
|
||||||
|
|
||||||
const getSelected = (item: Node | Edge) => item.selected;
|
const selected = (item: Node | Edge) => item.selected;
|
||||||
|
|
||||||
export default ({
|
export default ({
|
||||||
deleteKeyCode,
|
deleteKeyCode,
|
||||||
@@ -24,7 +24,7 @@ export default ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (deleteKeyPressed) {
|
if (deleteKeyPressed) {
|
||||||
const { edges, nodes } = store.getState();
|
const { edges, nodes } = store.getState();
|
||||||
deleteElements({ nodes: nodes.filter(getSelected), edges: edges.filter(getSelected) });
|
deleteElements({ nodes: nodes.filter(selected), edges: edges.filter(selected) });
|
||||||
store.setState({ nodesSelectionActive: false });
|
store.setState({ nodesSelectionActive: false });
|
||||||
}
|
}
|
||||||
}, [deleteKeyPressed]);
|
}, [deleteKeyPressed]);
|
||||||
|
|||||||
@@ -3,18 +3,12 @@ import { errorMessages, getDimensions } from '@xyflow/system';
|
|||||||
|
|
||||||
import { useStoreApi } from '../hooks/useStore';
|
import { useStoreApi } from '../hooks/useStore';
|
||||||
|
|
||||||
function useResizeHandler(rendererNode: MutableRefObject<HTMLDivElement | null>): void {
|
function useResizeHandler(domNode: MutableRefObject<HTMLDivElement | null>): void {
|
||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let resizeObserver: ResizeObserver;
|
|
||||||
|
|
||||||
const updateDimensions = () => {
|
const updateDimensions = () => {
|
||||||
if (!rendererNode.current) {
|
const size = getDimensions(domNode.current!);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const size = getDimensions(rendererNode.current);
|
|
||||||
|
|
||||||
if (size.height === 0 || size.width === 0) {
|
if (size.height === 0 || size.width === 0) {
|
||||||
store.getState().onError?.('004', errorMessages['error004']());
|
store.getState().onError?.('004', errorMessages['error004']());
|
||||||
@@ -23,21 +17,21 @@ function useResizeHandler(rendererNode: MutableRefObject<HTMLDivElement | null>)
|
|||||||
store.setState({ width: size.width || 500, height: size.height || 500 });
|
store.setState({ width: size.width || 500, height: size.height || 500 });
|
||||||
};
|
};
|
||||||
|
|
||||||
updateDimensions();
|
if (domNode.current) {
|
||||||
window.addEventListener('resize', updateDimensions);
|
updateDimensions();
|
||||||
|
window.addEventListener('resize', updateDimensions);
|
||||||
|
|
||||||
if (rendererNode.current) {
|
const resizeObserver = new ResizeObserver(() => updateDimensions());
|
||||||
resizeObserver = new ResizeObserver(() => updateDimensions());
|
resizeObserver.observe(domNode.current);
|
||||||
resizeObserver.observe(rendererNode.current);
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', updateDimensions);
|
||||||
|
|
||||||
|
if (resizeObserver && domNode.current) {
|
||||||
|
resizeObserver.unobserve(domNode.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('resize', updateDimensions);
|
|
||||||
|
|
||||||
if (resizeObserver && rendererNode.current) {
|
|
||||||
resizeObserver.unobserve(rendererNode.current!);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,41 +1,49 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { calcNextPosition } from '@xyflow/system';
|
import { calcNextPosition, snapPosition } from '@xyflow/system';
|
||||||
|
|
||||||
|
import { Node } from '../types';
|
||||||
import { useStoreApi } from '../hooks/useStore';
|
import { useStoreApi } from '../hooks/useStore';
|
||||||
|
|
||||||
|
const selectedAndDraggable = (nodesDraggable: boolean) => (n: Node) =>
|
||||||
|
n.selected && (n.draggable || (nodesDraggable && typeof n.draggable === 'undefined'));
|
||||||
|
|
||||||
function useUpdateNodePositions() {
|
function useUpdateNodePositions() {
|
||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
|
|
||||||
const updatePositions = useCallback((params: { x: number; y: number; isShiftPressed: boolean }) => {
|
const updatePositions = useCallback((params: { x: number; y: number; isShiftPressed: boolean }) => {
|
||||||
const { nodeExtent, updateNodePositions, nodes, snapToGrid, snapGrid, onError, nodesDraggable } = store.getState();
|
const { nodeExtent, nodes, snapToGrid, snapGrid, nodesDraggable, onError, updateNodePositions } = store.getState();
|
||||||
const selectedNodes = nodes.filter(
|
const selectedNodes = nodes.filter(selectedAndDraggable(nodesDraggable));
|
||||||
(n) => n.selected && (n.draggable || (nodesDraggable && typeof n.draggable === 'undefined'))
|
|
||||||
);
|
|
||||||
// by default a node moves 5px on each key press, or 20px if shift is pressed
|
// by default a node moves 5px on each key press, or 20px if shift is pressed
|
||||||
// if snap grid is enabled, we use that for the velocity.
|
// if snap grid is enabled, we use that for the velocity.
|
||||||
const xVelo = snapToGrid ? snapGrid[0] : 5;
|
const xVelo = snapToGrid ? snapGrid[0] : 5;
|
||||||
const yVelo = snapToGrid ? snapGrid[1] : 5;
|
const yVelo = snapToGrid ? snapGrid[1] : 5;
|
||||||
const factor = params.isShiftPressed ? 4 : 1;
|
const factor = params.isShiftPressed ? 4 : 1;
|
||||||
|
|
||||||
const positionDiffX = params.x * xVelo * factor;
|
const xDiff = params.x * xVelo * factor;
|
||||||
const positionDiffY = params.y * yVelo * factor;
|
const yDiff = params.y * yVelo * factor;
|
||||||
|
|
||||||
const nodeUpdates = selectedNodes.map((n) => {
|
const nodeUpdates = selectedNodes.map((node) => {
|
||||||
if (n.positionAbsolute) {
|
if (node.positionAbsolute) {
|
||||||
const nextPosition = { x: n.positionAbsolute.x + positionDiffX, y: n.positionAbsolute.y + positionDiffY };
|
let nextPosition = { x: node.positionAbsolute.x + xDiff, y: node.positionAbsolute.y + yDiff };
|
||||||
|
|
||||||
if (snapToGrid) {
|
if (snapToGrid) {
|
||||||
nextPosition.x = snapGrid[0] * Math.round(nextPosition.x / snapGrid[0]);
|
nextPosition = snapPosition(nextPosition, snapGrid);
|
||||||
nextPosition.y = snapGrid[1] * Math.round(nextPosition.y / snapGrid[1]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { positionAbsolute, position } = calcNextPosition(n, nextPosition, nodes, nodeExtent, undefined, onError);
|
const { positionAbsolute, position } = calcNextPosition(
|
||||||
|
node,
|
||||||
|
nextPosition,
|
||||||
|
nodes,
|
||||||
|
nodeExtent,
|
||||||
|
undefined,
|
||||||
|
onError
|
||||||
|
);
|
||||||
|
|
||||||
n.position = position;
|
node.position = position;
|
||||||
n.positionAbsolute = positionAbsolute;
|
node.positionAbsolute = positionAbsolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
return n;
|
return node;
|
||||||
});
|
});
|
||||||
|
|
||||||
updateNodePositions(nodeUpdates, true, false);
|
updateNodePositions(nodeUpdates, true, false);
|
||||||
|
|||||||
@@ -144,21 +144,14 @@ export const pointToRendererPoint = (
|
|||||||
{ x, y }: XYPosition,
|
{ x, y }: XYPosition,
|
||||||
[tx, ty, tScale]: Transform,
|
[tx, ty, tScale]: Transform,
|
||||||
snapToGrid: boolean,
|
snapToGrid: boolean,
|
||||||
[snapX, snapY]: [number, number]
|
snapGrid: SnapGrid
|
||||||
): XYPosition => {
|
): XYPosition => {
|
||||||
const position: XYPosition = {
|
const position: XYPosition = {
|
||||||
x: (x - tx) / tScale,
|
x: (x - tx) / tScale,
|
||||||
y: (y - ty) / tScale,
|
y: (y - ty) / tScale,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (snapToGrid) {
|
return snapToGrid ? snapPosition(position, snapGrid) : position;
|
||||||
return {
|
|
||||||
x: snapX * Math.round(position.x / snapX),
|
|
||||||
y: snapY * Math.round(position.y / snapY),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return position;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const rendererPointToPoint = ({ x, y }: XYPosition, [tx, ty, tScale]: Transform): XYPosition => {
|
export const rendererPointToPoint = ({ x, y }: XYPosition, [tx, ty, tScale]: Transform): XYPosition => {
|
||||||
@@ -349,10 +342,12 @@ export function getPointerPosition(
|
|||||||
y: (y - transform[1]) / transform[2],
|
y: (y - transform[1]) / transform[2],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { x: xSnapped, y: ySnapped } = snapToGrid ? snapPosition(pointerPos, snapGrid) : pointerPos;
|
||||||
|
|
||||||
// we need the snapped position in order to be able to skip unnecessary drag events
|
// we need the snapped position in order to be able to skip unnecessary drag events
|
||||||
return {
|
return {
|
||||||
xSnapped: snapToGrid ? snapGrid[0] * Math.round(pointerPos.x / snapGrid[0]) : pointerPos.x,
|
xSnapped,
|
||||||
ySnapped: snapToGrid ? snapGrid[1] * Math.round(pointerPos.y / snapGrid[1]) : pointerPos.y,
|
ySnapped,
|
||||||
...pointerPos,
|
...pointerPos,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -366,11 +361,11 @@ export function calcNextPosition<NodeType extends BaseNode>(
|
|||||||
onError?: OnError
|
onError?: OnError
|
||||||
): { position: XYPosition; positionAbsolute: XYPosition } {
|
): { position: XYPosition; positionAbsolute: XYPosition } {
|
||||||
let currentExtent = node.extent || nodeExtent;
|
let currentExtent = node.extent || nodeExtent;
|
||||||
let parentNode: NodeType;
|
let parentNode: NodeType | null = null;
|
||||||
let parentPos = { x: 0, y: 0 };
|
let parentPos = { x: 0, y: 0 };
|
||||||
|
|
||||||
if (node.parentNode) {
|
if (node.parentNode) {
|
||||||
parentNode = nodes.find((n) => n.id === node.parentNode);
|
parentNode = nodes.find((n) => n.id === node.parentNode) || null;
|
||||||
parentPos = parentNode
|
parentPos = parentNode
|
||||||
? getNodePositionWithOrigin(parentNode, parentNode.origin || nodeOrigin).positionAbsolute
|
? getNodePositionWithOrigin(parentNode, parentNode.origin || nodeOrigin).positionAbsolute
|
||||||
: parentPos;
|
: parentPos;
|
||||||
@@ -413,3 +408,10 @@ export function calcNextPosition<NodeType extends BaseNode>(
|
|||||||
positionAbsolute,
|
positionAbsolute,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function snapPosition(position: XYPosition, snapGrid: SnapGrid = [1, 1]) {
|
||||||
|
return {
|
||||||
|
x: snapGrid[0] * Math.round(position.x / snapGrid[0]),
|
||||||
|
y: snapGrid[1] * Math.round(position.y / snapGrid[1]),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { drag } from 'd3-drag';
|
import { drag } from 'd3-drag';
|
||||||
import { select } from 'd3-selection';
|
import { select } from 'd3-selection';
|
||||||
|
|
||||||
import { calcAutoPan, getEventPosition, getPointerPosition, calcNextPosition } from '../utils';
|
import { calcAutoPan, getEventPosition, getPointerPosition, calcNextPosition, snapPosition } from '../utils';
|
||||||
import { getDragItems, getEventHandlerParams, hasSelector, wrapSelectionDragFunc } from './utils';
|
import { getDragItems, getEventHandlerParams, hasSelector, wrapSelectionDragFunc } from './utils';
|
||||||
import type {
|
import type {
|
||||||
BaseNode,
|
BaseNode,
|
||||||
@@ -107,11 +107,10 @@ export function XYDrag({
|
|||||||
let hasChange = false;
|
let hasChange = false;
|
||||||
|
|
||||||
dragItems = dragItems.map((n) => {
|
dragItems = dragItems.map((n) => {
|
||||||
const nextPosition = { x: x - n.distance.x, y: y - n.distance.y };
|
let nextPosition = { x: x - n.distance.x, y: y - n.distance.y };
|
||||||
|
|
||||||
if (snapToGrid) {
|
if (snapToGrid) {
|
||||||
nextPosition.x = snapGrid[0] * Math.round(nextPosition.x / snapGrid[0]);
|
nextPosition = snapPosition(nextPosition, snapGrid);
|
||||||
nextPosition.y = snapGrid[1] * Math.round(nextPosition.y / snapGrid[1]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedPos = calcNextPosition(n, nextPosition, nodes, nodeExtent, nodeOrigin, onError);
|
const updatedPos = calcNextPosition(n, nextPosition, nodes, nodeExtent, nodeOrigin, onError);
|
||||||
@@ -173,10 +172,10 @@ export function XYDrag({
|
|||||||
transform,
|
transform,
|
||||||
snapGrid,
|
snapGrid,
|
||||||
snapToGrid,
|
snapToGrid,
|
||||||
|
selectNodesOnDrag,
|
||||||
onNodeDragStart,
|
onNodeDragStart,
|
||||||
onSelectionDragStart,
|
onSelectionDragStart,
|
||||||
unselectNodesAndEdges,
|
unselectNodesAndEdges,
|
||||||
selectNodesOnDrag,
|
|
||||||
} = getStoreItems();
|
} = getStoreItems();
|
||||||
|
|
||||||
if (!selectNodesOnDrag && !multiSelectionActive && nodeId) {
|
if (!selectNodesOnDrag && !multiSelectionActive && nodeId) {
|
||||||
|
|||||||
Reference in New Issue
Block a user