Merge pull request #5453 from xyflow/fix/nodedrag-snapgrid
chore(multiselect): snap selection instead of single nodes
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
'@xyflow/react': patch
|
||||||
|
'@xyflow/svelte': patch
|
||||||
|
'@xyflow/system': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Snap selection instead of separate nodes when snap grid is enabled
|
||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
getInternalNodesBounds,
|
getInternalNodesBounds,
|
||||||
rectToBox,
|
rectToBox,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { getDragItems, getEventHandlerParams, hasSelector } from './utils';
|
import { calculateSnapOffset, getDragItems, getEventHandlerParams, hasSelector } from './utils';
|
||||||
import type {
|
import type {
|
||||||
NodeBase,
|
NodeBase,
|
||||||
NodeDragItem,
|
NodeDragItem,
|
||||||
@@ -25,7 +25,6 @@ import type {
|
|||||||
PanBy,
|
PanBy,
|
||||||
OnSelectionDrag,
|
OnSelectionDrag,
|
||||||
UpdateNodePositions,
|
UpdateNodePositions,
|
||||||
Box,
|
|
||||||
InternalNodeBase,
|
InternalNodeBase,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
|
|
||||||
@@ -132,39 +131,42 @@ export function XYDrag<OnNodeDrag extends (e: any, nodes: any, node: any) => voi
|
|||||||
} = getStoreItems();
|
} = getStoreItems();
|
||||||
|
|
||||||
lastPos = { x, y };
|
lastPos = { x, y };
|
||||||
|
|
||||||
let hasChange = false;
|
let hasChange = false;
|
||||||
let nodesBox: Box = { x: 0, y: 0, x2: 0, y2: 0 };
|
|
||||||
|
|
||||||
if (dragItems.size > 1 && nodeExtent) {
|
const isMultiDrag = dragItems.size > 1;
|
||||||
const rect = getInternalNodesBounds(dragItems);
|
const nodesBox = isMultiDrag && nodeExtent ? rectToBox(getInternalNodesBounds(dragItems)) : null;
|
||||||
nodesBox = rectToBox(rect);
|
const multiDragSnapOffset =
|
||||||
}
|
isMultiDrag && snapToGrid
|
||||||
|
? calculateSnapOffset({
|
||||||
|
dragItems,
|
||||||
|
snapGrid,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
|
||||||
for (const [id, dragItem] of dragItems) {
|
for (const [id, dragItem] of dragItems) {
|
||||||
|
/*
|
||||||
|
* if the node is not in the nodeLookup anymore, it was probably deleted while dragging
|
||||||
|
*/
|
||||||
if (!nodeLookup.has(id)) {
|
if (!nodeLookup.has(id)) {
|
||||||
/*
|
|
||||||
* if the node is not in the nodeLookup anymore, it was probably deleted while dragging
|
|
||||||
* and we don't need to update it anymore
|
|
||||||
*/
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let nextPosition = { x: x - dragItem.distance.x, y: y - dragItem.distance.y };
|
let nextPosition = { x: x - dragItem.distance.x, y: y - dragItem.distance.y };
|
||||||
|
|
||||||
if (snapToGrid) {
|
if (snapToGrid) {
|
||||||
nextPosition = snapPosition(nextPosition, snapGrid);
|
nextPosition = multiDragSnapOffset
|
||||||
|
? {
|
||||||
|
x: nextPosition.x + multiDragSnapOffset.x,
|
||||||
|
y: nextPosition.y + multiDragSnapOffset.y,
|
||||||
|
}
|
||||||
|
: snapPosition(nextPosition, snapGrid);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
let adjustedNodeExtent: CoordinateExtent | null = null;
|
||||||
* if there is selection with multiple nodes and a node extent is set, we need to adjust the node extent for each node
|
|
||||||
* based on its position so that the node stays at it's position relative to the selection.
|
|
||||||
*/
|
|
||||||
let adjustedNodeExtent: CoordinateExtent = [
|
|
||||||
[nodeExtent[0][0], nodeExtent[0][1]],
|
|
||||||
[nodeExtent[1][0], nodeExtent[1][1]],
|
|
||||||
];
|
|
||||||
|
|
||||||
if (dragItems.size > 1 && nodeExtent && !dragItem.extent) {
|
if (isMultiDrag && nodeExtent && !dragItem.extent && nodesBox) {
|
||||||
const { positionAbsolute } = dragItem.internals;
|
const { positionAbsolute } = dragItem.internals;
|
||||||
const x1 = positionAbsolute.x - nodesBox.x + nodeExtent[0][0];
|
const x1 = positionAbsolute.x - nodesBox.x + nodeExtent[0][0];
|
||||||
const x2 = positionAbsolute.x + dragItem.measured.width - nodesBox.x2 + nodeExtent[1][0];
|
const x2 = positionAbsolute.x + dragItem.measured.width - nodesBox.x2 + nodeExtent[1][0];
|
||||||
@@ -182,7 +184,7 @@ export function XYDrag<OnNodeDrag extends (e: any, nodes: any, node: any) => voi
|
|||||||
nodeId: id,
|
nodeId: id,
|
||||||
nextPosition,
|
nextPosition,
|
||||||
nodeLookup,
|
nodeLookup,
|
||||||
nodeExtent: adjustedNodeExtent,
|
nodeExtent: adjustedNodeExtent ? adjustedNodeExtent : nodeExtent,
|
||||||
nodeOrigin,
|
nodeOrigin,
|
||||||
onError,
|
onError,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { type NodeDragItem, type XYPosition, InternalNodeBase, NodeBase, NodeLookup } from '../types';
|
import { type NodeDragItem, type XYPosition, InternalNodeBase, NodeBase, NodeLookup, SnapGrid } from '../types';
|
||||||
|
import { snapPosition } from '../utils';
|
||||||
|
|
||||||
export function isParentSelected<NodeType extends NodeBase>(node: NodeType, nodeLookup: NodeLookup): boolean {
|
export function isParentSelected<NodeType extends NodeBase>(node: NodeType, nodeLookup: NodeLookup): boolean {
|
||||||
if (!node.parentId) {
|
if (!node.parentId) {
|
||||||
@@ -114,10 +115,43 @@ export function getEventHandlerParams<NodeType extends InternalNodeBase>({
|
|||||||
!node
|
!node
|
||||||
? nodesFromDragItems[0]
|
? nodesFromDragItems[0]
|
||||||
: {
|
: {
|
||||||
...node,
|
...node,
|
||||||
position: dragItems.get(nodeId)?.position || node.position,
|
position: dragItems.get(nodeId)?.position || node.position,
|
||||||
dragging,
|
dragging,
|
||||||
},
|
},
|
||||||
nodesFromDragItems,
|
nodesFromDragItems,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a selection is being dragged we want to apply the same snap offset to all nodes in the selection.
|
||||||
|
* This function calculates the snap offset based on the first node in the selection.
|
||||||
|
*/
|
||||||
|
export function calculateSnapOffset({
|
||||||
|
dragItems,
|
||||||
|
snapGrid,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
}: {
|
||||||
|
dragItems: Map<string, NodeDragItem>;
|
||||||
|
snapGrid: SnapGrid;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}) {
|
||||||
|
const refDragItem = dragItems.values().next().value;
|
||||||
|
|
||||||
|
if (!refDragItem) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const refPos = {
|
||||||
|
x: x - refDragItem.distance.x,
|
||||||
|
y: y - refDragItem.distance.y,
|
||||||
|
};
|
||||||
|
const refPosSnapped = snapPosition(refPos, snapGrid);
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: refPosSnapped.x - refPos.x,
|
||||||
|
y: refPosSnapped.y - refPos.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user