From 714d30b63fb2aac501b81572567ffe1398ed5c17 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Tue, 16 Apr 2024 13:19:44 +0200 Subject: [PATCH] fixed bug with expanding parent, made adjustments to evaluateNodePosition --- .../NodeResizer/NodeResizeControl.tsx | 22 +++++++--- .../src/components/NodeWrapper/index.tsx | 33 ++++++-------- packages/react/src/hooks/useReactFlow.ts | 15 +++++-- packages/react/src/store/index.ts | 4 +- packages/system/src/utils/general.ts | 18 ++------ packages/system/src/utils/store.ts | 44 ++++++++++--------- 6 files changed, 72 insertions(+), 64 deletions(-) diff --git a/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx b/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx index b661ebde..3975ef52 100644 --- a/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx +++ b/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx @@ -9,7 +9,7 @@ import { type NodeChange, type NodeDimensionChange, type NodePositionChange, - handleParentExpand, + handleExpandParent, evaluateNodePosition, } from '@xyflow/system'; @@ -74,20 +74,32 @@ function ResizeControl({ const node = nodeLookup.get(id); if (node && node.expandParent && node.parentId) { - const nodeWithChange: Node = { - ...node, + // We create a new node that will be used to handleExpandParent ... + const nodeWithChange = { + ...(node as InternalNodeWithParentExpand), position: { x: change.x ?? node.position.x, y: change.y ?? node.position.y, }, + measured: { + width: change.width, + height: change.height, + }, width: change.width, 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); + + // 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.y = change.y ? Math.max(0, change.y) : undefined; } diff --git a/packages/react/src/components/NodeWrapper/index.tsx b/packages/react/src/components/NodeWrapper/index.tsx index c26c91c2..f4d0fc3a 100644 --- a/packages/react/src/components/NodeWrapper/index.tsx +++ b/packages/react/src/components/NodeWrapper/index.tsx @@ -42,22 +42,12 @@ export function NodeWrapper({ nodeOrigin, onError, }: NodeWrapperProps) { - const { node, positionAbsoluteX, positionAbsoluteY, internals } = useStore((s) => { + const { node, internals } = useStore((s) => { const node = s.nodeLookup.get(id)! as InternalNode; - const positionAbsolute = nodeExtent - ? clampPosition(node.internals.positionAbsolute, nodeExtent) - : node.internals.positionAbsolute || { x: 0, y: 0 }; - return { 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, - // zIndex: node.internals.z, - // isParent: node.internals.isParent, }; }, shallow); @@ -141,10 +131,15 @@ export function NodeWrapper({ return null; } - const positionAbsoluteOrigin = getPositionWithOrigin({ - x: positionAbsoluteX, - y: positionAbsoluteY, - ...nodeDimensions, + const positionAbsolute = nodeExtent + ? clampPosition(node.internals.positionAbsolute, nodeExtent) + : node.internals.positionAbsolute || { x: 0, y: 0 }; + + const positionWithOrigin = getPositionWithOrigin({ + x: positionAbsolute.x, + y: positionAbsolute.y, + width: nodeDimensions.width, + height: nodeDimensions.height, origin: node.origin || nodeOrigin, }); const hasPointerEvents = isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave; @@ -191,7 +186,7 @@ export function NodeWrapper({ store.setState({ ariaLiveMessage: `Moved selected node ${event.key .replace('Arrow', '') - .toLowerCase()}. New position, x: ${~~positionAbsoluteX}, y: ${~~positionAbsoluteY}`, + .toLowerCase()}. New position, x: ${~~positionAbsolute.x}, y: ${~~positionAbsolute.y}`, }); moveSelectedNodes({ @@ -222,7 +217,7 @@ export function NodeWrapper({ ref={nodeRef} style={{ zIndex: internals.z, - transform: `translate(${positionAbsoluteOrigin.x}px,${positionAbsoluteOrigin.y}px)`, + transform: `translate(${positionWithOrigin.x}px,${positionWithOrigin.y}px)`, pointerEvents: hasPointerEvents ? 'all' : 'none', visibility: initialized ? 'visible' : 'hidden', ...node.style, @@ -247,8 +242,8 @@ export function NodeWrapper({ id={id} data={node.data} type={nodeType} - positionAbsoluteX={positionAbsoluteX} - positionAbsoluteY={positionAbsoluteY} + positionAbsoluteX={positionAbsolute.x} + positionAbsoluteY={positionAbsolute.y} selected={node.selected} isConnectable={isConnectable} sourcePosition={node.sourcePosition} diff --git a/packages/react/src/hooks/useReactFlow.ts b/packages/react/src/hooks/useReactFlow.ts index 31035bd1..524ac67a 100644 --- a/packages/react/src/hooks/useReactFlow.ts +++ b/packages/react/src/hooks/useReactFlow.ts @@ -3,6 +3,7 @@ import { evaluateNodePosition, getElementsToRemove, getOverlappingArea, + isInternalNodeBase, isRectObject, nodeToRect, type Rect, @@ -232,10 +233,18 @@ export function useReactFlow { const { nodeLookup, nodeOrigin } = store.getState(); - const nodeToUse = isNode(node) ? node : nodeLookup.get(node.id)!; - const nodeWithPos = evaluateNodePosition(nodeToUse, nodeLookup, nodeOrigin); + const nodeToUse = isNode(node) ? node : nodeLookup.get(node.id)!; + 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>( diff --git a/packages/react/src/store/index.ts b/packages/react/src/store/index.ts index c38a980f..f2f273e1 100644 --- a/packages/react/src/store/index.ts +++ b/packages/react/src/store/index.ts @@ -7,7 +7,7 @@ import { panBy as panBySystem, updateNodeInternals as updateNodeInternalsSystem, updateConnectionLookup, - handleParentExpand, + handleExpandParent, NodeChange, EdgeSelectionChange, NodeSelectionChange, @@ -155,7 +155,7 @@ const createRFStore = ({ }); if (expandParentNodes.length > 0) { - const parentExpandChanges = handleParentExpand(expandParentNodes, nodeLookup, parentLookup); + const parentExpandChanges = handleExpandParent(expandParentNodes, nodeLookup, parentLookup); changes.push(...parentExpandChanges); } diff --git a/packages/system/src/utils/general.ts b/packages/system/src/utils/general.ts index e76d24a4..dff79096 100644 --- a/packages/system/src/utils/general.ts +++ b/packages/system/src/utils/general.ts @@ -234,14 +234,8 @@ export function evaluateNodePosition( node: NodeBase | InternalNodeBase, nodeLookup: NodeLookup, nodeOrigin: NodeOrigin = [0, 0] -): InternalNodeBase | null { - const internalNode = nodeLookup.get(node.id); - - if (!internalNode) { - return null; - } - - let parentId = internalNode.parentId; +): XYPosition { + let parentId = node.parentId; const positionAbsolute = { ...node.position }; while (parentId) { @@ -257,11 +251,5 @@ export function evaluateNodePosition( } } - return { - ...internalNode, - internals: { - ...internalNode.internals, - positionAbsolute, - }, - }; + return positionAbsolute; } diff --git a/packages/system/src/utils/store.ts b/packages/system/src/utils/store.ts index 11826898..5afa4787 100644 --- a/packages/system/src/utils/store.ts +++ b/packages/system/src/utils/store.ts @@ -159,36 +159,38 @@ function calculateXYZPosition( type ExpandParentNode = NodeType & { parentId: string; expandParent: true }; -export function handleParentExpand( +export function handleExpandParent( nodes: ExpandParentNode[], nodeLookup: NodeLookup, parentLookup: ParentLookup ): (NodeDimensionChange | NodePositionChange)[] { const changes: (NodeDimensionChange | NodePositionChange)[] = []; - const chilNodeRects = new Map(); + const childNodeRects = new Map(); + // determine the expanded rectangle the child nodes would take for each parent for (const node of nodes) { - const parentNode = nodeLookup.get(node.parentId); - if (!parentNode) { + const parent = nodeLookup.get(node.parentId); + if (!parent) { 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)); - chilNodeRects.set(node.parentId, expandedRect); + childNodeRects.set(node.parentId, { expandedRect, parent }); } - if (chilNodeRects.size > 0) { - chilNodeRects.forEach((rect, id) => { - const origParent = nodeLookup.get(id)!; - const { position } = getNodePositionWithOrigin(origParent, origParent.origin); - const dimensions = getNodeDimensions(origParent); + if (childNodeRects.size > 0) { + childNodeRects.forEach(({ expandedRect, parent }, parentId) => { + // determine the position & dimensions of the parent + const { position } = getNodePositionWithOrigin(parent, parent.origin); + const dimensions = getNodeDimensions(parent); - let xChange = rect.x < position.x ? Math.round(Math.abs(position.x - rect.x)) : 0; - let yChange = rect.y < position.y ? Math.round(Math.abs(position.y - rect.y)) : 0; + // determine how much the parent expands by moving the position + 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) { changes.push({ - id, + id: parentId, type: 'position', position: { 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) => { if (!nodes.some((n) => n.id === childNode.id)) { 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({ - id, + id: parentId, type: 'dimensions', resizing: true, dimensions: { - width: Math.max(dimensions.width, Math.round(rect.width)), - height: Math.max(dimensions.height, Math.round(rect.height)), + width: Math.max(dimensions.width, Math.round(expandedRect.width)), + height: Math.max(dimensions.height, Math.round(expandedRect.height)), }, }); } @@ -301,7 +305,7 @@ export function updateNodeInternals( }); if (parentExpandNodes.length > 0) { - const parentExpandChanges = handleParentExpand(parentExpandNodes, nodeLookup, parentLookup); + const parentExpandChanges = handleExpandParent(parentExpandNodes, nodeLookup, parentLookup); changes.push(...parentExpandChanges); }