From 72d775b8ebc1c6996c581cfe8c297aa93fa89f58 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Thu, 11 Apr 2024 15:14:39 +0200 Subject: [PATCH 01/11] simplified resize events --- .../NodeResizer/NodeResizeControl.tsx | 4 +- .../plugins/NodeResizer/ResizeControl.svelte | 36 ++- packages/system/src/xyresizer/XYResizer.ts | 285 +++++++++--------- 3 files changed, 163 insertions(+), 162 deletions(-) diff --git a/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx b/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx index 2052c98b..4ee8ff4a 100644 --- a/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx +++ b/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx @@ -66,7 +66,7 @@ function ResizeControl({ const changes: NodeChange[] = []; - if (change.isXPosChange || change.isYPosChange) { + if (change.x !== undefined && change.y !== undefined) { const positionChange: NodePositionChange = { id, type: 'position', @@ -79,7 +79,7 @@ function ResizeControl({ changes.push(positionChange); } - if (change.isWidthChange || change.isHeightChange) { + if (change.width !== undefined && change.height !== undefined) { const dimensionChange: NodeDimensionChange = { id, type: 'dimensions', diff --git a/packages/svelte/src/lib/plugins/NodeResizer/ResizeControl.svelte b/packages/svelte/src/lib/plugins/NodeResizer/ResizeControl.svelte index c346bd53..1e5f39e2 100644 --- a/packages/svelte/src/lib/plugins/NodeResizer/ResizeControl.svelte +++ b/packages/svelte/src/lib/plugins/NodeResizer/ResizeControl.svelte @@ -70,23 +70,27 @@ }, onChange: (change: XYResizerChange, childChanges: XYResizerChildChange[]) => { const node = $nodeLookup.get(id)?.internals.userNode; - if (node) { - node.height = change.isHeightChange ? change.height : node.height; - node.width = change.isWidthChange ? change.width : node.width; - node.position = - change.isXPosChange || change.isYPosChange - ? { x: change.x, y: change.y } - : node.position; - - for (const childChange of childChanges) { - const childNode = $nodeLookup.get(childChange.id)?.internals.userNode; - if (childNode) { - childNode.position = childChange.position; - } - } - - $nodes = $nodes; + if (!node) { + return; } + + if (change.x !== undefined && change.y !== undefined) { + node.position = { x: change.x, y: change.y }; + } + + if (change.width !== undefined && change.height !== undefined) { + node.width = change.width; + node.height = change.height; + } + + for (const childChange of childChanges) { + const childNode = $nodeLookup.get(childChange.id)?.internals.userNode; + if (childNode) { + childNode.position = childChange.position; + } + } + + $nodes = $nodes; } }); } diff --git a/packages/system/src/xyresizer/XYResizer.ts b/packages/system/src/xyresizer/XYResizer.ts index 50a3e4aa..abce3e14 100644 --- a/packages/system/src/xyresizer/XYResizer.ts +++ b/packages/system/src/xyresizer/XYResizer.ts @@ -15,19 +15,13 @@ const initStartValues = { aspectRatio: 1, }; -const initChange = { - x: 0, - y: 0, - width: 0, - height: 0, - isXPosChange: false, - isYPosChange: false, - isWidthChange: false, - isHeightChange: false, +export type XYResizerChange = { + x?: number; + y?: number; + width?: number; + height?: number; }; -export type XYResizerChange = typeof initChange; - export type XYResizerChildChange = { id: string; position: XYPosition; @@ -117,158 +111,161 @@ export function XYResizer({ domNode, nodeId, getStoreItems, onChange }: XYResize const { nodeLookup, transform, snapGrid, snapToGrid, nodeOrigin } = getStoreItems(); node = nodeLookup.get(nodeId); - if (node) { - const { xSnapped, ySnapped } = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid }); + if (!node) { + return; + } - prevValues = { - width: node.measured?.width ?? 0, - height: node.measured?.height ?? 0, - x: node.position.x ?? 0, - y: node.position.y ?? 0, - }; + const { xSnapped, ySnapped } = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid }); - startValues = { - ...prevValues, - pointerX: xSnapped, - pointerY: ySnapped, - aspectRatio: prevValues.width / prevValues.height, - }; + prevValues = { + width: node.measured?.width ?? 0, + height: node.measured?.height ?? 0, + x: node.position.x ?? 0, + y: node.position.y ?? 0, + }; - parentNode = undefined; - if (node.extent === 'parent' || node.expandParent) { - parentNode = nodeLookup.get(node.parentId!); - if (parentNode && node.extent === 'parent') { - parentExtent = nodeToParentExtent(parentNode); - } + startValues = { + ...prevValues, + pointerX: xSnapped, + pointerY: ySnapped, + aspectRatio: prevValues.width / prevValues.height, + }; + + parentNode = undefined; + if (node.extent === 'parent' || node.expandParent) { + parentNode = nodeLookup.get(node.parentId!); + if (parentNode && node.extent === 'parent') { + parentExtent = nodeToParentExtent(parentNode); } + } - // Collect all child nodes to correct their relative positions when top/left changes - // Determine largest minimal extent the parent node is allowed to resize to - childNodes = []; - childExtent = undefined; + // Collect all child nodes to correct their relative positions when top/left changes + // Determine largest minimal extent the parent node is allowed to resize to + childNodes = []; + childExtent = undefined; - for (const [childId, child] of nodeLookup) { - if (child.parentId === nodeId) { - childNodes.push({ - id: childId, - position: { ...child.position }, - extent: child.extent, - }); + for (const [childId, child] of nodeLookup) { + if (child.parentId === nodeId) { + childNodes.push({ + id: childId, + position: { ...child.position }, + extent: child.extent, + }); - if (child.extent === 'parent' || child.expandParent) { - const extent = nodeToChildExtent(child, node!, child.origin ?? nodeOrigin); + if (child.extent === 'parent' || child.expandParent) { + const extent = nodeToChildExtent(child, node!, child.origin ?? nodeOrigin); - if (childExtent) { - childExtent = [ - [Math.min(extent[0][0], childExtent[0][0]), Math.min(extent[0][1], childExtent[0][1])], - [Math.max(extent[1][0], childExtent[1][0]), Math.max(extent[1][1], childExtent[1][1])], - ]; - } else { - childExtent = extent; - } + if (childExtent) { + childExtent = [ + [Math.min(extent[0][0], childExtent[0][0]), Math.min(extent[0][1], childExtent[0][1])], + [Math.max(extent[1][0], childExtent[1][0]), Math.max(extent[1][1], childExtent[1][1])], + ]; + } else { + childExtent = extent; } } } - - onResizeStart?.(event, { ...prevValues }); } + + onResizeStart?.(event, { ...prevValues }); }) .on('drag', (event: ResizeDragEvent) => { const { transform, snapGrid, snapToGrid, nodeOrigin: storeNodeOrigin } = getStoreItems(); const pointerPosition = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid }); const childChanges: XYResizerChildChange[] = []; - if (node) { - const { x: prevX, y: prevY, width: prevWidth, height: prevHeight } = prevValues; - const change = { ...initChange }; - const nodeOrigin = node.origin ?? storeNodeOrigin; - - const { width, height, x, y } = getDimensionsAfterResize( - startValues, - controlDirection, - pointerPosition, - boundaries, - keepAspectRatio, - nodeOrigin, - parentExtent, - childExtent - ); - - const isWidthChange = width !== prevWidth; - const isHeightChange = height !== prevHeight; - - const isXPosChange = x !== prevX && isWidthChange; - const isYPosChange = y !== prevY && isHeightChange; - - if (isXPosChange || isYPosChange || nodeOrigin[0] === 1 || nodeOrigin[1] == 1) { - change.isXPosChange = isXPosChange; - change.isYPosChange = isYPosChange; - change.x = isXPosChange ? x : prevX; - change.y = isYPosChange ? y : prevY; - - prevValues.x = change.x; - prevValues.y = change.y; - - // Fix expandParent when resizing from top/left - if (parentNode && node.expandParent) { - if (change.x < 0) { - prevValues.x = 0; - startValues.x = startValues.x - change.x; - } - - if (change.y < 0) { - prevValues.y = 0; - startValues.y = startValues.y - change.y; - } - } - - if (childNodes.length > 0) { - const xChange = x - prevX; - const yChange = y - prevY; - - for (const childNode of childNodes) { - childNode.position = { - x: childNode.position.x - xChange + nodeOrigin[0] * (width - prevWidth), - y: childNode.position.y - yChange + nodeOrigin[1] * (height - prevHeight), - }; - childChanges.push(childNode); - } - } - } - - if (isWidthChange || isHeightChange) { - change.isWidthChange = isWidthChange; - change.isHeightChange = isHeightChange; - change.width = width; - change.height = height; - prevValues.width = change.width; - prevValues.height = change.height; - } - - if (!change.isXPosChange && !change.isYPosChange && !isWidthChange && !isHeightChange) { - return; - } - - const direction = getResizeDirection({ - width: prevValues.width, - prevWidth, - height: prevValues.height, - prevHeight, - affectsX: controlDirection.affectsX, - affectsY: controlDirection.affectsY, - }); - - const nextValues = { ...prevValues, direction }; - - const callResize = shouldResize?.(event, nextValues); - - if (callResize === false) { - return; - } - - onResize?.(event, nextValues); - onChange(change, childChanges); + if (!node) { + return; } + const { x: prevX, y: prevY, width: prevWidth, height: prevHeight } = prevValues; + const change: XYResizerChange = {}; + const nodeOrigin = node.origin ?? storeNodeOrigin; + + const { width, height, x, y } = getDimensionsAfterResize( + startValues, + controlDirection, + pointerPosition, + boundaries, + keepAspectRatio, + nodeOrigin, + parentExtent, + childExtent + ); + + const isWidthChange = width !== prevWidth; + const isHeightChange = height !== prevHeight; + + const isXPosChange = x !== prevX && isWidthChange; + const isYPosChange = y !== prevY && isHeightChange; + + if (!isXPosChange && !isYPosChange && !isWidthChange && !isHeightChange) { + return; + } + + console.log(isXPosChange, x, isYPosChange, y); + + if (isXPosChange || isYPosChange || nodeOrigin[0] === 1 || nodeOrigin[1] == 1) { + change.x = isXPosChange ? x : prevValues.x; + change.y = isYPosChange ? y : prevValues.y; + + prevValues.x = change.x; + prevValues.y = change.y; + + // Fix expandParent when resizing from top/left + if (parentNode && node.expandParent) { + if (change.x && change.x < 0) { + prevValues.x = 0; + startValues.x = startValues.x - change.x; + } + + if (change.y && change.y < 0) { + prevValues.y = 0; + startValues.y = startValues.y - change.y; + } + } + + if (childNodes.length > 0) { + const xChange = x - prevX; + const yChange = y - prevY; + + for (const childNode of childNodes) { + childNode.position = { + x: childNode.position.x - xChange + nodeOrigin[0] * (width - prevWidth), + y: childNode.position.y - yChange + nodeOrigin[1] * (height - prevHeight), + }; + childChanges.push(childNode); + } + } + } + + if (isWidthChange || isHeightChange) { + change.width = isWidthChange ? width : prevValues.width; + change.height = isHeightChange ? height : prevValues.height; + prevValues.width = change.width; + prevValues.height = change.height; + } + + console.log(change); + + const direction = getResizeDirection({ + width: prevValues.width, + prevWidth, + height: prevValues.height, + prevHeight, + affectsX: controlDirection.affectsX, + affectsY: controlDirection.affectsY, + }); + + const nextValues = { ...prevValues, direction }; + + const callResize = shouldResize?.(event, nextValues); + + if (callResize === false) { + return; + } + + onResize?.(event, nextValues); + onChange(change, childChanges); }) .on('end', (event: ResizeDragEvent) => { onResizeEnd?.(event, { ...prevValues }); From 256fad3053cfd0fb0792ba78ccdcdb061f90f377 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Thu, 11 Apr 2024 15:17:36 +0200 Subject: [PATCH 02/11] removed consolelog --- packages/system/src/xyresizer/XYResizer.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/system/src/xyresizer/XYResizer.ts b/packages/system/src/xyresizer/XYResizer.ts index abce3e14..093bdf94 100644 --- a/packages/system/src/xyresizer/XYResizer.ts +++ b/packages/system/src/xyresizer/XYResizer.ts @@ -202,8 +202,6 @@ export function XYResizer({ domNode, nodeId, getStoreItems, onChange }: XYResize return; } - console.log(isXPosChange, x, isYPosChange, y); - if (isXPosChange || isYPosChange || nodeOrigin[0] === 1 || nodeOrigin[1] == 1) { change.x = isXPosChange ? x : prevValues.x; change.y = isYPosChange ? y : prevValues.y; @@ -245,8 +243,6 @@ export function XYResizer({ domNode, nodeId, getStoreItems, onChange }: XYResize prevValues.height = change.height; } - console.log(change); - const direction = getResizeDirection({ width: prevValues.width, prevWidth, From 979fe7d56539414d03b79b77eea1b1c3d9ae3192 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Thu, 11 Apr 2024 18:34:17 +0200 Subject: [PATCH 03/11] broken parent expand on resize but almost there --- .../NodeResizer/NodeResizeControl.tsx | 40 ++++++++++++++++--- packages/system/src/utils/store.ts | 39 +++++++++--------- 2 files changed, 53 insertions(+), 26 deletions(-) diff --git a/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx b/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx index 4ee8ff4a..725639b2 100644 --- a/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx +++ b/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx @@ -9,11 +9,15 @@ import { type NodeChange, type NodeDimensionChange, type NodePositionChange, + handleParentExpand, } from '@xyflow/system'; import { useStoreApi } from '../../hooks/useStore'; import { useNodeId } from '../../contexts/NodeIdContext'; import type { ResizeControlProps, ResizeControlLineProps } from './types'; +import { InternalNode } from '../../types'; + +type InternalNodeWithParentExpand = InternalNode & { expandParent: true; parentId: string }; function ResizeControl({ nodeId, @@ -62,20 +66,44 @@ function ResizeControl({ }; }, onChange: (change: XYResizerChange, childChanges: XYResizerChildChange[]) => { - const { triggerNodeChanges } = store.getState(); + const { triggerNodeChanges, nodeLookup } = store.getState(); const changes: NodeChange[] = []; + const newPosition = { x: change.x, y: change.y }; - if (change.x !== undefined && change.y !== undefined) { - const positionChange: NodePositionChange = { - id, - type: 'position', + const node = nodeLookup.get(id); + if (node && node.expandParent && node.parentId) { + const nodeWithChange = { + ...node, position: { x: change.x, y: change.y, }, - }; + width: change.width, + height: change.height, + measured: { + width: change.width ?? node.measured?.width, + height: change.height ?? node.measured?.height, + }, + } as InternalNodeWithParentExpand; + const parentExpandChanges = handleParentExpand([nodeWithChange], nodeLookup); + if (parentExpandChanges.length > 0) { + newPosition.x = change.x && change.x < 0 ? 0 : change.x; + newPosition.y = change.y && change.y < 0 ? 0 : change.y; + } else { + console.log('there was no parent expand'); + } + changes.push(...parentExpandChanges); + } + + if (change.x !== undefined && change.y !== undefined) { + console.log(newPosition); + const positionChange: NodePositionChange = { + id, + type: 'position', + position: { ...newPosition } as { x: number; y: number }, + }; changes.push(positionChange); } diff --git a/packages/system/src/utils/store.ts b/packages/system/src/utils/store.ts index 0d7f2645..11b5e3c6 100644 --- a/packages/system/src/utils/store.ts +++ b/packages/system/src/utils/store.ts @@ -145,25 +145,25 @@ function calculateXYZPosition( ); } +type NodeWithExpandParent = NodeType & { parentId: string; expandParent: true }; + export function handleParentExpand( - nodes: InternalNodeBase[], + nodes: NodeWithExpandParent[], nodeLookup: NodeLookup ): (NodeDimensionChange | NodePositionChange)[] { const changes: (NodeDimensionChange | NodePositionChange)[] = []; const chilNodeRects = new Map(); - nodes.forEach((node) => { - const parentId = node.parentId; - if (node.expandParent && parentId) { - const parentNode = nodeLookup.get(parentId); - - if (parentNode) { - const parentRect = chilNodeRects.get(parentId) || nodeToRect(parentNode, node.origin); - const expandedRect = getBoundsOfRects(parentRect, nodeToRect(node, node.origin)); - chilNodeRects.set(parentId, expandedRect); - } + for (const node of nodes) { + const parentNode = nodeLookup.get(node.parentId); + if (!parentNode) { + continue; } - }); + + const parentRect = chilNodeRects.get(node.parentId) || nodeToRect(parentNode, node.origin); + const expandedRect = getBoundsOfRects(parentRect, nodeToRect(node, node.origin)); + chilNodeRects.set(node.parentId, expandedRect); + } if (chilNodeRects.size > 0) { chilNodeRects.forEach((rect, id) => { @@ -193,9 +193,8 @@ export function handleParentExpand( height: dimensions.height + yChange, }, }); - - // @todo we need to reset child node positions if < 0 - } else if (dimensions.width < rect.width || dimensions.height < rect.height) { + } + if (dimensions.width < rect.width || dimensions.height < rect.height) { changes.push({ id, type: 'dimensions', @@ -229,7 +228,7 @@ export function updateNodeInternals( const style = window.getComputedStyle(viewportNode); const { m22: zoom } = new window.DOMMatrixReadOnly(style.transform); // in this array we collect nodes, that might trigger changes (like expanding parent) - const triggerChangeNodes: NodeType[] = []; + const parentExpandNodes: NodeWithExpandParent[] = []; updates.forEach((update) => { const node = nodeLookup.get(update.id); @@ -275,16 +274,16 @@ export function updateNodeInternals( dimensions, }); - if (newNode.expandParent) { - triggerChangeNodes.push(newNode); + if (newNode.expandParent && newNode.parentId) { + parentExpandNodes.push(newNode as NodeWithExpandParent); } } } } }); - if (triggerChangeNodes.length > 0) { - const parentExpandChanges = handleParentExpand(triggerChangeNodes, nodeLookup); + if (parentExpandNodes.length > 0) { + const parentExpandChanges = handleParentExpand(parentExpandNodes, nodeLookup); changes.push(...parentExpandChanges); } From c5f394e1f77ce6ed7a6487d74b07dd4b1c22a488 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Mon, 15 Apr 2024 15:58:25 +0200 Subject: [PATCH 04/11] use evaluateNodePosition for parentExpand --- .../NodeResizer/NodeResizeControl.tsx | 28 +++++++--------- packages/react/src/store/index.ts | 15 +++++---- packages/system/src/utils/store.ts | 32 +++++++------------ 3 files changed, 30 insertions(+), 45 deletions(-) diff --git a/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx b/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx index 725639b2..6ca54676 100644 --- a/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx +++ b/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx @@ -10,12 +10,13 @@ import { type NodeDimensionChange, type NodePositionChange, handleParentExpand, + evaluateNodePosition, } from '@xyflow/system'; import { useStoreApi } from '../../hooks/useStore'; import { useNodeId } from '../../contexts/NodeIdContext'; import type { ResizeControlProps, ResizeControlLineProps } from './types'; -import { InternalNode } from '../../types'; +import { InternalNode, Node } from '../../types'; type InternalNodeWithParentExpand = InternalNode & { expandParent: true; parentId: string }; @@ -73,32 +74,25 @@ function ResizeControl({ const node = nodeLookup.get(id); if (node && node.expandParent && node.parentId) { - const nodeWithChange = { + const nodeWithChange: Node = { ...node, position: { - x: change.x, - y: change.y, + x: change.x ?? node.position.x, + y: change.y ?? node.position.y, }, width: change.width, height: change.height, - measured: { - width: change.width ?? node.measured?.width, - height: change.height ?? node.measured?.height, - }, - } as InternalNodeWithParentExpand; + }; - const parentExpandChanges = handleParentExpand([nodeWithChange], nodeLookup); - if (parentExpandChanges.length > 0) { - newPosition.x = change.x && change.x < 0 ? 0 : change.x; - newPosition.y = change.y && change.y < 0 ? 0 : change.y; - } else { - console.log('there was no parent expand'); - } + const nodeWith = evaluateNodePosition(nodeWithChange, nodeLookup) as InternalNodeWithParentExpand; + + const parentExpandChanges = handleParentExpand([nodeWith], nodeLookup); changes.push(...parentExpandChanges); + newPosition.x = change.x ? Math.max(0, change.x) : undefined; + newPosition.y = change.y ? Math.max(0, change.y) : undefined; } if (change.x !== undefined && change.y !== undefined) { - console.log(newPosition); const positionChange: NodePositionChange = { id, type: 'position', diff --git a/packages/react/src/store/index.ts b/packages/react/src/store/index.ts index 7ce263c9..f02e74f4 100644 --- a/packages/react/src/store/index.ts +++ b/packages/react/src/store/index.ts @@ -117,7 +117,8 @@ const createRFStore = ({ }, updateNodePositions: (nodeDragItems, dragging = false) => { const { nodeLookup } = get(); - const triggerChangeNodes: InternalNode[] = []; + type ExpandParentInternalNode = InternalNode & { parentId: string; expandParent: true }; + const expandParentNodes: ExpandParentInternalNode[] = []; const changes: NodeChange[] = nodeDragItems.map((node) => { // @todo add expandParent to drag item so that we can get rid of the look up here @@ -129,15 +130,15 @@ const createRFStore = ({ dragging, }; - if (internalNode?.expandParent && change.position) { - triggerChangeNodes.push({ + if (internalNode?.expandParent && internalNode?.parentId && change.position) { + expandParentNodes.push({ ...internalNode, - position: change.position, + position: { ...node.position }, internals: { ...internalNode.internals, positionAbsolute: node.internals.positionAbsolute, }, - }); + } as ExpandParentInternalNode); change.position.x = Math.max(0, change.position.x); change.position.y = Math.max(0, change.position.y); @@ -146,8 +147,8 @@ const createRFStore = ({ return change; }); - if (triggerChangeNodes.length > 0) { - const parentExpandChanges = handleParentExpand(triggerChangeNodes, nodeLookup); + if (expandParentNodes.length > 0) { + const parentExpandChanges = handleParentExpand(expandParentNodes, nodeLookup); changes.push(...parentExpandChanges); } diff --git a/packages/system/src/utils/store.ts b/packages/system/src/utils/store.ts index 11b5e3c6..3d8727c2 100644 --- a/packages/system/src/utils/store.ts +++ b/packages/system/src/utils/store.ts @@ -145,10 +145,10 @@ function calculateXYZPosition( ); } -type NodeWithExpandParent = NodeType & { parentId: string; expandParent: true }; +type ExpandParentNode = NodeType & { parentId: string; expandParent: true }; export function handleParentExpand( - nodes: NodeWithExpandParent[], + nodes: ExpandParentNode[], nodeLookup: NodeLookup ): (NodeDimensionChange | NodePositionChange)[] { const changes: (NodeDimensionChange | NodePositionChange)[] = []; @@ -160,7 +160,7 @@ export function handleParentExpand( continue; } - const parentRect = chilNodeRects.get(node.parentId) || nodeToRect(parentNode, node.origin); + const parentRect = chilNodeRects.get(node.parentId) ?? nodeToRect(parentNode, node.origin); const expandedRect = getBoundsOfRects(parentRect, nodeToRect(node, node.origin)); chilNodeRects.set(node.parentId, expandedRect); } @@ -171,10 +171,9 @@ export function handleParentExpand( const { position } = getNodePositionWithOrigin(origParent, origParent.origin); const dimensions = getNodeDimensions(origParent); - if (rect.x < position.x || rect.y < position.y) { - const xChange = Math.round(Math.abs(position.x - rect.x)); - const yChange = Math.round(Math.abs(position.y - rect.y)); - + 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; + if (xChange > 0 || yChange > 0) { changes.push({ id, type: 'position', @@ -183,25 +182,16 @@ export function handleParentExpand( y: position.y - yChange, }, }); - - changes.push({ - id, - type: 'dimensions', - resizing: true, - dimensions: { - width: dimensions.width + xChange, - height: dimensions.height + yChange, - }, - }); } + if (dimensions.width < rect.width || dimensions.height < rect.height) { changes.push({ id, type: 'dimensions', resizing: true, dimensions: { - width: Math.max(dimensions.width, rect.width), - height: Math.max(dimensions.height, rect.height), + width: Math.max(dimensions.width, Math.round(rect.width)), + height: Math.max(dimensions.height, Math.round(rect.height)), }, }); } @@ -228,7 +218,7 @@ export function updateNodeInternals( const style = window.getComputedStyle(viewportNode); const { m22: zoom } = new window.DOMMatrixReadOnly(style.transform); // in this array we collect nodes, that might trigger changes (like expanding parent) - const parentExpandNodes: NodeWithExpandParent[] = []; + const parentExpandNodes: ExpandParentNode[] = []; updates.forEach((update) => { const node = nodeLookup.get(update.id); @@ -275,7 +265,7 @@ export function updateNodeInternals( }); if (newNode.expandParent && newNode.parentId) { - parentExpandNodes.push(newNode as NodeWithExpandParent); + parentExpandNodes.push(newNode as ExpandParentNode); } } } From ccf6578824a9832ebb2fd2c690abeb23cd786c79 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Mon, 15 Apr 2024 17:03:03 +0200 Subject: [PATCH 05/11] added parentLookup --- .../src/components/NodeWrapper/index.tsx | 13 ++--- packages/react/src/store/index.ts | 4 +- packages/react/src/store/initialState.ts | 4 +- packages/react/src/types/store.ts | 1 + .../svelte/src/lib/store/initial-store.ts | 6 ++- packages/svelte/src/lib/store/utils.ts | 5 +- packages/system/src/utils/store.ts | 47 ++++++++++++------- 7 files changed, 49 insertions(+), 31 deletions(-) diff --git a/packages/react/src/components/NodeWrapper/index.tsx b/packages/react/src/components/NodeWrapper/index.tsx index 18c2d314..c26c91c2 100644 --- a/packages/react/src/components/NodeWrapper/index.tsx +++ b/packages/react/src/components/NodeWrapper/index.tsx @@ -42,7 +42,7 @@ export function NodeWrapper({ nodeOrigin, onError, }: NodeWrapperProps) { - const { node, positionAbsoluteX, positionAbsoluteY, zIndex, isParent } = useStore((s) => { + const { node, positionAbsoluteX, positionAbsoluteY, internals } = useStore((s) => { const node = s.nodeLookup.get(id)! as InternalNode; const positionAbsolute = nodeExtent @@ -55,8 +55,9 @@ export function NodeWrapper({ // so we we need to force a re-render when some change positionAbsoluteX: positionAbsolute.x, positionAbsoluteY: positionAbsolute.y, - zIndex: node.internals.z, - isParent: node.internals.isParent, + internals: node.internals, + // zIndex: node.internals.z, + // isParent: node.internals.isParent, }; }, shallow); @@ -213,14 +214,14 @@ export function NodeWrapper({ { selected: node.selected, selectable: isSelectable, - parent: isParent, + parent: internals.isParent, draggable: isDraggable, dragging, }, ])} ref={nodeRef} style={{ - zIndex, + zIndex: internals.z, transform: `translate(${positionAbsoluteOrigin.x}px,${positionAbsoluteOrigin.y}px)`, pointerEvents: hasPointerEvents ? 'all' : 'none', visibility: initialized ? 'visible' : 'hidden', @@ -254,7 +255,7 @@ export function NodeWrapper({ targetPosition={node.targetPosition} dragging={dragging} dragHandle={node.dragHandle} - zIndex={zIndex} + zIndex={internals.z} {...nodeDimensions} /> diff --git a/packages/react/src/store/index.ts b/packages/react/src/store/index.ts index f02e74f4..682915da 100644 --- a/packages/react/src/store/index.ts +++ b/packages/react/src/store/index.ts @@ -38,14 +38,14 @@ const createRFStore = ({ (set, get) => ({ ...getInitialState({ nodes, edges, width, height, fitView, defaultNodes, defaultEdges }), setNodes: (nodes: Node[]) => { - const { nodeLookup, nodeOrigin, elevateNodesOnSelect } = get(); + const { nodeLookup, parentLookup, nodeOrigin, elevateNodesOnSelect } = get(); // setNodes() is called exclusively in response to user actions: // - either when the `` prop is updated in the controlled ReactFlow setup, // - or when the user calls something like `reactFlowInstance.setNodes()` in an uncontrolled ReactFlow setup. // // When this happens, we take the note objects passed by the user and extend them with fields // relevant for internal React Flow operations. - adoptUserNodes(nodes, nodeLookup, { nodeOrigin, elevateNodesOnSelect }); + adoptUserNodes(nodes, nodeLookup, parentLookup, { nodeOrigin, elevateNodesOnSelect }); set({ nodes }); }, diff --git a/packages/react/src/store/initialState.ts b/packages/react/src/store/initialState.ts index cc754976..d3812725 100644 --- a/packages/react/src/store/initialState.ts +++ b/packages/react/src/store/initialState.ts @@ -29,13 +29,14 @@ const getInitialState = ({ fitView?: boolean; } = {}): ReactFlowStore => { const nodeLookup = new Map(); + const parentLookup = new Map(); const connectionLookup = new Map(); const edgeLookup = new Map(); const storeEdges = defaultEdges ?? edges ?? []; const storeNodes = defaultNodes ?? nodes ?? []; updateConnectionLookup(connectionLookup, edgeLookup, storeEdges); - adoptUserNodes(storeNodes, nodeLookup, { + adoptUserNodes(storeNodes, nodeLookup, parentLookup, { nodeOrigin: [0, 0], elevateNodesOnSelect: false, }); @@ -59,6 +60,7 @@ const getInitialState = ({ transform, nodes: storeNodes, nodeLookup, + parentLookup, edges: storeEdges, edgeLookup, connectionLookup, diff --git a/packages/react/src/types/store.ts b/packages/react/src/types/store.ts index 397ca6f1..bd5f5720 100644 --- a/packages/react/src/types/store.ts +++ b/packages/react/src/types/store.ts @@ -54,6 +54,7 @@ export type ReactFlowStore>; + parentLookup: Map[]>; edges: Edge[]; edgeLookup: EdgeLookup; connectionLookup: ConnectionLookup; diff --git a/packages/svelte/src/lib/store/initial-store.ts b/packages/svelte/src/lib/store/initial-store.ts index 7d2b2444..073f206c 100644 --- a/packages/svelte/src/lib/store/initial-store.ts +++ b/packages/svelte/src/lib/store/initial-store.ts @@ -81,7 +81,8 @@ export const getInitialStore = ({ fitView?: boolean; }) => { const nodeLookup: NodeLookup = new Map(); - adoptUserNodes(nodes, nodeLookup, { + const parentLookup = new Map(); + adoptUserNodes(nodes, nodeLookup, parentLookup, { nodeOrigin: [0, 0], elevateNodesOnSelect: false, checkEquality: false @@ -104,8 +105,9 @@ export const getInitialStore = ({ return { flowId: writable(null), - nodes: createNodesStore(nodes, nodeLookup), + nodes: createNodesStore(nodes, nodeLookup, parentLookup), nodeLookup: readable>(nodeLookup), + parentLookup: readable>(parentLookup), edgeLookup: readable>(edgeLookup), visibleNodes: readable([]), edges: createEdgesStore(edges, connectionLookup, edgeLookup), diff --git a/packages/svelte/src/lib/store/utils.ts b/packages/svelte/src/lib/store/utils.ts index 4dabbd33..5fd66cfe 100644 --- a/packages/svelte/src/lib/store/utils.ts +++ b/packages/svelte/src/lib/store/utils.ts @@ -127,7 +127,8 @@ export type NodeStoreOptions = { // The user only passes in relative positions, so we need to calculate the absolute positions based on the parent nodes. export const createNodesStore = ( nodes: Node[], - nodeLookup: NodeLookup + nodeLookup: NodeLookup, + parentLookup: Map ): { subscribe: (this: void, run: Subscriber) => Unsubscriber; update: (this: void, updater: Updater) => void; @@ -141,7 +142,7 @@ export const createNodesStore = ( let elevateNodesOnSelect = true; const _set = (nds: Node[]): Node[] => { - adoptUserNodes(nds, nodeLookup, { + adoptUserNodes(nds, nodeLookup, parentLookup, { elevateNodesOnSelect, defaults, checkEquality: false diff --git a/packages/system/src/utils/store.ts b/packages/system/src/utils/store.ts index 3d8727c2..55aa368b 100644 --- a/packages/system/src/utils/store.ts +++ b/packages/system/src/utils/store.ts @@ -27,7 +27,7 @@ export function updateAbsolutePositions( elevateNodesOnSelect: true, defaults: {}, }, - parentNodeIds?: Set + parentLookup?: Map[]> ) { const selectedNodeZ: number = options?.elevateNodesOnSelect ? 1000 : 0; @@ -38,7 +38,7 @@ export function updateAbsolutePositions( throw new Error(`Parent node ${parentId} not found`); } - if (parentId || node.internals.isParent || parentNodeIds?.has(id)) { + if (parentId || node.internals.isParent || parentLookup?.has(id)) { const parentNode = parentId ? nodeLookup.get(parentId) : null; const { x, y, z } = calculateXYZPosition( node, @@ -47,20 +47,21 @@ export function updateAbsolutePositions( ...node.position, z: (isNumeric(node.zIndex) ? node.zIndex : 0) + (node.selected ? selectedNodeZ : 0), }, - parentNode?.origin || options.nodeOrigin + parentNode?.origin ?? options.nodeOrigin ); const currPosition = node.internals.positionAbsolute; const positionChanged = x !== currPosition.x || y !== currPosition.y; - node.internals.positionAbsolute = positionChanged ? { x, y } : currPosition; - node.internals.z = z; + node.internals = { + ...node.internals, + positionAbsolute: positionChanged ? { x, y } : currPosition, + z, + }; - if (parentNodeIds !== undefined) { - node.internals.isParent = !!parentNodeIds?.has(id); + if (parentLookup !== undefined) { + node.internals.isParent = !!parentLookup.has(id); } - - nodeLookup.set(id, node); } } } @@ -75,6 +76,7 @@ type UpdateNodesOptions = { export function adoptUserNodes( nodes: NodeType[], nodeLookup: Map>, + parentLookup: Map[]>, options: UpdateNodesOptions = { nodeOrigin: [0, 0] as NodeOrigin, elevateNodesOnSelect: true, @@ -84,20 +86,19 @@ export function adoptUserNodes( ) { const tmpLookup = new Map(nodeLookup); nodeLookup.clear(); + parentLookup.clear(); + const selectedNodeZ: number = options?.elevateNodesOnSelect ? 1000 : 0; - const parentNodeIds = new Set(); + // const parentNodeIds = new Set(); nodes.forEach((userNode) => { const currentStoreNode = tmpLookup.get(userNode.id); - if (userNode.parentId) { - parentNodeIds.add(userNode.parentId); - } - + let internalNode = currentStoreNode!; if (options.checkEquality && userNode === currentStoreNode?.internals.userNode) { nodeLookup.set(userNode.id, currentStoreNode); } else { - nodeLookup.set(userNode.id, { + internalNode = { ...options.defaults, ...userNode, measured: { @@ -111,12 +112,22 @@ export function adoptUserNodes( userNode, isParent: false, }, - }); + }; + nodeLookup.set(userNode.id, internalNode); + } + + if (userNode.parentId) { + const childNodes = parentLookup.get(userNode.parentId); + if (childNodes) { + childNodes.push(internalNode); + } else { + parentLookup.set(userNode.parentId, [internalNode]); + } } }); - if (parentNodeIds.size > 0) { - updateAbsolutePositions(nodeLookup, options, parentNodeIds); + if (parentLookup.size > 0) { + updateAbsolutePositions(nodeLookup, options, parentLookup); } } From 87086e99975dfbdd0b4424c640e7075cf94c07dd Mon Sep 17 00:00:00 2001 From: peterkogo Date: Mon, 15 Apr 2024 17:23:14 +0200 Subject: [PATCH 06/11] added correct expandParent offset of children --- .../NodeResizer/NodeResizeControl.tsx | 4 ++-- packages/react/src/store/index.ts | 13 ++++++++--- packages/svelte/src/lib/store/index.ts | 1 + packages/system/src/types/nodes.ts | 1 + packages/system/src/utils/store.ts | 23 ++++++++++++++++--- 5 files changed, 34 insertions(+), 8 deletions(-) diff --git a/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx b/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx index 6ca54676..b661ebde 100644 --- a/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx +++ b/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx @@ -67,7 +67,7 @@ function ResizeControl({ }; }, onChange: (change: XYResizerChange, childChanges: XYResizerChildChange[]) => { - const { triggerNodeChanges, nodeLookup } = store.getState(); + const { triggerNodeChanges, nodeLookup, parentLookup } = store.getState(); const changes: NodeChange[] = []; const newPosition = { x: change.x, y: change.y }; @@ -86,7 +86,7 @@ function ResizeControl({ const nodeWith = evaluateNodePosition(nodeWithChange, nodeLookup) as InternalNodeWithParentExpand; - const parentExpandChanges = handleParentExpand([nodeWith], nodeLookup); + const parentExpandChanges = handleParentExpand([nodeWith], nodeLookup, parentLookup); changes.push(...parentExpandChanges); 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/store/index.ts b/packages/react/src/store/index.ts index 682915da..c38a980f 100644 --- a/packages/react/src/store/index.ts +++ b/packages/react/src/store/index.ts @@ -76,6 +76,7 @@ const createRFStore = ({ onNodesChange, fitView, nodeLookup, + parentLookup, fitViewOnInit, fitViewDone, fitViewOnInitOptions, @@ -84,7 +85,13 @@ const createRFStore = ({ debug, } = get(); - const { changes, updatedInternals } = updateNodeInternalsSystem(updates, nodeLookup, domNode, nodeOrigin); + const { changes, updatedInternals } = updateNodeInternalsSystem( + updates, + nodeLookup, + parentLookup, + domNode, + nodeOrigin + ); if (!updatedInternals) { return; @@ -116,7 +123,7 @@ const createRFStore = ({ } }, updateNodePositions: (nodeDragItems, dragging = false) => { - const { nodeLookup } = get(); + const { nodeLookup, parentLookup } = get(); type ExpandParentInternalNode = InternalNode & { parentId: string; expandParent: true }; const expandParentNodes: ExpandParentInternalNode[] = []; @@ -148,7 +155,7 @@ const createRFStore = ({ }); if (expandParentNodes.length > 0) { - const parentExpandChanges = handleParentExpand(expandParentNodes, nodeLookup); + const parentExpandChanges = handleParentExpand(expandParentNodes, nodeLookup, parentLookup); changes.push(...parentExpandChanges); } diff --git a/packages/svelte/src/lib/store/index.ts b/packages/svelte/src/lib/store/index.ts index c808d2af..07d4ab76 100644 --- a/packages/svelte/src/lib/store/index.ts +++ b/packages/svelte/src/lib/store/index.ts @@ -83,6 +83,7 @@ export function createStore({ const { changes, updatedInternals } = updateNodeInternalsSystem( updates, nodeLookup, + get(store.parentLookup), get(store.domNode), get(store.nodeOrigin) ); diff --git a/packages/system/src/types/nodes.ts b/packages/system/src/types/nodes.ts index d802dad8..7e6d7f14 100644 --- a/packages/system/src/types/nodes.ts +++ b/packages/system/src/types/nodes.ts @@ -144,3 +144,4 @@ export type NodeHandle = Optional; export type Align = 'center' | 'start' | 'end'; export type NodeLookup = Map; +export type ParentLookup = Map; diff --git a/packages/system/src/utils/store.ts b/packages/system/src/utils/store.ts index 55aa368b..07044503 100644 --- a/packages/system/src/utils/store.ts +++ b/packages/system/src/utils/store.ts @@ -15,6 +15,7 @@ import { Rect, NodeDimensionChange, NodePositionChange, + ParentLookup, } from '../types'; import { getDimensions, getHandleBounds } from './dom'; import { getBoundsOfRects, getNodeDimensions, isNumeric, nodeToRect } from './general'; @@ -160,7 +161,8 @@ type ExpandParentNode = NodeType & { parentId: string; expandParent: t export function handleParentExpand( nodes: ExpandParentNode[], - nodeLookup: NodeLookup + nodeLookup: NodeLookup, + parentLookup: ParentLookup ): (NodeDimensionChange | NodePositionChange)[] { const changes: (NodeDimensionChange | NodePositionChange)[] = []; const chilNodeRects = new Map(); @@ -193,6 +195,20 @@ export function handleParentExpand( y: position.y - yChange, }, }); + + const childNodes = parentLookup.get(id); + childNodes?.forEach((childNode) => { + if (!nodes.find((n) => n.id === childNode.id)) { + changes.push({ + id: childNode.id, + type: 'position', + position: { + x: childNode.position.x + xChange, + y: childNode.position.y + yChange, + }, + }); + } + }); } if (dimensions.width < rect.width || dimensions.height < rect.height) { @@ -214,7 +230,8 @@ export function handleParentExpand( export function updateNodeInternals( updates: Map, - nodeLookup: Map, + nodeLookup: NodeLookup, + parentLookup: ParentLookup, domNode: HTMLElement | null, nodeOrigin?: NodeOrigin ): { changes: (NodeDimensionChange | NodePositionChange)[]; updatedInternals: boolean } { @@ -284,7 +301,7 @@ export function updateNodeInternals( }); if (parentExpandNodes.length > 0) { - const parentExpandChanges = handleParentExpand(parentExpandNodes, nodeLookup); + const parentExpandChanges = handleParentExpand(parentExpandNodes, nodeLookup, parentLookup); changes.push(...parentExpandChanges); } From 81bdaf6b68f56e3c2bed9ca0c7ebfbeabbdd6f78 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Mon, 15 Apr 2024 17:31:48 +0200 Subject: [PATCH 07/11] prevent falsely adjusting currently dragged item on parentExpand --- packages/system/src/utils/store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/system/src/utils/store.ts b/packages/system/src/utils/store.ts index 07044503..11826898 100644 --- a/packages/system/src/utils/store.ts +++ b/packages/system/src/utils/store.ts @@ -198,7 +198,7 @@ export function handleParentExpand( const childNodes = parentLookup.get(id); childNodes?.forEach((childNode) => { - if (!nodes.find((n) => n.id === childNode.id)) { + if (!nodes.some((n) => n.id === childNode.id)) { changes.push({ id: childNode.id, type: 'position', From 714d30b63fb2aac501b81572567ffe1398ed5c17 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Tue, 16 Apr 2024 13:19:44 +0200 Subject: [PATCH 08/11] 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); } From aad6de5359f01826453cbe5a6d95d8358aece120 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Tue, 16 Apr 2024 16:54:38 +0200 Subject: [PATCH 09/11] simplified handleExpandParent, removed isParent, improved some functions --- .../NodeResizer/NodeResizeControl.tsx | 41 +++++---- .../src/components/NodeWrapper/index.tsx | 6 +- packages/react/src/hooks/useReactFlow.ts | 10 ++- packages/react/src/store/index.ts | 23 ++--- .../NodeRenderer/NodeRenderer.svelte | 5 +- packages/system/src/types/nodes.ts | 2 - packages/system/src/utils/general.ts | 18 ++-- packages/system/src/utils/store.ts | 83 ++++++++++--------- packages/system/src/utils/types.ts | 4 + 9 files changed, 101 insertions(+), 91 deletions(-) diff --git a/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx b/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx index 3975ef52..5b405498 100644 --- a/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx +++ b/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx @@ -10,7 +10,8 @@ import { type NodeDimensionChange, type NodePositionChange, handleExpandParent, - evaluateNodePosition, + evaluateAbsolutePosition, + ParentExpandChild, } from '@xyflow/system'; import { useStoreApi } from '../../hooks/useStore'; @@ -67,36 +68,32 @@ function ResizeControl({ }; }, onChange: (change: XYResizerChange, childChanges: XYResizerChildChange[]) => { - const { triggerNodeChanges, nodeLookup, parentLookup } = store.getState(); + const { triggerNodeChanges, nodeLookup, parentLookup, nodeOrigin } = store.getState(); const changes: NodeChange[] = []; const newPosition = { x: change.x, y: change.y }; const node = nodeLookup.get(id); if (node && node.expandParent && node.parentId) { - // 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, + const child: ParentExpandChild = { + id: node.id, + parentId: node.parentId, + rect: { + width: change.width ?? node.measured.width!, + height: change.height ?? node.measured.height!, + ...evaluateAbsolutePosition( + { + x: change.x ?? node.position.x, + y: change.y ?? node.position.y, + }, + node.parentId, + nodeLookup, + node.origin ?? nodeOrigin + ), }, - measured: { - width: change.width, - height: change.height, - }, - width: change.width, - height: change.height, }; - // ...determine its new absolute position... - nodeWithChange.internals = { - ...nodeWithChange.internals, - positionAbsolute: evaluateNodePosition(nodeWithChange, nodeLookup), - }; - - // ... and use it to expand the parent - const parentExpandChanges = handleExpandParent([nodeWithChange], nodeLookup, parentLookup); + const parentExpandChanges = handleExpandParent([child], nodeLookup, parentLookup, nodeOrigin); changes.push(...parentExpandChanges); // when the parent was expanded by the child node, its position will be clamped at 0,0 diff --git a/packages/react/src/components/NodeWrapper/index.tsx b/packages/react/src/components/NodeWrapper/index.tsx index f4d0fc3a..acc0d7fe 100644 --- a/packages/react/src/components/NodeWrapper/index.tsx +++ b/packages/react/src/components/NodeWrapper/index.tsx @@ -42,12 +42,14 @@ export function NodeWrapper({ nodeOrigin, onError, }: NodeWrapperProps) { - const { node, internals } = useStore((s) => { + const { node, internals, isParent } = useStore((s) => { const node = s.nodeLookup.get(id)! as InternalNode; + const isParent = s.parentLookup.has(id); return { node, internals: node.internals, + isParent, }; }, shallow); @@ -209,7 +211,7 @@ export function NodeWrapper({ { selected: node.selected, selectable: isSelectable, - parent: internals.isParent, + parent: isParent, draggable: isDraggable, dragging, }, diff --git a/packages/react/src/hooks/useReactFlow.ts b/packages/react/src/hooks/useReactFlow.ts index 524ac67a..5f3d2acb 100644 --- a/packages/react/src/hooks/useReactFlow.ts +++ b/packages/react/src/hooks/useReactFlow.ts @@ -1,9 +1,8 @@ import { useCallback, useMemo, useRef, useState } from 'react'; import { - evaluateNodePosition, + evaluateAbsolutePosition, getElementsToRemove, getOverlappingArea, - isInternalNodeBase, isRectObject, nodeToRect, type Rect, @@ -233,12 +232,15 @@ export function useReactFlow { const { nodeLookup, nodeOrigin } = store.getState(); + const nodeToUse = isNode(node) ? node : nodeLookup.get(node.id)!; - const positionAbsolute = evaluateNodePosition(nodeToUse, nodeLookup, nodeOrigin); + const position = nodeToUse.parentId + ? evaluateAbsolutePosition(nodeToUse.position, nodeToUse.parentId, nodeLookup, nodeOrigin) + : nodeToUse.position; const nodeWithPosition = { id: nodeToUse.id, - position: positionAbsolute, + position, width: nodeToUse.measured?.width ?? nodeToUse.width, height: nodeToUse.measured?.height ?? nodeToUse.height, data: nodeToUse.data, diff --git a/packages/react/src/store/index.ts b/packages/react/src/store/index.ts index f2f273e1..8b999013 100644 --- a/packages/react/src/store/index.ts +++ b/packages/react/src/store/index.ts @@ -11,6 +11,7 @@ import { NodeChange, EdgeSelectionChange, NodeSelectionChange, + ParentExpandChild, } from '@xyflow/system'; import { applyEdgeChanges, applyNodeChanges, createSelectionChange, getSelectionChanges } from '../utils/changes'; @@ -124,8 +125,7 @@ const createRFStore = ({ }, updateNodePositions: (nodeDragItems, dragging = false) => { const { nodeLookup, parentLookup } = get(); - type ExpandParentInternalNode = InternalNode & { parentId: string; expandParent: true }; - const expandParentNodes: ExpandParentInternalNode[] = []; + const parentExpandChildren: ParentExpandChild[] = []; const changes: NodeChange[] = nodeDragItems.map((node) => { // @todo add expandParent to drag item so that we can get rid of the look up here @@ -138,14 +138,15 @@ const createRFStore = ({ }; if (internalNode?.expandParent && internalNode?.parentId && change.position) { - expandParentNodes.push({ - ...internalNode, - position: { ...node.position }, - internals: { - ...internalNode.internals, - positionAbsolute: node.internals.positionAbsolute, + parentExpandChildren.push({ + id: internalNode.id, + parentId: internalNode.parentId, + rect: { + ...node.internals.positionAbsolute, + width: internalNode.measured.width!, + height: internalNode.measured.height!, }, - } as ExpandParentInternalNode); + }); change.position.x = Math.max(0, change.position.x); change.position.y = Math.max(0, change.position.y); @@ -154,8 +155,8 @@ const createRFStore = ({ return change; }); - if (expandParentNodes.length > 0) { - const parentExpandChanges = handleExpandParent(expandParentNodes, nodeLookup, parentLookup); + if (parentExpandChildren.length > 0) { + const parentExpandChanges = handleExpandParent(parentExpandChildren, nodeLookup, parentLookup); changes.push(...parentExpandChanges); } diff --git a/packages/svelte/src/lib/container/NodeRenderer/NodeRenderer.svelte b/packages/svelte/src/lib/container/NodeRenderer/NodeRenderer.svelte index c0b8165e..dc08aee0 100644 --- a/packages/svelte/src/lib/container/NodeRenderer/NodeRenderer.svelte +++ b/packages/svelte/src/lib/container/NodeRenderer/NodeRenderer.svelte @@ -10,7 +10,8 @@ nodesDraggable, nodesConnectable, elementsSelectable, - updateNodeInternals + updateNodeInternals, + parentLookup } = useStore(); const resizeObserver: ResizeObserver | null = @@ -65,7 +66,7 @@ positionY={node.internals.positionAbsolute.y} positionOriginX={posOrigin.x ?? 0} positionOriginY={posOrigin.y ?? 0} - isParent={!!node.internals.isParent} + isParent={$parentLookup.has(node.id)} style={node.style} class={node.class} type={node.type ?? 'default'} diff --git a/packages/system/src/types/nodes.ts b/packages/system/src/types/nodes.ts index 7e6d7f14..34823b5f 100644 --- a/packages/system/src/types/nodes.ts +++ b/packages/system/src/types/nodes.ts @@ -73,8 +73,6 @@ export type InternalNodeBase = NodeType & internals: { positionAbsolute: XYPosition; z: number; - // @todo should we rename this to "handles" and use same type as node.handles? - isParent: boolean; /** Holds a reference to the original node object provided by the user. * Used as an optimization to avoid certain operations. */ userNode: NodeType; diff --git a/packages/system/src/utils/general.ts b/packages/system/src/utils/general.ts index dff79096..f2b662da 100644 --- a/packages/system/src/utils/general.ts +++ b/packages/system/src/utils/general.ts @@ -222,25 +222,27 @@ export function nodeHasDimensions(node: No } /** - * Helper to calculate the absolute position of a node + * Convert child position to aboslute position * * @internal - * @param node + * @param position + * @param parentId * @param nodeLookup * @param nodeOrigin * @returns an internal node with an absolute position */ -export function evaluateNodePosition( - node: NodeBase | InternalNodeBase, +export function evaluateAbsolutePosition( + position: XYPosition, + parentId: string, nodeLookup: NodeLookup, nodeOrigin: NodeOrigin = [0, 0] ): XYPosition { - let parentId = node.parentId; - const positionAbsolute = { ...node.position }; + let nextParentId: string | undefined = parentId; + const positionAbsolute = { ...position }; - while (parentId) { + while (nextParentId) { const parent = nodeLookup.get(parentId); - parentId = parent?.parentId; + nextParentId = parent?.parentId; if (parent) { const origin = parent.origin || nodeOrigin; diff --git a/packages/system/src/utils/store.ts b/packages/system/src/utils/store.ts index 5afa4787..fa140e80 100644 --- a/packages/system/src/utils/store.ts +++ b/packages/system/src/utils/store.ts @@ -20,6 +20,7 @@ import { import { getDimensions, getHandleBounds } from './dom'; import { getBoundsOfRects, getNodeDimensions, isNumeric, nodeToRect } from './general'; import { getNodePositionWithOrigin } from './graph'; +import { ParentExpandChild } from './types'; export function updateAbsolutePositions( nodeLookup: Map>, @@ -27,42 +28,41 @@ export function updateAbsolutePositions( nodeOrigin: [0, 0] as NodeOrigin, elevateNodesOnSelect: true, defaults: {}, - }, - parentLookup?: Map[]> + } ) { const selectedNodeZ: number = options?.elevateNodesOnSelect ? 1000 : 0; - for (const [id, node] of nodeLookup) { + for (const [, node] of nodeLookup) { const parentId = node.parentId; - if (parentId && !nodeLookup.has(parentId)) { + if (!parentId) { + continue; + } + + if (!nodeLookup.has(parentId)) { throw new Error(`Parent node ${parentId} not found`); } - if (parentId || node.internals.isParent || parentLookup?.has(id)) { - const parentNode = parentId ? nodeLookup.get(parentId) : null; - const { x, y, z } = calculateXYZPosition( - node, - nodeLookup, - { - ...node.position, - z: (isNumeric(node.zIndex) ? node.zIndex : 0) + (node.selected ? selectedNodeZ : 0), - }, - parentNode?.origin ?? options.nodeOrigin - ); + const parentNode = nodeLookup.get(parentId); + const { x, y, z } = calculateXYZPosition( + node, + nodeLookup, + { + ...node.position, + z: (isNumeric(node.zIndex) ? node.zIndex : 0) + (node.selected ? selectedNodeZ : 0), + }, + parentNode?.origin ?? options.nodeOrigin + ); - const currPosition = node.internals.positionAbsolute; - const positionChanged = x !== currPosition.x || y !== currPosition.y; + const currPosition = node.internals.positionAbsolute; + const positionChanged = x !== currPosition.x || y !== currPosition.y; + if (positionChanged || z !== node.internals.z) { node.internals = { ...node.internals, positionAbsolute: positionChanged ? { x, y } : currPosition, z, }; - - if (parentLookup !== undefined) { - node.internals.isParent = !!parentLookup.has(id); - } } } } @@ -111,7 +111,6 @@ export function adoptUserNodes( handleBounds: currentStoreNode?.internals.handleBounds, z: (isNumeric(userNode.zIndex) ? userNode.zIndex : 0) + (userNode.selected ? selectedNodeZ : 0), userNode, - isParent: false, }, }; nodeLookup.set(userNode.id, internalNode); @@ -128,7 +127,7 @@ export function adoptUserNodes( }); if (parentLookup.size > 0) { - updateAbsolutePositions(nodeLookup, options, parentLookup); + updateAbsolutePositions(nodeLookup, options); } } @@ -157,30 +156,30 @@ function calculateXYZPosition( ); } -type ExpandParentNode = NodeType & { parentId: string; expandParent: true }; - export function handleExpandParent( - nodes: ExpandParentNode[], + children: ParentExpandChild[], nodeLookup: NodeLookup, - parentLookup: ParentLookup + parentLookup: ParentLookup, + nodeOrigin?: NodeOrigin ): (NodeDimensionChange | NodePositionChange)[] { const changes: (NodeDimensionChange | NodePositionChange)[] = []; - const childNodeRects = new Map(); + const parentExpansions = new Map(); // determine the expanded rectangle the child nodes would take for each parent - for (const node of nodes) { - const parent = nodeLookup.get(node.parentId); + for (const child of children) { + const parent = nodeLookup.get(child.parentId); if (!parent) { continue; } - const parentRect = childNodeRects.get(node.parentId)?.expandedRect ?? nodeToRect(parent, node.origin); - const expandedRect = getBoundsOfRects(parentRect, nodeToRect(node, node.origin)); - childNodeRects.set(node.parentId, { expandedRect, parent }); + const parentRect = + parentExpansions.get(child.parentId)?.expandedRect ?? nodeToRect(parent, parent.origin ?? nodeOrigin); + const expandedRect = getBoundsOfRects(parentRect, child.rect); + parentExpansions.set(child.parentId, { expandedRect, parent }); } - if (childNodeRects.size > 0) { - childNodeRects.forEach(({ expandedRect, parent }, parentId) => { + if (parentExpansions.size > 0) { + parentExpansions.forEach(({ expandedRect, parent }, parentId) => { // determine the position & dimensions of the parent const { position } = getNodePositionWithOrigin(parent, parent.origin); const dimensions = getNodeDimensions(parent); @@ -202,7 +201,7 @@ export function handleExpandParent( // 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)) { + if (!children.some((child) => child.id === childNode.id)) { changes.push({ id: childNode.id, type: 'position', @@ -250,7 +249,7 @@ export function updateNodeInternals( const style = window.getComputedStyle(viewportNode); const { m22: zoom } = new window.DOMMatrixReadOnly(style.transform); // in this array we collect nodes, that might trigger changes (like expanding parent) - const parentExpandNodes: ExpandParentNode[] = []; + const parentExpandChildren: ParentExpandChild[] = []; updates.forEach((update) => { const node = nodeLookup.get(update.id); @@ -297,15 +296,19 @@ export function updateNodeInternals( }); if (newNode.expandParent && newNode.parentId) { - parentExpandNodes.push(newNode as ExpandParentNode); + parentExpandChildren.push({ + id: newNode.id, + parentId: newNode.parentId, + rect: nodeToRect(newNode, newNode.origin || nodeOrigin), + }); } } } } }); - if (parentExpandNodes.length > 0) { - const parentExpandChanges = handleExpandParent(parentExpandNodes, nodeLookup, parentLookup); + if (parentExpandChildren.length > 0) { + const parentExpandChanges = handleExpandParent(parentExpandChildren, nodeLookup, parentLookup, nodeOrigin); changes.push(...parentExpandChanges); } diff --git a/packages/system/src/utils/types.ts b/packages/system/src/utils/types.ts index 4a4634c2..92a1f903 100644 --- a/packages/system/src/utils/types.ts +++ b/packages/system/src/utils/types.ts @@ -1 +1,5 @@ +import { Rect } from '../types'; + export type Optional = Pick, K> & Omit; + +export type ParentExpandChild = { id: string; parentId: string; rect: Rect }; From 27987006f387041f6c9db50f0e890798118220bf Mon Sep 17 00:00:00 2001 From: peterkogo Date: Tue, 16 Apr 2024 16:58:56 +0200 Subject: [PATCH 10/11] renamed newPosition to nextPosition, removed comment --- .../NodeResizer/NodeResizeControl.tsx | 14 ++++++-------- packages/system/src/utils/store.ts | 1 - 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx b/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx index 5b405498..8e6ca2f1 100644 --- a/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx +++ b/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx @@ -12,14 +12,12 @@ import { handleExpandParent, evaluateAbsolutePosition, ParentExpandChild, + XYPosition, } from '@xyflow/system'; import { useStoreApi } from '../../hooks/useStore'; import { useNodeId } from '../../contexts/NodeIdContext'; import type { ResizeControlProps, ResizeControlLineProps } from './types'; -import { InternalNode, Node } from '../../types'; - -type InternalNodeWithParentExpand = InternalNode & { expandParent: true; parentId: string }; function ResizeControl({ nodeId, @@ -71,7 +69,7 @@ function ResizeControl({ const { triggerNodeChanges, nodeLookup, parentLookup, nodeOrigin } = store.getState(); const changes: NodeChange[] = []; - const newPosition = { x: change.x, y: change.y }; + const nextPosition = { x: change.x, y: change.y }; const node = nodeLookup.get(id); if (node && node.expandParent && node.parentId) { @@ -97,15 +95,15 @@ function ResizeControl({ 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; + nextPosition.x = change.x ? Math.max(0, change.x) : undefined; + nextPosition.y = change.y ? Math.max(0, change.y) : undefined; } - if (change.x !== undefined && change.y !== undefined) { + if (nextPosition.x !== undefined && nextPosition.y !== undefined) { const positionChange: NodePositionChange = { id, type: 'position', - position: { ...newPosition } as { x: number; y: number }, + position: { ...(nextPosition as XYPosition) }, }; changes.push(positionChange); } diff --git a/packages/system/src/utils/store.ts b/packages/system/src/utils/store.ts index fa140e80..67a7c365 100644 --- a/packages/system/src/utils/store.ts +++ b/packages/system/src/utils/store.ts @@ -90,7 +90,6 @@ export function adoptUserNodes( parentLookup.clear(); const selectedNodeZ: number = options?.elevateNodesOnSelect ? 1000 : 0; - // const parentNodeIds = new Set(); nodes.forEach((userNode) => { const currentStoreNode = tmpLookup.get(userNode.id); From 5af34fd9d4e9003f35c7c51fc7a0b99c71e3a07a Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 16 Apr 2024 17:52:15 +0200 Subject: [PATCH 11/11] fix(react): use equlity check for adopt user nodes --- packages/react/src/store/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/store/index.ts b/packages/react/src/store/index.ts index 8b999013..50d981c2 100644 --- a/packages/react/src/store/index.ts +++ b/packages/react/src/store/index.ts @@ -46,7 +46,7 @@ const createRFStore = ({ // // When this happens, we take the note objects passed by the user and extend them with fields // relevant for internal React Flow operations. - adoptUserNodes(nodes, nodeLookup, parentLookup, { nodeOrigin, elevateNodesOnSelect }); + adoptUserNodes(nodes, nodeLookup, parentLookup, { nodeOrigin, elevateNodesOnSelect, checkEquality: true }); set({ nodes }); },