From 0974aa57327d6e606c9d1173bb23fff8bcd36b40 Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 18 Nov 2021 13:01:11 +0100 Subject: [PATCH] refactor(nodes): only use node internals --- example/src/Basic/index.tsx | 1 + example/src/FloatingEdges/FloatingEdge.tsx | 8 +-- example/src/Hidden/index.tsx | 62 +++++++++++++++++++ example/src/Stress/index.tsx | 2 + example/src/index.tsx | 5 ++ example/src_oldapi/Hidden/index.tsx | 53 ----------------- src/additional-components/MiniMap/index.tsx | 19 +++--- src/components/ConnectionLine/index.tsx | 6 +- src/components/Nodes/wrapNode.tsx | 8 +-- src/components/NodesSelection/index.tsx | 6 +- src/components/SelectionListener/index.tsx | 5 +- src/container/NodeRenderer/index.tsx | 21 ++++--- src/hooks/useGlobalKeyHandler.ts | 4 +- src/hooks/useNodeInternalsRef.ts | 14 ----- src/hooks/useOnLoadHandler.ts | 9 ++- src/hooks/useVisibleNodes.ts | 6 +- src/hooks/useZoomPanHelper.ts | 5 +- src/store/index.ts | 66 ++++++++++++--------- src/store/utils.ts | 14 +++-- src/types/general.ts | 1 - src/types/nodes.ts | 7 +-- 21 files changed, 171 insertions(+), 151 deletions(-) create mode 100644 example/src/Hidden/index.tsx delete mode 100644 example/src_oldapi/Hidden/index.tsx delete mode 100644 src/hooks/useNodeInternalsRef.ts diff --git a/example/src/Basic/index.tsx b/example/src/Basic/index.tsx index 1416b115..825fd333 100644 --- a/example/src/Basic/index.tsx +++ b/example/src/Basic/index.tsx @@ -147,6 +147,7 @@ const BasicFlow = () => { }; const onNodesChange = useCallback((changes: NodeChange[]) => { + console.log('node change', changes); setNodes((ns) => applyNodeChanges(changes, ns)); }, []); diff --git a/example/src/FloatingEdges/FloatingEdge.tsx b/example/src/FloatingEdges/FloatingEdge.tsx index a163fcbe..1812ee2b 100644 --- a/example/src/FloatingEdges/FloatingEdge.tsx +++ b/example/src/FloatingEdges/FloatingEdge.tsx @@ -3,13 +3,13 @@ import { EdgeProps, useStore, getBezierPath, ReactFlowState } from 'react-flow-r import { getEdgeParams } from './utils'; -const nodeSelector = (s: ReactFlowState) => s.nodes; +const nodeSelector = (s: ReactFlowState) => s.nodeInternals; const FloatingEdge: FC = ({ id, source, target, style }) => { - const nodes = useStore(nodeSelector); + const nodeInternals = useStore(nodeSelector); - const sourceNode = useMemo(() => nodes.find((n) => n.id === source), [source, nodes]); - const targetNode = useMemo(() => nodes.find((n) => n.id === target), [target, nodes]); + const sourceNode = useMemo(() => nodeInternals.get(source), [source, nodeInternals]); + const targetNode = useMemo(() => nodeInternals.get(target), [target, nodeInternals]); if (!sourceNode || !targetNode) { return null; diff --git a/example/src/Hidden/index.tsx b/example/src/Hidden/index.tsx new file mode 100644 index 00000000..b0249310 --- /dev/null +++ b/example/src/Hidden/index.tsx @@ -0,0 +1,62 @@ +import { useState, useCallback } from 'react'; + +import { useEffect } from 'react'; +import ReactFlow, { addEdge, MiniMap, Controls, Connection, Edge, Node } from 'react-flow-renderer'; + +const initialNodes: Node[] = [ + { id: '1', type: 'input', isHidden: true, data: { label: 'Node 1' }, position: { x: 250, y: 5 } }, + { id: '2', isHidden: true, data: { label: 'Node 2' }, position: { x: 100, y: 100 } }, + { id: '3', isHidden: true, data: { label: 'Node 3' }, position: { x: 400, y: 100 } }, + { id: '4', isHidden: true, data: { label: 'Node 4' }, position: { x: 400, y: 200 } }, +]; + +const initialEdges: Edge[] = [ + { id: 'e1-2', source: '1', target: '2' }, + { id: 'e1-3', source: '1', target: '3' }, + { id: 'e3-4', source: '3', target: '4' }, +]; + +const setHidden = (isHidden: boolean) => (els: any[]) => + els.map((e: any) => { + e.isHidden = isHidden; + return e; + }); + +const HiddenFlow = () => { + const [nodes, setNodes] = useState(initialNodes); + const [edges, setEdges] = useState(initialEdges); + const [isHidden, setIsHidden] = useState(true); + + const onConnect = useCallback((params: Edge | Connection) => { + setEdges((eds) => addEdge(params, eds)); + }, []); + + useEffect(() => { + setNodes(setHidden(isHidden)); + setNodes(setHidden(isHidden)); + }, [isHidden]); + + return ( + + + + +
+
+ +
+
+
+ ); +}; + +export default HiddenFlow; diff --git a/example/src/Stress/index.tsx b/example/src/Stress/index.tsx index e8bc9323..5b97298c 100644 --- a/example/src/Stress/index.tsx +++ b/example/src/Stress/index.tsx @@ -51,6 +51,8 @@ const StressFlow = () => { }; const onNodesChange = useCallback((changes: NodeChange[]) => { + console.log('node change', changes); + setNodes((ns) => applyNodeChanges(changes, ns)); }, []); diff --git a/example/src/index.tsx b/example/src/index.tsx index b23fc09c..45aa0a16 100644 --- a/example/src/index.tsx +++ b/example/src/index.tsx @@ -9,6 +9,7 @@ import CustomNode from './CustomNode'; import FloatingEdges from './FloatingEdges'; import Layouting from './Layouting'; import NestedNodes from './NestedNodes'; +import Hidden from './Hidden'; import './index.css'; @@ -41,6 +42,10 @@ const routes = [ path: '/nested-nodes', component: NestedNodes, }, + { + path: '/hidden', + component: Hidden, + }, ]; const Header = withRouter(({ history, location }) => { diff --git a/example/src_oldapi/Hidden/index.tsx b/example/src_oldapi/Hidden/index.tsx deleted file mode 100644 index 9869b2bc..00000000 --- a/example/src_oldapi/Hidden/index.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React, { useState } from 'react'; - -import { useEffect } from 'react'; -import ReactFlow, { addEdge, MiniMap, Controls, Connection, Edge, Elements } from 'react-flow-renderer'; - -const initialElements: Elements = [ - { id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } }, - { id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 } }, - { id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 } }, - { id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 } }, - { id: 'e1-2', source: '1', target: '2' }, - { id: 'e1-3', source: '1', target: '3' }, - { id: 'e3-4', source: '3', target: '4' }, -]; - -const HiddenFlow = () => { - const [elements, setElements] = useState(initialElements); - const [isHidden, setIsHidden] = useState(false); - const onConnect = (params: Connection | Edge) => setElements((els) => addEdge(params, els)); - - useEffect(() => { - setElements((els) => - els.map((e) => { - e.isHidden = isHidden; - return e; - }) - ); - }, [isHidden]); - - return ( - - - - -
-
- -
-
-
- ); -}; - -export default HiddenFlow; diff --git a/src/additional-components/MiniMap/index.tsx b/src/additional-components/MiniMap/index.tsx index 98735e55..bf5d7e02 100644 --- a/src/additional-components/MiniMap/index.tsx +++ b/src/additional-components/MiniMap/index.tsx @@ -28,7 +28,6 @@ const selector = (s: ReactFlowState) => ({ width: s.width, height: s.height, transform: s.transform, - nodes: s.nodes, nodeInternals: s.nodeInternals, }); @@ -42,13 +41,7 @@ const MiniMap = ({ nodeStrokeWidth = 2, maskColor = 'rgb(240, 242, 243, 0.7)', }: MiniMapProps) => { - const { - width: containerWidth, - height: containerHeight, - transform, - nodes, - nodeInternals, - } = useStore(selector, shallow); + const { width: containerWidth, height: containerHeight, transform, nodeInternals } = useStore(selector, shallow); const [tX, tY, tScale] = transform; const mapClasses = cc(['react-flow__minimap', className]); @@ -59,7 +52,9 @@ const MiniMap = ({ nodeStrokeColor instanceof Function ? nodeStrokeColor : () => nodeStrokeColor ) as StringFunc; const nodeClassNameFunc = (nodeClassName instanceof Function ? nodeClassName : () => nodeClassName) as StringFunc; - const hasNodes = nodes && nodes.length; + const hasNodes = nodeInternals && nodeInternals.size > 0; + // @TODO: work with nodeInternals instead of converting it to an array + const nodes = Array.from(nodeInternals).map(([_, node]) => node); const bb = getRectOfNodes(nodes); const viewBB: Rect = { x: -tX / tScale, @@ -88,9 +83,9 @@ const MiniMap = ({ style={style} className={mapClasses} > - {nodes - .filter((node) => !node.isHidden && node.width && node.height) - .map((node) => { + {Array.from(nodeInternals) + .filter(([_, node]) => !node.isHidden && node.width && node.height) + .map(([_, node]) => { const positionAbsolute = nodeInternals.get(node.id)?.positionAbsolute; return ( diff --git a/src/components/ConnectionLine/index.tsx b/src/components/ConnectionLine/index.tsx index 8ce8e0f1..390132d1 100644 --- a/src/components/ConnectionLine/index.tsx +++ b/src/components/ConnectionLine/index.tsx @@ -27,7 +27,7 @@ interface ConnectionLineProps { CustomConnectionLineComponent?: ConnectionLineComponent; } -const selector = (s: ReactFlowState) => ({ nodeInternals: s.nodeInternals, nodes: s.nodes, transform: s.transform }); +const selector = (s: ReactFlowState) => ({ nodeInternals: s.nodeInternals, transform: s.transform }); export default ({ connectionNodeId, @@ -43,9 +43,9 @@ export default ({ const nodeId = connectionNodeId; const handleId = connectionHandleId; - const { nodeInternals, nodes, transform } = useStore(selector, shallow); + const { nodeInternals, transform } = useStore(selector, shallow); const sourceNodeInternals = useRef(nodeInternals.get(nodeId)); - const sourceNode = useRef(nodes.find((n) => n.id === nodeId)); + const sourceNode = useRef(nodeInternals.get(nodeId)); if ( !sourceNode.current || diff --git a/src/components/Nodes/wrapNode.tsx b/src/components/Nodes/wrapNode.tsx index 58265fc4..deccc17b 100644 --- a/src/components/Nodes/wrapNode.tsx +++ b/src/components/Nodes/wrapNode.tsx @@ -56,7 +56,7 @@ export default (NodeComponent: ComponentType) => { unselectNodesAndEdges, unsetNodesSelection, updateNodePosition, - updateNodeDimensions, + // updateNodeDimensions, } = useStore(selector, shallow); const nodeElement = useRef(null); @@ -201,12 +201,6 @@ export default (NodeComponent: ComponentType) => { [node, onNodeDoubleClick] ); - useEffect(() => { - if (nodeElement.current && (!isHidden || !isInitialized)) { - updateNodeDimensions([{ id, nodeElement: nodeElement.current, forceUpdate: true }]); - } - }, [id, isHidden, sourcePosition, targetPosition, type, isInitialized]); - useEffect(() => { if (nodeElement.current) { const currNode = nodeElement.current; diff --git a/src/components/NodesSelection/index.tsx b/src/components/NodesSelection/index.tsx index bf46a6a2..95fac2c0 100644 --- a/src/components/NodesSelection/index.tsx +++ b/src/components/NodesSelection/index.tsx @@ -15,12 +15,14 @@ export interface NodesSelectionProps { onSelectionDragStop?: (event: MouseEvent, nodes: Node[]) => void; onSelectionContextMenu?: (event: MouseEvent, nodes: Node[]) => void; } - +// @TODO: work with nodeInternals instead of converting it to an array const selector = (s: ReactFlowState) => ({ transform: s.transform, selectedNodesBbox: s.selectedNodesBbox, selectionActive: s.selectionActive, - selectedNodes: s.nodes.filter((n) => n.selected), + selectedNodes: Array.from(s.nodeInternals) + .filter(([_, n]) => n.selected) + .map(([_, n]) => n), snapToGrid: s.snapToGrid, snapGrid: s.snapGrid, updateNodePosition: s.updateNodePosition, diff --git a/src/components/SelectionListener/index.tsx b/src/components/SelectionListener/index.tsx index d2cfd097..c8b59795 100644 --- a/src/components/SelectionListener/index.tsx +++ b/src/components/SelectionListener/index.tsx @@ -8,8 +8,11 @@ interface SelectionListenerProps { onSelectionChange: OnSelectionChangeFunc; } +// @TODO: work with nodeInternals instead of converting it to an array const selectedElementsSelector = (s: ReactFlowState) => ({ - selectedNodes: s.nodes.filter((n) => n.selected), + selectedNodes: Array.from(s.nodeInternals) + .filter(([_, n]) => n.selected) + .map(([_, node]) => node), selectedEdges: s.edges.filter((e) => e.selected), }); diff --git a/src/container/NodeRenderer/index.tsx b/src/container/NodeRenderer/index.tsx index 3482db92..8a093d6e 100644 --- a/src/container/NodeRenderer/index.tsx +++ b/src/container/NodeRenderer/index.tsx @@ -3,8 +3,6 @@ import shallow from 'zustand/shallow'; import { useStore } from '../../store'; import { Node, NodeTypesType, ReactFlowState, WrapNodeProps } from '../../types'; -import useVisibleNodes from '../../hooks/useVisibleNodes'; -import useNodeInternalsRef from '../../hooks/useNodeInternalsRef'; interface NodeRendererProps { nodeTypes: NodeTypesType; @@ -29,13 +27,20 @@ const selector = (s: ReactFlowState) => ({ updateNodeDimensions: s.updateNodeDimensions, snapGrid: s.snapGrid, snapToGrid: s.snapToGrid, + nodeInternals: s.nodeInternals, }); const NodeRenderer = (props: NodeRendererProps) => { - const { scale, nodesDraggable, nodesConnectable, elementsSelectable, updateNodeDimensions, snapGrid, snapToGrid } = - useStore(selector, shallow); - const nodeInternals = useNodeInternalsRef(); - const nodes = useVisibleNodes(props.onlyRenderVisibleElements); + const { + scale, + nodesDraggable, + nodesConnectable, + elementsSelectable, + updateNodeDimensions, + snapGrid, + snapToGrid, + nodeInternals, + } = useStore(selector, shallow); const resizeObserver = useMemo(() => { if (typeof ResizeObserver === 'undefined') { @@ -54,9 +59,9 @@ const NodeRenderer = (props: NodeRendererProps) => { return (
- {nodes.map((node) => { + {Array.from(nodeInternals).map(([_, node]) => { const nodeType = node.type || 'default'; - const internals = nodeInternals.current.get(node.id); + const internals = nodeInternals.get(node.id); if (!props.nodeTypes[nodeType]) { console.warn(`Node type "${nodeType}" not found. Using fallback type "default".`); diff --git a/src/hooks/useGlobalKeyHandler.ts b/src/hooks/useGlobalKeyHandler.ts index 19743457..ead31a62 100644 --- a/src/hooks/useGlobalKeyHandler.ts +++ b/src/hooks/useGlobalKeyHandler.ts @@ -28,7 +28,9 @@ export default ({ deleteKeyCode, multiSelectionKeyCode }: HookParams): void => { const multiSelectionKeyPressed = useKeyPress(multiSelectionKeyCode); useEffect(() => { - const { nodes, edges } = store.getState(); + const { nodeInternals, edges } = store.getState(); + // @TODO: work with nodeInternals instead of converting it to an array + const nodes = Array.from(nodeInternals).map(([_, node]) => node); const selectedNodes = nodes.filter((n) => n.selected); const selectedEdges = edges.filter((e) => e.selected); diff --git a/src/hooks/useNodeInternalsRef.ts b/src/hooks/useNodeInternalsRef.ts deleted file mode 100644 index d6b9b2d9..00000000 --- a/src/hooks/useNodeInternalsRef.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { useRef, useEffect } from 'react'; - -import { useStoreApi } from '../store'; - -function useNodeInternalsRef() { - const store = useStoreApi(); - const nodeInternals = useRef(store.getState().nodeInternals); - - useEffect(() => store.subscribe((state) => (nodeInternals.current = state.nodeInternals)), []); - - return nodeInternals; -} - -export default useNodeInternalsRef; diff --git a/src/hooks/useOnLoadHandler.ts b/src/hooks/useOnLoadHandler.ts index ed128817..d75ba9f4 100644 --- a/src/hooks/useOnLoadHandler.ts +++ b/src/hooks/useOnLoadHandler.ts @@ -19,7 +19,9 @@ function useOnLoadHandler(onLoad: OnLoad | undefined) { }; const getNodes = (): Node[] => { - const { nodes = [] } = store.getState(); + const { nodeInternals } = store.getState(); + // @TODO: work with nodeInternals instead of converting it to an array + const nodes = Array.from(nodeInternals).map(([_, node]) => node); return nodes.map((n) => ({ ...n })); }; @@ -29,8 +31,9 @@ function useOnLoadHandler(onLoad: OnLoad | undefined) { }; const toObject = (): FlowExportObject => { - const { nodes = [], edges = [], transform } = store.getState(); - + const { nodeInternals, edges = [], transform } = store.getState(); + // @TODO: work with nodeInternals instead of converting it to an array + const nodes = Array.from(nodeInternals).map(([_, node]) => node); return { nodes: nodes.map((n) => ({ ...n })), edges: edges.map((e) => ({ ...e })), diff --git a/src/hooks/useVisibleNodes.ts b/src/hooks/useVisibleNodes.ts index d6c9bd96..4252db59 100644 --- a/src/hooks/useVisibleNodes.ts +++ b/src/hooks/useVisibleNodes.ts @@ -8,9 +8,11 @@ function useVisibleNodes(onlyRenderVisible: boolean) { const nodes = useStore( useCallback( (s: ReactFlowState) => { + // @TODO: work with nodeInternals instead of converting it to an array + const nodes = Array.from(s.nodeInternals).map(([_, node]) => node); return onlyRenderVisible - ? getNodesInside(s.nodes, { x: 0, y: 0, width: s.width, height: s.height }, s.transform, true) - : s.nodes; + ? getNodesInside(nodes, { x: 0, y: 0, width: s.width, height: s.height }, s.transform, true) + : nodes; }, [onlyRenderVisible] ) diff --git a/src/hooks/useZoomPanHelper.ts b/src/hooks/useZoomPanHelper.ts index aacc48da..7ba41095 100644 --- a/src/hooks/useZoomPanHelper.ts +++ b/src/hooks/useZoomPanHelper.ts @@ -41,8 +41,9 @@ const useZoomPanHelper = (): ZoomPanHelperFunctions => { d3Zoom.transform(d3Selection, nextTransform); }, fitView: (options: FitViewParams = { padding: DEFAULT_PADDING, includeHiddenNodes: false }) => { - const { nodes, width, height, minZoom, maxZoom } = store.getState(); - + const { nodeInternals, width, height, minZoom, maxZoom } = store.getState(); + // @TODO: work with nodeInternals instead of converting it to an array + const nodes = Array.from(nodeInternals).map(([_, node]) => node); if (!nodes.length) { return; } diff --git a/src/store/index.ts b/src/store/index.ts index c12e10c7..54c718ad 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -42,7 +42,6 @@ const createStore = () => width: 0, height: 0, transform: [0, 0, 1], - nodes: [], edges: [], onNodesChange: null, onEdgesChange: null, @@ -87,28 +86,29 @@ const createStore = () => setNodes: (nodes: Node[]) => { const nodeInternals = createNodeInternals(nodes, get().nodeInternals); - set({ nodes, nodeInternals }); + set({ nodeInternals }); }, setEdges: (edges: Edge[]) => { set({ edges }); }, updateNodeDimensions: (updates: NodeDimensionUpdate[]) => { - const { onNodesChange, nodes, transform, nodeInternals } = get(); + const { onNodesChange, transform, nodeInternals } = get(); const nodesToChange: NodeChange[] = updates.reduce((res, update) => { - const node = nodes.find((n) => n.id === update.id); + const node = nodeInternals.get(update.id); if (node) { const dimensions = getDimensions(update.nodeElement); - const doUpdate = + const doUpdate = !!( dimensions.width && dimensions.height && - (node.width !== dimensions.width || node.height !== dimensions.height || update.forceUpdate); + (node.width !== dimensions.width || node.height !== dimensions.height || update.forceUpdate) + ); if (doUpdate) { const handleBounds = getHandleBounds(update.nodeElement, transform[2]); nodeInternals.set(node.id, { - ...nodeInternals.get(node.id), + ...node, handleBounds, ...dimensions, }); @@ -127,17 +127,19 @@ const createStore = () => set({ nodeInternals: new Map(nodeInternals) }); - onNodesChange?.(nodesToChange); + if (nodesToChange?.length > 0) { + onNodesChange?.(nodesToChange); + } }, updateNodePosition: ({ id, diff, dragging }: NodeDiffUpdate) => { - const { onNodesChange, nodes, nodeExtent, nodeInternals } = get(); + const { onNodesChange, nodeExtent, nodeInternals } = get(); if (onNodesChange) { - const matchingNodes = nodes.filter((n) => !!(n.selected || n.id === id)); - + const nodes = Array.from(nodeInternals); + const matchingNodes = nodes.filter(([_, n]) => !!(n.selected || n.id === id)); if (matchingNodes?.length) { onNodesChange( - matchingNodes?.map((node) => { + matchingNodes?.map(([_, node]) => { const change: NodeDimensionChange = { id: node.id, type: 'dimensions', @@ -190,7 +192,7 @@ const createStore = () => }); }, updateUserSelection: (mousePos: XYPosition) => { - const { userSelectionRect, nodes, edges, transform, onNodesChange, onEdgesChange } = get(); + const { userSelectionRect, nodeInternals, edges, transform, onNodesChange, onEdgesChange } = get(); const startX = userSelectionRect.startX ?? 0; const startY = userSelectionRect.startY ?? 0; @@ -202,6 +204,8 @@ const createStore = () => height: Math.abs(mousePos.y - startY), }; + // @TODO: work with nodeInternals instead of converting it to an array + const nodes = Array.from(nodeInternals).map(([_, node]) => node); const selectedNodes = getNodesInside(nodes, nextUserSelectRect, transform, false, true); const selectedEdgeIds = getConnectedEdges(selectedNodes, edges).map((e) => e.id); const selectedNodeIds = selectedNodes.map((n) => n.id); @@ -218,7 +222,9 @@ const createStore = () => }); }, unsetUserSelection: () => { - const { userSelectionRect, nodes } = get(); + const { userSelectionRect, nodeInternals } = get(); + // @TODO: work with nodeInternals instead of converting it to an array + const nodes = Array.from(nodeInternals).map(([_, node]) => node); const selectedNodes = nodes.filter((node) => node.selected); const stateUpdate = { @@ -240,8 +246,9 @@ const createStore = () => set(stateUpdate); }, addSelectedElements: (selectedElementsArr: Array) => { - const { multiSelectionActive, onNodesChange, onEdgesChange, nodes, edges } = get(); - + const { multiSelectionActive, onNodesChange, onEdgesChange, nodeInternals, edges } = get(); + // @TODO: work with nodeInternals instead of converting it to an array + const nodes = Array.from(nodeInternals).map(([_, node]) => node); let changedNodes; let changedEdges; @@ -266,7 +273,9 @@ const createStore = () => } }, unselectNodesAndEdges: () => { - const { nodes, edges, onNodesChange, onEdgesChange } = get(); + const { nodeInternals, edges, onNodesChange, onEdgesChange } = get(); + // @TODO: work with nodeInternals instead of converting it to an array + const nodes = Array.from(nodeInternals).map(([_, node]) => node); const nodesToUnselect = nodes.map((n) => { n.selected = false; @@ -308,8 +317,9 @@ const createStore = () => }, resetSelectedElements: () => { - const { nodes, edges, onNodesChange, onEdgesChange } = get(); - + const { nodeInternals, edges, onNodesChange, onEdgesChange } = get(); + // @TODO: work with nodeInternals instead of converting it to an array + const nodes = Array.from(nodeInternals).map(([_, node]) => node); const nodesToUnselect = nodes.filter((e) => e.selected).map(createNodeOrEdgeSelectionChange(false)); const edgesToUnselect = edges.filter((e) => e.selected).map(createNodeOrEdgeSelectionChange(false)); @@ -320,16 +330,18 @@ const createStore = () => onEdgesChange?.(edgesToUnselect as EdgeChange[]); } }, - setNodeExtent: (nodeExtent: CoordinateExtent) => + setNodeExtent: (nodeExtent: CoordinateExtent) => { + const { nodeInternals } = get(); + + nodeInternals.forEach((node) => { + node.positionAbsolute = clampPosition(node.position, nodeExtent); + }); + set({ nodeExtent, - nodes: get().nodes.map((node) => { - return { - ...node, - position: clampPosition(node.position, nodeExtent), - }; - }), - }), + nodeInternals: new Map(nodeInternals), + }); + }, unsetNodesSelection: () => set({ nodesSelectionActive: false }), updateTransform: (transform: Transform) => set({ transform }), updateSize: (size: Dimensions) => set({ width: size.width || 500, height: size.height || 500 }), diff --git a/src/store/utils.ts b/src/store/utils.ts index be01a1b0..25505d50 100644 --- a/src/store/utils.ts +++ b/src/store/utils.ts @@ -39,11 +39,11 @@ export function createNodeInternals(nodes: Node[], nodeInternals: NodeInternals) const z = node.zIndex ? node.zIndex : node.dragging || node.selected ? 1000 : 0; const internals: NodeInternalsItem = { ...nodeInternals.get(node.id), - id: node.id, - width: node.width || null, - height: node.height || null, - position: node.position, - positionAbsolute: node.position, + ...node, + positionAbsolute: { + x: node.position.x, + y: node.position.y, + }, z, }; if (node.parentNode) { @@ -56,6 +56,10 @@ export function createNodeInternals(nodes: Node[], nodeInternals: NodeInternals) nodes.forEach((node) => { const updatedInternals: NodeInternalsItem = nextNodeInternals.get(node.id)!; + if (node.parentNode && !nextNodeInternals.has(node.parentNode)) { + throw new Error(`Parent node ${node.parentNode} not found`); + } + if (node.parentNode || parentNodes[node.id]) { let startingZ = updatedInternals.z || 0; diff --git a/src/types/general.ts b/src/types/general.ts index 68dd316e..5cafafef 100644 --- a/src/types/general.ts +++ b/src/types/general.ts @@ -134,7 +134,6 @@ export interface ReactFlowState { width: number; height: number; transform: Transform; - nodes: Node[]; nodeInternals: NodeInternals; edges: Edge[]; selectedNodesBbox: Rect; diff --git a/src/types/nodes.ts b/src/types/nodes.ts index f2b7b02a..60d052bc 100644 --- a/src/types/nodes.ts +++ b/src/types/nodes.ts @@ -101,12 +101,7 @@ export type NodeDimensionUpdate = { forceUpdate?: boolean; }; -export type NodeInternalsItem = { - id?: string; - width?: number | null; - height?: number | null; - parentNode?: string; - position?: XYPosition; +export type NodeInternalsItem = Node & { positionAbsolute?: XYPosition; handleBounds?: NodeHandleBounds; z?: number;