refactor(system): revise calcNextPosition
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
|||||||
MiniMap,
|
MiniMap,
|
||||||
Background,
|
Background,
|
||||||
Panel,
|
Panel,
|
||||||
|
NodeOrigin,
|
||||||
} from '@xyflow/react';
|
} from '@xyflow/react';
|
||||||
|
|
||||||
import DebugNode from './DebugNode';
|
import DebugNode from './DebugNode';
|
||||||
@@ -119,6 +120,7 @@ const initialNodes: Node[] = [
|
|||||||
data: { label: 'Node 3' },
|
data: { label: 'Node 3' },
|
||||||
position: { x: 400, y: 100 },
|
position: { x: 400, y: 100 },
|
||||||
className: 'light',
|
className: 'light',
|
||||||
|
extent: 'parent',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { calcNextPosition, snapPosition } from '@xyflow/system';
|
import { calculateNodePosition, snapPosition } from '@xyflow/system';
|
||||||
|
|
||||||
import { Node } from '../types';
|
import { Node } from '../types';
|
||||||
import { useStoreApi } from '../hooks/useStore';
|
import { useStoreApi } from '../hooks/useStore';
|
||||||
@@ -17,7 +17,17 @@ export 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, nodes, snapToGrid, snapGrid, nodesDraggable, onError, updateNodePositions } = store.getState();
|
const {
|
||||||
|
nodeExtent,
|
||||||
|
nodes,
|
||||||
|
snapToGrid,
|
||||||
|
snapGrid,
|
||||||
|
nodesDraggable,
|
||||||
|
onError,
|
||||||
|
updateNodePositions,
|
||||||
|
nodeLookup,
|
||||||
|
nodeOrigin,
|
||||||
|
} = store.getState();
|
||||||
const selectedNodes = nodes.filter(selectedAndDraggable(nodesDraggable));
|
const selectedNodes = nodes.filter(selectedAndDraggable(nodesDraggable));
|
||||||
// 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.
|
||||||
@@ -31,27 +41,24 @@ export function useUpdateNodePositions() {
|
|||||||
const nodeUpdates = selectedNodes.map((node) => {
|
const nodeUpdates = selectedNodes.map((node) => {
|
||||||
if (node.computed?.positionAbsolute) {
|
if (node.computed?.positionAbsolute) {
|
||||||
let nextPosition = {
|
let nextPosition = {
|
||||||
x: node.computed?.positionAbsolute.x + xDiff,
|
x: node.computed.positionAbsolute.x + xDiff,
|
||||||
y: node.computed?.positionAbsolute.y + yDiff,
|
y: node.computed.positionAbsolute.y + yDiff,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (snapToGrid) {
|
if (snapToGrid) {
|
||||||
nextPosition = snapPosition(nextPosition, snapGrid);
|
nextPosition = snapPosition(nextPosition, snapGrid);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { positionAbsolute, position } = calcNextPosition(
|
const { position, positionAbsolute } = calculateNodePosition({
|
||||||
node,
|
nodeId: node.id,
|
||||||
nextPosition,
|
nextPosition,
|
||||||
nodes,
|
nodeLookup,
|
||||||
nodeExtent,
|
nodeExtent,
|
||||||
undefined,
|
nodeOrigin,
|
||||||
onError
|
onError,
|
||||||
);
|
});
|
||||||
|
|
||||||
node.position = position;
|
node.position = position;
|
||||||
if (!node.computed) {
|
|
||||||
node.computed = {};
|
|
||||||
}
|
|
||||||
node.computed.positionAbsolute = positionAbsolute;
|
node.computed.positionAbsolute = positionAbsolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
getViewportForBounds,
|
getViewportForBounds,
|
||||||
Transform,
|
Transform,
|
||||||
updateConnectionLookup,
|
updateConnectionLookup,
|
||||||
|
devWarn,
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import type { Edge, Node, ReactFlowStore } from '../types';
|
import type { Edge, Node, ReactFlowStore } from '../types';
|
||||||
@@ -100,7 +101,7 @@ const getInitialState = ({
|
|||||||
autoPanOnConnect: true,
|
autoPanOnConnect: true,
|
||||||
autoPanOnNodeDrag: true,
|
autoPanOnNodeDrag: true,
|
||||||
connectionRadius: 20,
|
connectionRadius: 20,
|
||||||
onError: () => null,
|
onError: devWarn,
|
||||||
isValidConnection: undefined,
|
isValidConnection: undefined,
|
||||||
onSelectionChangeHandlers: [],
|
onSelectionChangeHandlers: [],
|
||||||
|
|
||||||
|
|||||||
@@ -198,3 +198,7 @@ export const getViewportForBounds = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const isMacOs = () => typeof navigator !== 'undefined' && navigator?.userAgent?.indexOf('Mac') >= 0;
|
export const isMacOs = () => typeof navigator !== 'undefined' && navigator?.userAgent?.indexOf('Mac') >= 0;
|
||||||
|
|
||||||
|
export function isCoordinateExtent(extent?: CoordinateExtent | 'parent'): extent is CoordinateExtent {
|
||||||
|
return extent !== undefined && extent !== 'parent';
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import {
|
|||||||
clampPosition,
|
clampPosition,
|
||||||
getBoundsOfBoxes,
|
getBoundsOfBoxes,
|
||||||
getOverlappingArea,
|
getOverlappingArea,
|
||||||
isNumeric,
|
|
||||||
rectToBox,
|
rectToBox,
|
||||||
nodeToRect,
|
nodeToRect,
|
||||||
pointToRendererPoint,
|
pointToRendererPoint,
|
||||||
getViewportForBounds,
|
getViewportForBounds,
|
||||||
|
isCoordinateExtent,
|
||||||
} from './general';
|
} from './general';
|
||||||
import {
|
import {
|
||||||
type Transform,
|
type Transform,
|
||||||
@@ -19,10 +19,10 @@ import {
|
|||||||
type EdgeBase,
|
type EdgeBase,
|
||||||
type FitViewParamsBase,
|
type FitViewParamsBase,
|
||||||
type FitViewOptionsBase,
|
type FitViewOptionsBase,
|
||||||
NodeDragItem,
|
|
||||||
CoordinateExtent,
|
CoordinateExtent,
|
||||||
OnError,
|
OnError,
|
||||||
OnBeforeDeleteBase,
|
OnBeforeDeleteBase,
|
||||||
|
NodeLookup,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { errorMessages } from '../constants';
|
import { errorMessages } from '../constants';
|
||||||
|
|
||||||
@@ -258,69 +258,87 @@ export function fitView<Params extends FitViewParamsBase<NodeBase>, Options exte
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function clampNodeExtent(node: NodeDragItem | NodeBase, extent?: CoordinateExtent | 'parent') {
|
/**
|
||||||
|
* This function clamps the passed extend by the node's width and height.
|
||||||
|
* This is needed to prevent the node from being dragged outside of its extent.
|
||||||
|
*
|
||||||
|
* @param node
|
||||||
|
* @param extent
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function clampNodeExtent<NodeType extends NodeBase>(
|
||||||
|
node: NodeType,
|
||||||
|
extent?: CoordinateExtent | 'parent'
|
||||||
|
): CoordinateExtent | 'parent' | undefined {
|
||||||
if (!extent || extent === 'parent') {
|
if (!extent || extent === 'parent') {
|
||||||
return extent;
|
return extent;
|
||||||
}
|
}
|
||||||
return [extent[0], [extent[1][0] - (node.computed?.width ?? 0), extent[1][1] - (node.computed?.height ?? 0)]];
|
return [extent[0], [extent[1][0] - (node.computed?.width ?? 0), extent[1][1] - (node.computed?.height ?? 0)]];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function calcNextPosition<NodeType extends NodeBase>(
|
/**
|
||||||
node: NodeDragItem | NodeType,
|
* This function calculates the next position of a node, taking into account the node's extent, parent node, and origin.
|
||||||
nextPosition: XYPosition,
|
*
|
||||||
nodes: NodeType[],
|
* @internal
|
||||||
nodeExtent?: CoordinateExtent,
|
* @returns position, positionAbsolute
|
||||||
nodeOrigin: NodeOrigin = [0, 0],
|
*/
|
||||||
onError?: OnError
|
export function calculateNodePosition<NodeType extends NodeBase>({
|
||||||
): { position: XYPosition; positionAbsolute: XYPosition } {
|
nodeId,
|
||||||
const clampedNodeExtent = clampNodeExtent(node, node.extent || nodeExtent);
|
nextPosition,
|
||||||
let currentExtent = clampedNodeExtent;
|
nodeLookup,
|
||||||
let parentNode: NodeType | null = null;
|
nodeOrigin = [0, 0],
|
||||||
let parentPos = { x: 0, y: 0 };
|
nodeExtent,
|
||||||
|
onError,
|
||||||
if (node.parentNode) {
|
}: {
|
||||||
parentNode = nodes.find((n) => n.id === node.parentNode) || null;
|
nodeId: string;
|
||||||
parentPos = parentNode
|
nextPosition: XYPosition;
|
||||||
? getNodePositionWithOrigin(parentNode, parentNode.origin || nodeOrigin).positionAbsolute
|
nodeLookup: NodeLookup<NodeType>;
|
||||||
: parentPos;
|
nodeOrigin?: NodeOrigin;
|
||||||
}
|
nodeExtent?: CoordinateExtent;
|
||||||
|
onError?: OnError;
|
||||||
|
}): { position: XYPosition; positionAbsolute: XYPosition } {
|
||||||
|
const node = nodeLookup.get(nodeId)!;
|
||||||
|
const parentNode = node.parentNode ? nodeLookup.get(node.parentNode) : undefined;
|
||||||
|
const { x: parentX, y: parentY } = parentNode
|
||||||
|
? getNodePositionWithOrigin(parentNode, parentNode.origin || nodeOrigin).positionAbsolute
|
||||||
|
: { x: 0, y: 0 };
|
||||||
|
let currentExtent = clampNodeExtent(node, node.extent || nodeExtent);
|
||||||
|
|
||||||
if (node.extent === 'parent' && !node.expandParent) {
|
if (node.extent === 'parent' && !node.expandParent) {
|
||||||
const nodeWidth = node.computed?.width;
|
if (!parentNode) {
|
||||||
const nodeHeight = node.computed?.height;
|
|
||||||
if (node.parentNode && nodeWidth && nodeHeight) {
|
|
||||||
const currNodeOrigin = node.origin || nodeOrigin;
|
|
||||||
|
|
||||||
currentExtent =
|
|
||||||
parentNode && isNumeric(parentNode.computed?.width) && isNumeric(parentNode.computed?.height)
|
|
||||||
? [
|
|
||||||
[parentPos.x + nodeWidth * currNodeOrigin[0], parentPos.y + nodeHeight * currNodeOrigin[1]],
|
|
||||||
[
|
|
||||||
parentPos.x + (parentNode.computed?.width ?? 0) - nodeWidth + nodeWidth * currNodeOrigin[0],
|
|
||||||
parentPos.y + (parentNode.computed?.height ?? 0) - nodeHeight + nodeHeight * currNodeOrigin[1],
|
|
||||||
],
|
|
||||||
]
|
|
||||||
: currentExtent;
|
|
||||||
} else {
|
|
||||||
onError?.('005', errorMessages['error005']());
|
onError?.('005', errorMessages['error005']());
|
||||||
currentExtent = clampedNodeExtent;
|
} else {
|
||||||
|
const nodeWidth = node.computed?.width;
|
||||||
|
const nodeHeight = node.computed?.height;
|
||||||
|
const parentWidth = parentNode?.computed?.width;
|
||||||
|
const parentHeight = parentNode?.computed?.height;
|
||||||
|
|
||||||
|
if (nodeWidth && nodeHeight && parentWidth && parentHeight) {
|
||||||
|
const currNodeOrigin = node.origin || nodeOrigin;
|
||||||
|
const extentX = parentX + nodeWidth * currNodeOrigin[0];
|
||||||
|
const extentY = parentY + nodeHeight * currNodeOrigin[1];
|
||||||
|
|
||||||
|
currentExtent = [
|
||||||
|
[extentX, extentY],
|
||||||
|
[extentX + parentWidth - nodeWidth, extentY + parentHeight - nodeHeight],
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (node.extent && node.parentNode && node.extent !== 'parent') {
|
} else if (parentNode && isCoordinateExtent(node.extent)) {
|
||||||
currentExtent = [
|
currentExtent = [
|
||||||
[node.extent[0][0] + parentPos.x, node.extent[0][1] + parentPos.y],
|
[node.extent[0][0] + parentX, node.extent[0][1] + parentY],
|
||||||
[node.extent[1][0] + parentPos.x, node.extent[1][1] + parentPos.y],
|
[node.extent[1][0] + parentX, node.extent[1][1] + parentY],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
const positionAbsolute =
|
const positionAbsolute = isCoordinateExtent(currentExtent)
|
||||||
currentExtent && currentExtent !== 'parent'
|
? clampPosition(nextPosition, currentExtent)
|
||||||
? clampPosition(nextPosition, currentExtent as CoordinateExtent)
|
: nextPosition;
|
||||||
: nextPosition;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
position: {
|
position: {
|
||||||
x: positionAbsolute.x - parentPos.x,
|
x: positionAbsolute.x - parentX,
|
||||||
y: positionAbsolute.y - parentPos.y,
|
y: positionAbsolute.y - parentY,
|
||||||
},
|
},
|
||||||
positionAbsolute,
|
positionAbsolute,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
calcAutoPan,
|
calcAutoPan,
|
||||||
getEventPosition,
|
getEventPosition,
|
||||||
getPointerPosition,
|
getPointerPosition,
|
||||||
calcNextPosition,
|
calculateNodePosition,
|
||||||
snapPosition,
|
snapPosition,
|
||||||
getNodesBounds,
|
getNodesBounds,
|
||||||
rectToBox,
|
rectToBox,
|
||||||
@@ -103,7 +103,6 @@ export function XYDrag<OnNodeDrag extends (e: any, nodes: any, node: any) => voi
|
|||||||
function update({ noDragClassName, handleSelector, domNode, isSelectable, nodeId }: DragUpdateParams) {
|
function update({ noDragClassName, handleSelector, domNode, isSelectable, nodeId }: DragUpdateParams) {
|
||||||
function updateNodes({ x, y }: XYPosition) {
|
function updateNodes({ x, y }: XYPosition) {
|
||||||
const {
|
const {
|
||||||
nodes,
|
|
||||||
nodeLookup,
|
nodeLookup,
|
||||||
nodeExtent,
|
nodeExtent,
|
||||||
snapGrid,
|
snapGrid,
|
||||||
@@ -149,13 +148,20 @@ export function XYDrag<OnNodeDrag extends (e: any, nodes: any, node: any) => voi
|
|||||||
n.computed.positionAbsolute.y + (n.computed?.height ?? 0) - nodesBox.y2 + nodeExtent[1][1];
|
n.computed.positionAbsolute.y + (n.computed?.height ?? 0) - nodesBox.y2 + nodeExtent[1][1];
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedPos = calcNextPosition(n, nextPosition, nodes, adjustedNodeExtent, nodeOrigin, onError);
|
const { position, positionAbsolute } = calculateNodePosition({
|
||||||
|
nodeId: n.id,
|
||||||
|
nextPosition,
|
||||||
|
nodeLookup,
|
||||||
|
nodeExtent: adjustedNodeExtent,
|
||||||
|
nodeOrigin,
|
||||||
|
onError,
|
||||||
|
});
|
||||||
|
|
||||||
// we want to make sure that we only fire a change event when there is a change
|
// we want to make sure that we only fire a change event when there is a change
|
||||||
hasChange = hasChange || n.position.x !== updatedPos.position.x || n.position.y !== updatedPos.position.y;
|
hasChange = hasChange || n.position.x !== position.x || n.position.y !== position.y;
|
||||||
|
|
||||||
n.position = updatedPos.position;
|
n.position = position;
|
||||||
n.computed.positionAbsolute = updatedPos.positionAbsolute;
|
n.computed.positionAbsolute = positionAbsolute;
|
||||||
|
|
||||||
return n;
|
return n;
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user