fixed bug with expanding parent, made adjustments to evaluateNodePosition

This commit is contained in:
peterkogo
2024-04-16 13:19:44 +02:00
parent 81bdaf6b68
commit 714d30b63f
6 changed files with 72 additions and 64 deletions
@@ -9,7 +9,7 @@ import {
type NodeChange, type NodeChange,
type NodeDimensionChange, type NodeDimensionChange,
type NodePositionChange, type NodePositionChange,
handleParentExpand, handleExpandParent,
evaluateNodePosition, evaluateNodePosition,
} from '@xyflow/system'; } from '@xyflow/system';
@@ -74,20 +74,32 @@ function ResizeControl({
const node = nodeLookup.get(id); const node = nodeLookup.get(id);
if (node && node.expandParent && node.parentId) { if (node && node.expandParent && node.parentId) {
const nodeWithChange: Node = { // We create a new node that will be used to handleExpandParent ...
...node, const nodeWithChange = {
...(node as InternalNodeWithParentExpand),
position: { position: {
x: change.x ?? node.position.x, x: change.x ?? node.position.x,
y: change.y ?? node.position.y, y: change.y ?? node.position.y,
}, },
measured: {
width: change.width,
height: change.height,
},
width: change.width, width: change.width,
height: change.height, height: change.height,
}; };
const nodeWith = evaluateNodePosition(nodeWithChange, nodeLookup) as InternalNodeWithParentExpand; // ...determine its new absolute position...
nodeWithChange.internals = {
...nodeWithChange.internals,
positionAbsolute: evaluateNodePosition(nodeWithChange, nodeLookup),
};
const parentExpandChanges = handleParentExpand([nodeWith], nodeLookup, parentLookup); // ... and use it to expand the parent
const parentExpandChanges = handleExpandParent([nodeWithChange], nodeLookup, parentLookup);
changes.push(...parentExpandChanges); changes.push(...parentExpandChanges);
// when the parent was expanded by the child node, its position will be clamped at 0,0
newPosition.x = change.x ? Math.max(0, change.x) : undefined; newPosition.x = change.x ? Math.max(0, change.x) : undefined;
newPosition.y = change.y ? Math.max(0, change.y) : undefined; newPosition.y = change.y ? Math.max(0, change.y) : undefined;
} }
@@ -42,22 +42,12 @@ export function NodeWrapper<NodeType extends Node>({
nodeOrigin, nodeOrigin,
onError, onError,
}: NodeWrapperProps<NodeType>) { }: NodeWrapperProps<NodeType>) {
const { node, positionAbsoluteX, positionAbsoluteY, internals } = useStore((s) => { const { node, internals } = useStore((s) => {
const node = s.nodeLookup.get(id)! as InternalNode<NodeType>; const node = s.nodeLookup.get(id)! as InternalNode<NodeType>;
const positionAbsolute = nodeExtent
? clampPosition(node.internals.positionAbsolute, nodeExtent)
: node.internals.positionAbsolute || { x: 0, y: 0 };
return { return {
node, node,
// we are mutating positionAbsolute, z and isParent attributes for sub flows
// so we we need to force a re-render when some change
positionAbsoluteX: positionAbsolute.x,
positionAbsoluteY: positionAbsolute.y,
internals: node.internals, internals: node.internals,
// zIndex: node.internals.z,
// isParent: node.internals.isParent,
}; };
}, shallow); }, shallow);
@@ -141,10 +131,15 @@ export function NodeWrapper<NodeType extends Node>({
return null; return null;
} }
const positionAbsoluteOrigin = getPositionWithOrigin({ const positionAbsolute = nodeExtent
x: positionAbsoluteX, ? clampPosition(node.internals.positionAbsolute, nodeExtent)
y: positionAbsoluteY, : node.internals.positionAbsolute || { x: 0, y: 0 };
...nodeDimensions,
const positionWithOrigin = getPositionWithOrigin({
x: positionAbsolute.x,
y: positionAbsolute.y,
width: nodeDimensions.width,
height: nodeDimensions.height,
origin: node.origin || nodeOrigin, origin: node.origin || nodeOrigin,
}); });
const hasPointerEvents = isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave; const hasPointerEvents = isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave;
@@ -191,7 +186,7 @@ export function NodeWrapper<NodeType extends Node>({
store.setState({ store.setState({
ariaLiveMessage: `Moved selected node ${event.key ariaLiveMessage: `Moved selected node ${event.key
.replace('Arrow', '') .replace('Arrow', '')
.toLowerCase()}. New position, x: ${~~positionAbsoluteX}, y: ${~~positionAbsoluteY}`, .toLowerCase()}. New position, x: ${~~positionAbsolute.x}, y: ${~~positionAbsolute.y}`,
}); });
moveSelectedNodes({ moveSelectedNodes({
@@ -222,7 +217,7 @@ export function NodeWrapper<NodeType extends Node>({
ref={nodeRef} ref={nodeRef}
style={{ style={{
zIndex: internals.z, zIndex: internals.z,
transform: `translate(${positionAbsoluteOrigin.x}px,${positionAbsoluteOrigin.y}px)`, transform: `translate(${positionWithOrigin.x}px,${positionWithOrigin.y}px)`,
pointerEvents: hasPointerEvents ? 'all' : 'none', pointerEvents: hasPointerEvents ? 'all' : 'none',
visibility: initialized ? 'visible' : 'hidden', visibility: initialized ? 'visible' : 'hidden',
...node.style, ...node.style,
@@ -247,8 +242,8 @@ export function NodeWrapper<NodeType extends Node>({
id={id} id={id}
data={node.data} data={node.data}
type={nodeType} type={nodeType}
positionAbsoluteX={positionAbsoluteX} positionAbsoluteX={positionAbsolute.x}
positionAbsoluteY={positionAbsoluteY} positionAbsoluteY={positionAbsolute.y}
selected={node.selected} selected={node.selected}
isConnectable={isConnectable} isConnectable={isConnectable}
sourcePosition={node.sourcePosition} sourcePosition={node.sourcePosition}
+12 -3
View File
@@ -3,6 +3,7 @@ import {
evaluateNodePosition, evaluateNodePosition,
getElementsToRemove, getElementsToRemove,
getOverlappingArea, getOverlappingArea,
isInternalNodeBase,
isRectObject, isRectObject,
nodeToRect, nodeToRect,
type Rect, type Rect,
@@ -232,10 +233,18 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
const getNodeRect = useCallback((node: NodeType | { id: string }): Rect | null => { const getNodeRect = useCallback((node: NodeType | { id: string }): Rect | null => {
const { nodeLookup, nodeOrigin } = store.getState(); const { nodeLookup, nodeOrigin } = store.getState();
const nodeToUse = isNode(node) ? node : nodeLookup.get(node.id)!; const nodeToUse = isNode<NodeType>(node) ? node : nodeLookup.get(node.id)!;
const nodeWithPos = evaluateNodePosition(nodeToUse, nodeLookup, nodeOrigin); const positionAbsolute = evaluateNodePosition(nodeToUse, nodeLookup, nodeOrigin);
return nodeWithPos ? nodeToRect(nodeWithPos) : null; const nodeWithPosition = {
id: nodeToUse.id,
position: positionAbsolute,
width: nodeToUse.measured?.width ?? nodeToUse.width,
height: nodeToUse.measured?.height ?? nodeToUse.height,
data: nodeToUse.data,
};
return nodeToRect(nodeWithPosition);
}, []); }, []);
const getIntersectingNodes = useCallback<Instance.GetIntersectingNodes<NodeType>>( const getIntersectingNodes = useCallback<Instance.GetIntersectingNodes<NodeType>>(
+2 -2
View File
@@ -7,7 +7,7 @@ import {
panBy as panBySystem, panBy as panBySystem,
updateNodeInternals as updateNodeInternalsSystem, updateNodeInternals as updateNodeInternalsSystem,
updateConnectionLookup, updateConnectionLookup,
handleParentExpand, handleExpandParent,
NodeChange, NodeChange,
EdgeSelectionChange, EdgeSelectionChange,
NodeSelectionChange, NodeSelectionChange,
@@ -155,7 +155,7 @@ const createRFStore = ({
}); });
if (expandParentNodes.length > 0) { if (expandParentNodes.length > 0) {
const parentExpandChanges = handleParentExpand(expandParentNodes, nodeLookup, parentLookup); const parentExpandChanges = handleExpandParent(expandParentNodes, nodeLookup, parentLookup);
changes.push(...parentExpandChanges); changes.push(...parentExpandChanges);
} }
+3 -15
View File
@@ -234,14 +234,8 @@ export function evaluateNodePosition(
node: NodeBase | InternalNodeBase, node: NodeBase | InternalNodeBase,
nodeLookup: NodeLookup, nodeLookup: NodeLookup,
nodeOrigin: NodeOrigin = [0, 0] nodeOrigin: NodeOrigin = [0, 0]
): InternalNodeBase | null { ): XYPosition {
const internalNode = nodeLookup.get(node.id); let parentId = node.parentId;
if (!internalNode) {
return null;
}
let parentId = internalNode.parentId;
const positionAbsolute = { ...node.position }; const positionAbsolute = { ...node.position };
while (parentId) { while (parentId) {
@@ -257,11 +251,5 @@ export function evaluateNodePosition(
} }
} }
return { return positionAbsolute;
...internalNode,
internals: {
...internalNode.internals,
positionAbsolute,
},
};
} }
+24 -20
View File
@@ -159,36 +159,38 @@ function calculateXYZPosition<NodeType extends NodeBase>(
type ExpandParentNode<NodeType> = NodeType & { parentId: string; expandParent: true }; type ExpandParentNode<NodeType> = NodeType & { parentId: string; expandParent: true };
export function handleParentExpand( export function handleExpandParent(
nodes: ExpandParentNode<InternalNodeBase>[], nodes: ExpandParentNode<InternalNodeBase>[],
nodeLookup: NodeLookup, nodeLookup: NodeLookup,
parentLookup: ParentLookup parentLookup: ParentLookup
): (NodeDimensionChange | NodePositionChange)[] { ): (NodeDimensionChange | NodePositionChange)[] {
const changes: (NodeDimensionChange | NodePositionChange)[] = []; const changes: (NodeDimensionChange | NodePositionChange)[] = [];
const chilNodeRects = new Map<string, Rect>(); const childNodeRects = new Map<string, { expandedRect: Rect; parent: InternalNodeBase }>();
// determine the expanded rectangle the child nodes would take for each parent
for (const node of nodes) { for (const node of nodes) {
const parentNode = nodeLookup.get(node.parentId); const parent = nodeLookup.get(node.parentId);
if (!parentNode) { if (!parent) {
continue; continue;
} }
const parentRect = chilNodeRects.get(node.parentId) ?? nodeToRect(parentNode, node.origin); const parentRect = childNodeRects.get(node.parentId)?.expandedRect ?? nodeToRect(parent, node.origin);
const expandedRect = getBoundsOfRects(parentRect, nodeToRect(node, node.origin)); const expandedRect = getBoundsOfRects(parentRect, nodeToRect(node, node.origin));
chilNodeRects.set(node.parentId, expandedRect); childNodeRects.set(node.parentId, { expandedRect, parent });
} }
if (chilNodeRects.size > 0) { if (childNodeRects.size > 0) {
chilNodeRects.forEach((rect, id) => { childNodeRects.forEach(({ expandedRect, parent }, parentId) => {
const origParent = nodeLookup.get(id)!; // determine the position & dimensions of the parent
const { position } = getNodePositionWithOrigin(origParent, origParent.origin); const { position } = getNodePositionWithOrigin(parent, parent.origin);
const dimensions = getNodeDimensions(origParent); const dimensions = getNodeDimensions(parent);
let xChange = rect.x < position.x ? Math.round(Math.abs(position.x - rect.x)) : 0; // determine how much the parent expands by moving the position
let yChange = rect.y < position.y ? Math.round(Math.abs(position.y - rect.y)) : 0; let xChange = expandedRect.x < position.x ? Math.round(Math.abs(position.x - expandedRect.x)) : 0;
let yChange = expandedRect.y < position.y ? Math.round(Math.abs(position.y - expandedRect.y)) : 0;
if (xChange > 0 || yChange > 0) { if (xChange > 0 || yChange > 0) {
changes.push({ changes.push({
id, id: parentId,
type: 'position', type: 'position',
position: { position: {
x: position.x - xChange, x: position.x - xChange,
@@ -196,7 +198,9 @@ export function handleParentExpand(
}, },
}); });
const childNodes = parentLookup.get(id); // We move all child nodes in the oppsite direction
// so the x,y changes of the parent do not move the children
const childNodes = parentLookup.get(parentId);
childNodes?.forEach((childNode) => { childNodes?.forEach((childNode) => {
if (!nodes.some((n) => n.id === childNode.id)) { if (!nodes.some((n) => n.id === childNode.id)) {
changes.push({ changes.push({
@@ -211,14 +215,14 @@ export function handleParentExpand(
}); });
} }
if (dimensions.width < rect.width || dimensions.height < rect.height) { if (dimensions.width < expandedRect.width || dimensions.height < expandedRect.height) {
changes.push({ changes.push({
id, id: parentId,
type: 'dimensions', type: 'dimensions',
resizing: true, resizing: true,
dimensions: { dimensions: {
width: Math.max(dimensions.width, Math.round(rect.width)), width: Math.max(dimensions.width, Math.round(expandedRect.width)),
height: Math.max(dimensions.height, Math.round(rect.height)), height: Math.max(dimensions.height, Math.round(expandedRect.height)),
}, },
}); });
} }
@@ -301,7 +305,7 @@ export function updateNodeInternals<NodeType extends InternalNodeBase>(
}); });
if (parentExpandNodes.length > 0) { if (parentExpandNodes.length > 0) {
const parentExpandChanges = handleParentExpand(parentExpandNodes, nodeLookup, parentLookup); const parentExpandChanges = handleExpandParent(parentExpandNodes, nodeLookup, parentLookup);
changes.push(...parentExpandChanges); changes.push(...parentExpandChanges);
} }