diff --git a/examples/vite-app/src/examples/EasyConnect/FloatingEdge.tsx b/examples/vite-app/src/examples/EasyConnect/FloatingEdge.tsx index 55ed472b..7709cb42 100644 --- a/examples/vite-app/src/examples/EasyConnect/FloatingEdge.tsx +++ b/examples/vite-app/src/examples/EasyConnect/FloatingEdge.tsx @@ -4,8 +4,8 @@ import { useStore, getStraightPath, EdgeProps } from '@xyflow/react'; import { getEdgeParams } from './utils.js'; function FloatingEdge({ id, source, target, markerEnd, style }: EdgeProps) { - const sourceNode = useStore(useCallback((store) => store.nodeInternals.get(source), [source])); - const targetNode = useStore(useCallback((store) => store.nodeInternals.get(target), [target])); + const sourceNode = useStore(useCallback((store) => store.nodes.find((n) => n.id === source), [source])); + const targetNode = useStore(useCallback((store) => store.nodes.find((n) => n.id === target), [target])); if (!sourceNode || !targetNode) { return null; diff --git a/examples/vite-app/src/examples/EdgeRenderer/CustomEdge.tsx b/examples/vite-app/src/examples/EdgeRenderer/CustomEdge.tsx index ca9bb5f0..511abca3 100644 --- a/examples/vite-app/src/examples/EdgeRenderer/CustomEdge.tsx +++ b/examples/vite-app/src/examples/EdgeRenderer/CustomEdge.tsx @@ -14,7 +14,7 @@ const CustomEdge: FC = ({ data, }) => { const isConnectedNodeDragging = useStore((s) => - s.getNodes().find((n) => n.dragging && (target === n.id || source === n.id)) + s.nodes.find((n) => n.dragging && (target === n.id || source === n.id)) ); const [edgePath, labelX, labelY] = getBezierPath({ diff --git a/examples/vite-app/src/examples/EdgeRenderer/CustomEdge2.tsx b/examples/vite-app/src/examples/EdgeRenderer/CustomEdge2.tsx index d060a19d..29caad72 100644 --- a/examples/vite-app/src/examples/EdgeRenderer/CustomEdge2.tsx +++ b/examples/vite-app/src/examples/EdgeRenderer/CustomEdge2.tsx @@ -14,7 +14,7 @@ const CustomEdge: FC = ({ data, }) => { const isConnectedNodeDragging = useStore((s) => - s.getNodes().find((n) => n.dragging && (target === n.id || source === n.id)) + s.nodes.find((n) => n.dragging && (target === n.id || source === n.id)) ); const [edgePath, labelX, labelY] = getBezierPath({ diff --git a/examples/vite-app/src/examples/FloatingEdges/FloatingEdge.tsx b/examples/vite-app/src/examples/FloatingEdges/FloatingEdge.tsx index 2dbd1050..6ee9e1e2 100644 --- a/examples/vite-app/src/examples/FloatingEdges/FloatingEdge.tsx +++ b/examples/vite-app/src/examples/FloatingEdges/FloatingEdge.tsx @@ -1,15 +1,15 @@ -import { FC, useMemo, CSSProperties } from 'react'; -import { EdgeProps, useStore, getBezierPath, ReactFlowState } from '@xyflow/react'; +import { FC, CSSProperties } from 'react'; +import { EdgeProps, useStore, getBezierPath } from '@xyflow/react'; import { getEdgeParams } from './utils'; -const nodeSelector = (s: ReactFlowState) => s.nodeInternals; - const FloatingEdge: FC = ({ id, source, target, style }) => { - const nodeInternals = useStore(nodeSelector); + const { sourceNode, targetNode } = useStore((s) => { + const sourceNode = s.nodes.find((n) => n.id === source); + const targetNode = s.nodes.find((n) => n.id === target); - const sourceNode = useMemo(() => nodeInternals.get(source), [source, nodeInternals]); - const targetNode = useMemo(() => nodeInternals.get(target), [target, nodeInternals]); + return { sourceNode, targetNode }; + }); if (!sourceNode || !targetNode) { return null; diff --git a/examples/vite-app/src/examples/NodeToolbar/SelectedNodesToolbar.tsx b/examples/vite-app/src/examples/NodeToolbar/SelectedNodesToolbar.tsx index d5ccded6..4996a45a 100644 --- a/examples/vite-app/src/examples/NodeToolbar/SelectedNodesToolbar.tsx +++ b/examples/vite-app/src/examples/NodeToolbar/SelectedNodesToolbar.tsx @@ -1,10 +1,7 @@ import { NodeToolbar, ReactFlowState, useStore } from '@xyflow/react'; const selectedNodesSelector = (state: ReactFlowState) => - state - .getNodes() - .filter((node) => node.selected) - .map((node) => node.id); + state.nodes.filter((node) => node.selected).map((node) => node.id); export default function SelectedNodesToolbar() { const selectedNodeIds = useStore(selectedNodesSelector); diff --git a/examples/vite-app/src/examples/Provider/Sidebar.tsx b/examples/vite-app/src/examples/Provider/Sidebar.tsx index 67c2c8b9..931f553c 100644 --- a/examples/vite-app/src/examples/Provider/Sidebar.tsx +++ b/examples/vite-app/src/examples/Provider/Sidebar.tsx @@ -5,7 +5,7 @@ import styles from './provider.module.css'; const Sidebar = () => { const { setNodes } = useReactFlow(); const nodeInfos = useStore((store) => - store.getNodes().map((n) => `Node ${n.id} - x: ${n.position.x.toFixed(2)}, y: ${n.position.y.toFixed(2)}`) + store.nodes.map((n) => `Node ${n.id} - x: ${n.position.x.toFixed(2)}, y: ${n.position.y.toFixed(2)}`) ); const transform = useStore((store) => store.transform); diff --git a/packages/react/src/additional-components/MiniMap/MiniMap.tsx b/packages/react/src/additional-components/MiniMap/MiniMap.tsx index b0fc80ff..86212934 100644 --- a/packages/react/src/additional-components/MiniMap/MiniMap.tsx +++ b/packages/react/src/additional-components/MiniMap/MiniMap.tsx @@ -16,7 +16,6 @@ const defaultWidth = 200; const defaultHeight = 150; const selector = (s: ReactFlowState) => { - const nodes = s.getNodes(); const viewBB: Rect = { x: -s.transform[0] / s.transform[2], y: -s.transform[1] / s.transform[2], @@ -26,7 +25,7 @@ const selector = (s: ReactFlowState) => { return { viewBB, - boundingRect: nodes.length > 0 ? getBoundsOfRects(getRectOfNodes(nodes, s.nodeOrigin), viewBB) : viewBB, + boundingRect: s.nodes.length > 0 ? getBoundsOfRects(getRectOfNodes(s.nodes, s.nodeOrigin), viewBB) : viewBB, rfId: s.rfId, nodeOrigin: s.nodeOrigin, panZoom: s.panZoom, @@ -118,7 +117,7 @@ function MiniMap({ const onSvgNodeClick = onNodeClick ? useCallback((event: MouseEvent, nodeId: string) => { - const node = store.getState().nodeInternals.get(nodeId)!; + const node = store.getState().nodes.find((n) => n.id === nodeId)!; onNodeClick(event, node); }, []) : undefined; diff --git a/packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx b/packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx index adbd9810..b4311013 100644 --- a/packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx +++ b/packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx @@ -12,7 +12,7 @@ import type { MiniMapNodes, GetMiniMapNodeAttribute } from './types'; declare const window: any; const selector = (s: ReactFlowState) => s.nodeOrigin; -const selectorNodes = (s: ReactFlowState) => s.getNodes().filter((node) => !node.hidden && node.width && node.height); +const selectorNodes = (s: ReactFlowState) => s.nodes.filter((node) => !node.hidden && node.width && node.height); const getAttrFunction = (func: any): GetMiniMapNodeAttribute => (func instanceof Function ? func : () => func); function MiniMapNodes({ diff --git a/packages/react/src/additional-components/NodeResizer/ResizeControl.tsx b/packages/react/src/additional-components/NodeResizer/ResizeControl.tsx index cbf75c67..9eb62fd6 100644 --- a/packages/react/src/additional-components/NodeResizer/ResizeControl.tsx +++ b/packages/react/src/additional-components/NodeResizer/ResizeControl.tsx @@ -65,8 +65,8 @@ function ResizeControl({ const dragHandler = drag() .on('start', (event: ResizeDragEvent) => { - const { nodeInternals, transform, snapGrid, snapToGrid } = store.getState(); - const node = nodeInternals.get(id); + const { nodes, transform, snapGrid, snapToGrid } = store.getState(); + const node = nodes.find((n) => n.id === id); const { xSnapped, ySnapped } = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid }); prevValues.current = { @@ -86,9 +86,9 @@ function ResizeControl({ onResizeStart?.(event, { ...prevValues.current }); }) .on('drag', (event: ResizeDragEvent) => { - const { nodeInternals, transform, snapGrid, snapToGrid, triggerNodeChanges } = store.getState(); + const { nodes, transform, snapGrid, snapToGrid, triggerNodeChanges } = store.getState(); const { xSnapped, ySnapped } = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid }); - const node = nodeInternals.get(id); + const node = nodes.find((n) => n.id === id); if (node) { const changes: NodeChange[] = []; diff --git a/packages/react/src/additional-components/NodeToolbar/NodeToolbar.tsx b/packages/react/src/additional-components/NodeToolbar/NodeToolbar.tsx index 50d67f12..8710f88a 100644 --- a/packages/react/src/additional-components/NodeToolbar/NodeToolbar.tsx +++ b/packages/react/src/additional-components/NodeToolbar/NodeToolbar.tsx @@ -24,7 +24,7 @@ const nodesEqualityFn = (a: Node[], b: Node[]) => { const storeSelector = (state: ReactFlowState) => ({ transform: state.transform, nodeOrigin: state.nodeOrigin, - selectedNodesCount: state.getNodes().filter((node) => node.selected).length, + selectedNodesCount: state.nodes.filter((node) => node.selected).length, }); function getTransform(nodeRect: Rect, transform: Transform, position: Position, offset: number, align: Align): string { @@ -87,7 +87,7 @@ function NodeToolbar({ const nodeIds = Array.isArray(nodeId) ? nodeId : [nodeId || contextNodeId || '']; return nodeIds.reduce((acc, id) => { - const node = state.nodeInternals.get(id); + const node = state.nodes.find((n) => n.id === id); if (node) { acc.push(node); } diff --git a/packages/react/src/components/ConnectionLine/index.tsx b/packages/react/src/components/ConnectionLine/index.tsx index 757e1fba..439cc342 100644 --- a/packages/react/src/components/ConnectionLine/index.tsx +++ b/packages/react/src/components/ConnectionLine/index.tsx @@ -43,7 +43,7 @@ const ConnectionLine = ({ const { fromNode, handleId, toX, toY, connectionMode } = useStore( useCallback( (s: ReactFlowStore) => ({ - fromNode: s.nodeInternals.get(nodeId), + fromNode: s.nodes.find((n) => n.id === nodeId), handleId: s.connectionStartHandle?.handleId, toX: (s.connectionPosition.x - s.transform[0]) / s.transform[2], toY: (s.connectionPosition.y - s.transform[1]) / s.transform[2], diff --git a/packages/react/src/components/Edges/wrapEdge.tsx b/packages/react/src/components/Edges/wrapEdge.tsx index ea66d988..3a107e88 100644 --- a/packages/react/src/components/Edges/wrapEdge.tsx +++ b/packages/react/src/components/Edges/wrapEdge.tsx @@ -54,8 +54,8 @@ export default (EdgeComponent: ComponentType) => { const [updating, setUpdating] = useState(false); const store = useStoreApi(); const edgePosition = useStore((state) => { - const sourceNode = state.nodeInternals.get(source); - const targetNode = state.nodeInternals.get(target); + const sourceNode = state.nodes.find((n) => n.id === source); + const targetNode = state.nodes.find((n) => n.id === target); if (!sourceNode || !targetNode) { return null; @@ -118,7 +118,7 @@ export default (EdgeComponent: ComponentType) => { onConnectStart, onConnectEnd, cancelConnection, - getNodes, + nodes, panBy, updateConnection, } = store.getState(); @@ -128,7 +128,6 @@ export default (EdgeComponent: ComponentType) => { const isTarget = isSourceHandle; const edge = edges.find((e) => e.id === id)!; - const nodes = getNodes(); setUpdating(true); onEdgeUpdateStart?.(event, edge, handleType); diff --git a/packages/react/src/components/Handle/index.tsx b/packages/react/src/components/Handle/index.tsx index b2accaa8..925bc93e 100644 --- a/packages/react/src/components/Handle/index.tsx +++ b/packages/react/src/components/Handle/index.tsx @@ -106,7 +106,7 @@ const Handle = forwardRef( connectionMode: currentStore.connectionMode, connectionRadius: currentStore.connectionRadius, domNode: currentStore.domNode, - nodes: currentStore.getNodes(), + nodes: currentStore.nodes, lib: currentStore.lib, isTarget, handleId, diff --git a/packages/react/src/components/Nodes/utils.ts b/packages/react/src/components/Nodes/utils.ts index 9fab47ac..ab1a6672 100644 --- a/packages/react/src/components/Nodes/utils.ts +++ b/packages/react/src/components/Nodes/utils.ts @@ -11,7 +11,7 @@ export function getMouseHandler( return handler === undefined ? handler : (event: MouseEvent) => { - const node = getState().nodeInternals.get(id)!; + const node = getState().nodes.find((n) => n.id === id)!; handler(event, { ...node }); }; } @@ -34,8 +34,8 @@ export function handleNodeClick({ unselect?: boolean; nodeRef?: RefObject; }) { - const { addSelectedNodes, unselectNodesAndEdges, multiSelectionActive, nodeInternals } = store.getState(); - const node = nodeInternals.get(id)!; + const { addSelectedNodes, unselectNodesAndEdges, multiSelectionActive, nodes } = store.getState(); + const node = nodes.find((n) => n.id === id)!; store.setState({ nodesSelectionActive: false }); diff --git a/packages/react/src/components/Nodes/wrapNode.tsx b/packages/react/src/components/Nodes/wrapNode.tsx index 4d8fb8a0..c9c08135 100644 --- a/packages/react/src/components/Nodes/wrapNode.tsx +++ b/packages/react/src/components/Nodes/wrapNode.tsx @@ -78,7 +78,7 @@ export default (NodeComponent: ComponentType) => { } if (onClick) { - const node = store.getState().nodeInternals.get(id)!; + const node = store.getState().nodes.find((n) => n.id === id)!; onClick(event, { ...node }); } }; diff --git a/packages/react/src/components/NodesSelection/index.tsx b/packages/react/src/components/NodesSelection/index.tsx index 125425c1..f22c35ee 100644 --- a/packages/react/src/components/NodesSelection/index.tsx +++ b/packages/react/src/components/NodesSelection/index.tsx @@ -21,7 +21,7 @@ export type NodesSelectionProps = { }; const selector = (s: ReactFlowState) => { - const selectedNodes = s.getNodes().filter((n) => n.selected); + const selectedNodes = s.nodes.filter((n) => n.selected); const { width, height, x, y } = getRectOfNodes(selectedNodes, s.nodeOrigin); return { @@ -57,10 +57,7 @@ function NodesSelection({ onSelectionContextMenu, noPanClassName, disableKeyboar const onContextMenu = onSelectionContextMenu ? (event: MouseEvent) => { - const selectedNodes = store - .getState() - .getNodes() - .filter((n) => n.selected); + const selectedNodes = store.getState().nodes.filter((n) => n.selected); onSelectionContextMenu(event, selectedNodes); } : undefined; diff --git a/packages/react/src/components/SelectionListener/index.tsx b/packages/react/src/components/SelectionListener/index.tsx index 556d7ce8..92644f85 100644 --- a/packages/react/src/components/SelectionListener/index.tsx +++ b/packages/react/src/components/SelectionListener/index.tsx @@ -9,7 +9,7 @@ type SelectionListenerProps = { }; const selector = (s: ReactFlowState) => ({ - selectedNodes: s.getNodes().filter((n) => n.selected), + selectedNodes: s.nodes.filter((n) => n.selected), selectedEdges: s.edges.filter((e) => e.selected), }); diff --git a/packages/react/src/container/Pane/index.tsx b/packages/react/src/container/Pane/index.tsx index 981a155b..e1d26022 100644 --- a/packages/react/src/container/Pane/index.tsx +++ b/packages/react/src/container/Pane/index.tsx @@ -129,8 +129,7 @@ const Pane = memo( }; const onMouseMove = (event: ReactMouseEvent): void => { - const { userSelectionRect, edges, transform, onNodesChange, onEdgesChange, nodeOrigin, getNodes } = - store.getState(); + const { userSelectionRect, edges, transform, nodeOrigin, nodes, onNodesChange, onEdgesChange } = store.getState(); if (!isSelecting || !containerBounds.current || !userSelectionRect) { return; } @@ -149,7 +148,6 @@ const Pane = memo( height: Math.abs(mousePos.y - startY), }; - const nodes = getNodes(); const selectedNodes = getNodesInside( nodes, nextUserSelectRect, diff --git a/packages/react/src/hooks/useDrag.ts b/packages/react/src/hooks/useDrag.ts index e5f6ebb9..1cee64f3 100644 --- a/packages/react/src/hooks/useDrag.ts +++ b/packages/react/src/hooks/useDrag.ts @@ -22,14 +22,7 @@ function useDrag({ nodeRef, disabled = false, noDragClassName, handleSelector, n if (nodeRef?.current) { xyDrag.current = XYDrag({ domNode: nodeRef.current, - getStoreItems: () => { - const currentStore = store.getState(); - - return { - nodes: currentStore.getNodes(), - ...store.getState(), - }; - }, + getStoreItems: () => store.getState(), onNodeClick: () => { if (nodeId) { handleNodeClick({ diff --git a/packages/react/src/hooks/useGlobalKeyHandler.ts b/packages/react/src/hooks/useGlobalKeyHandler.ts index c592a34d..5d3c5bcd 100644 --- a/packages/react/src/hooks/useGlobalKeyHandler.ts +++ b/packages/react/src/hooks/useGlobalKeyHandler.ts @@ -4,6 +4,9 @@ import type { KeyCode } from '@xyflow/system'; import { useStoreApi } from '../hooks/useStore'; import useKeyPress from './useKeyPress'; import useReactFlow from './useReactFlow'; +import { Edge, Node } from '../types'; + +const getSelected = (item: Node | Edge) => item.selected; export default ({ deleteKeyCode, @@ -20,10 +23,8 @@ export default ({ useEffect(() => { if (deleteKeyPressed) { - const { edges, getNodes } = store.getState(); - const selectedNodes = getNodes().filter((node) => node.selected); - const selectedEdges = edges.filter((edge) => edge.selected); - deleteElements({ nodes: selectedNodes, edges: selectedEdges }); + const { edges, nodes } = store.getState(); + deleteElements({ nodes: nodes.filter(getSelected), edges: edges.filter(getSelected) }); store.setState({ nodesSelectionActive: false }); } }, [deleteKeyPressed]); diff --git a/packages/react/src/hooks/useNodes.ts b/packages/react/src/hooks/useNodes.ts index 8d71b435..cc75f142 100644 --- a/packages/react/src/hooks/useNodes.ts +++ b/packages/react/src/hooks/useNodes.ts @@ -3,7 +3,7 @@ import { shallow } from 'zustand/shallow'; import { useStore } from '../hooks/useStore'; import type { Node, ReactFlowState } from '../types'; -const nodesSelector = (state: ReactFlowState) => state.getNodes(); +const nodesSelector = (state: ReactFlowState) => state.nodes; function useNodes(): Node[] { const nodes = useStore(nodesSelector, shallow); diff --git a/packages/react/src/hooks/useNodesInitialized.ts b/packages/react/src/hooks/useNodesInitialized.ts index ea779fb6..3e869873 100644 --- a/packages/react/src/hooks/useNodesInitialized.ts +++ b/packages/react/src/hooks/useNodesInitialized.ts @@ -8,12 +8,11 @@ export type UseNodesInitializedOptions = { }; const selector = (options: UseNodesInitializedOptions) => (s: ReactFlowState) => { - if (s.nodeInternals.size === 0) { + if (s.nodes.length === 0) { return false; } - return s - .getNodes() + return s.nodes .filter((n) => (options.includeHiddenNodes ? true : !n.hidden)) .every((n) => n[internalsSymbol]?.handleBounds !== undefined); }; diff --git a/packages/react/src/hooks/useReactFlow.ts b/packages/react/src/hooks/useReactFlow.ts index f5e15231..45a8dbe1 100644 --- a/packages/react/src/hooks/useReactFlow.ts +++ b/packages/react/src/hooks/useReactFlow.ts @@ -23,14 +23,11 @@ export default function useReactFlow(): ReactFlo const store = useStoreApi(); const getNodes = useCallback>(() => { - return store - .getState() - .getNodes() - .map((n) => ({ ...n })); + return store.getState().nodes.map((n) => ({ ...n })); }, []); const getNode = useCallback>((id) => { - return store.getState().nodeInternals.get(id); + return store.getState().nodes.find((n) => n.id === id); }, []); const getEdges = useCallback>(() => { @@ -44,8 +41,7 @@ export default function useReactFlow(): ReactFlo }, []); const setNodes = useCallback>((payload) => { - const { getNodes, setNodes, hasDefaultNodes, onNodesChange } = store.getState(); - const nodes = getNodes(); + const { nodes, setNodes, hasDefaultNodes, onNodesChange } = store.getState(); const nextNodes = typeof payload === 'function' ? payload(nodes) : payload; if (hasDefaultNodes) { @@ -76,10 +72,9 @@ export default function useReactFlow(): ReactFlo const addNodes = useCallback>((payload) => { const nodes = Array.isArray(payload) ? payload : [payload]; - const { getNodes, setNodes, hasDefaultNodes, onNodesChange } = store.getState(); + const { nodes: currentNodes, hasDefaultNodes, onNodesChange, setNodes } = store.getState(); if (hasDefaultNodes) { - const currentNodes = getNodes(); const nextNodes = [...currentNodes, ...nodes]; setNodes(nextNodes); } else if (onNodesChange) { @@ -101,10 +96,10 @@ export default function useReactFlow(): ReactFlo }, []); const toObject = useCallback>(() => { - const { getNodes, edges = [], transform } = store.getState(); + const { nodes = [], edges = [], transform } = store.getState(); const [x, y, zoom] = transform; return { - nodes: getNodes().map((n) => ({ ...n })), + nodes: nodes.map((n) => ({ ...n })), edges: edges.map((e) => ({ ...e })), viewport: { x, @@ -116,8 +111,7 @@ export default function useReactFlow(): ReactFlo const deleteElements = useCallback(({ nodes: nodesDeleted, edges: edgesDeleted }) => { const { - nodeInternals, - getNodes, + nodes, edges, hasDefaultNodes, hasDefaultEdges, @@ -129,7 +123,7 @@ export default function useReactFlow(): ReactFlo const { matchingNodes, matchingEdges } = getElementsToRemove({ nodesToRemove: nodesDeleted || [], edgesToRemove: edgesDeleted || [], - nodes: getNodes(), + nodes, edges, }); @@ -142,12 +136,8 @@ export default function useReactFlow(): ReactFlo } if (hasDefaultNodes) { - matchingNodes.forEach((node) => { - nodeInternals.delete(node.id); - }); - store.setState({ - nodeInternals: new Map(nodeInternals), + nodes: nodes.filter((n) => !matchingNodes.some((mN) => mN.id === n.id)), }); } } @@ -181,7 +171,7 @@ export default function useReactFlow(): ReactFlo nodeOrRect: (Partial> & { id: Node['id'] }) | Rect ): [Rect | null, Node | null | undefined, boolean] => { const isRect = isRectObject(nodeOrRect); - const node = isRect ? null : store.getState().nodeInternals.get(nodeOrRect.id); + const node = isRect ? null : store.getState().nodes.find((n) => n.id === nodeOrRect.id); if (!isRect && !node) { [null, null, isRect]; @@ -202,7 +192,7 @@ export default function useReactFlow(): ReactFlo return []; } - return (nodes || store.getState().getNodes()).filter((n) => { + return (nodes || store.getState().nodes).filter((n) => { if (!isRect && (n.id === node!.id || !n.positionAbsolute)) { return false; } diff --git a/packages/react/src/hooks/useUpdateNodePositions.ts b/packages/react/src/hooks/useUpdateNodePositions.ts index a65f14ee..91576b5f 100644 --- a/packages/react/src/hooks/useUpdateNodePositions.ts +++ b/packages/react/src/hooks/useUpdateNodePositions.ts @@ -7,9 +7,7 @@ function useUpdateNodePositions() { const store = useStoreApi(); const updatePositions = useCallback((params: { x: number; y: number; isShiftPressed: boolean }) => { - const { nodeExtent, updateNodePositions, getNodes, snapToGrid, snapGrid, onError, nodesDraggable } = - store.getState(); - const nodes = getNodes(); + const { nodeExtent, updateNodePositions, nodes, snapToGrid, snapGrid, onError, nodesDraggable } = store.getState(); const selectedNodes = nodes.filter( (n) => n.selected && (n.draggable || (nodesDraggable && typeof n.draggable === 'undefined')) ); diff --git a/packages/react/src/hooks/useViewportHelper.ts b/packages/react/src/hooks/useViewportHelper.ts index 67301e04..aded1f17 100644 --- a/packages/react/src/hooks/useViewportHelper.ts +++ b/packages/react/src/hooks/useViewportHelper.ts @@ -36,12 +36,12 @@ const useViewportHelper = (): ViewportHelperFunctions => { return { x, y, zoom }; }, fitView: (options) => { - const { getNodes, width, height, nodeOrigin, minZoom, maxZoom, panZoom } = store.getState(); + const { nodes, width, height, nodeOrigin, minZoom, maxZoom, panZoom } = store.getState(); return panZoom ? fitView( { - nodes: getNodes(), + nodes, width, height, nodeOrigin, diff --git a/packages/react/src/hooks/useVisibleEdges.ts b/packages/react/src/hooks/useVisibleEdges.ts index bb1ac052..6d7f808a 100644 --- a/packages/react/src/hooks/useVisibleEdges.ts +++ b/packages/react/src/hooks/useVisibleEdges.ts @@ -12,8 +12,8 @@ function useVisibleEdges(onlyRenderVisible: boolean, elevateEdgesOnSelect: boole const visibleEdges = onlyRenderVisible && s.width && s.height ? s.edges.filter((e) => { - const sourceNode = s.nodeInternals.get(e.source); - const targetNode = s.nodeInternals.get(e.target); + const sourceNode = s.nodes.find((n) => n.id === e.source); + const targetNode = s.nodes.find((n) => n.id === e.target); return ( sourceNode && @@ -29,7 +29,7 @@ function useVisibleEdges(onlyRenderVisible: boolean, elevateEdgesOnSelect: boole }) : s.edges; - return groupEdgesByZLevel(visibleEdges, s.nodeInternals, elevateEdgesOnSelect); + return groupEdgesByZLevel(visibleEdges, s.nodes, elevateEdgesOnSelect); }, [onlyRenderVisible, elevateEdgesOnSelect] ), diff --git a/packages/react/src/hooks/useVisibleNodes.ts b/packages/react/src/hooks/useVisibleNodes.ts index c0fdb56a..ddbb2794 100644 --- a/packages/react/src/hooks/useVisibleNodes.ts +++ b/packages/react/src/hooks/useVisibleNodes.ts @@ -9,8 +9,8 @@ function useVisibleNodes(onlyRenderVisible: boolean) { useCallback( (s: ReactFlowState) => onlyRenderVisible - ? getNodesInside(s.getNodes(), { x: 0, y: 0, width: s.width, height: s.height }, s.transform, true) - : s.getNodes(), + ? getNodesInside(s.nodes, { x: 0, y: 0, width: s.width, height: s.height }, s.transform, true) + : s.nodes, [onlyRenderVisible] ) ); diff --git a/packages/react/src/store/index.ts b/packages/react/src/store/index.ts index c5cfbc65..e227d113 100644 --- a/packages/react/src/store/index.ts +++ b/packages/react/src/store/index.ts @@ -26,11 +26,11 @@ const createRFStore = () => createStore((set, get) => ({ ...initialState, setNodes: (nodes: Node[]) => { - const { nodeInternals, nodeOrigin, elevateNodesOnSelect } = get(); - set({ nodeInternals: createNodeInternals(nodes, nodeInternals, nodeOrigin, elevateNodesOnSelect) }); + const { nodes: storeNodes, nodeOrigin, elevateNodesOnSelect } = get(); + set({ nodes: createNodeInternals(nodes, storeNodes, nodeOrigin, elevateNodesOnSelect) }); }, getNodes: () => { - return Array.from(get().nodeInternals.values()); + return get().nodes; }, setEdges: (edges: Edge[]) => { const { defaultEdgeOptions = {} } = get(); @@ -40,17 +40,17 @@ const createRFStore = () => const hasDefaultNodes = typeof nodes !== 'undefined'; const hasDefaultEdges = typeof edges !== 'undefined'; - const nodeInternals = hasDefaultNodes - ? createNodeInternals(nodes, new Map(), get().nodeOrigin, get().elevateNodesOnSelect) - : new Map(); + const nextNodes = hasDefaultNodes + ? createNodeInternals(nodes, [], get().nodeOrigin, get().elevateNodesOnSelect) + : []; const nextEdges = hasDefaultEdges ? edges : []; - set({ nodeInternals, edges: nextEdges, hasDefaultNodes, hasDefaultEdges }); + set({ nodes: nextNodes, edges: nextEdges, hasDefaultNodes, hasDefaultEdges }); }, updateNodeDimensions: (updates) => { const { onNodesChange, - nodeInternals, + nodes, fitViewOnInit, fitViewOnInitDone, fitViewOnInitOptions, @@ -70,11 +70,12 @@ const createRFStore = () => const style = window.getComputedStyle(viewportNode); const { m22: zoom } = new window.DOMMatrixReadOnly(style.transform); + const changes: NodeDimensionChange[] = []; - const changes: NodeDimensionChange[] = updates.reduce((res, update) => { - const node = nodeInternals.get(update.id); + const nextNodes = nodes.map((node) => { + const update = updates.find((change) => change.id === node.id); - if (node) { + if (update) { const dimensions = getDimensions(update.nodeElement); const doUpdate = !!( dimensions.width && @@ -83,8 +84,15 @@ const createRFStore = () => ); if (doUpdate) { - nodeInternals.set(node.id, { + changes.push({ + id: node.id, + type: 'dimensions', + dimensions, + }); + + return { ...node, + ...dimensions, [internalsSymbol]: { ...node[internalsSymbol], handleBounds: { @@ -92,21 +100,14 @@ const createRFStore = () => target: getHandleBounds('.target', update.nodeElement, zoom, node.origin || nodeOrigin), }, }, - ...dimensions, - }); - - res.push({ - id: node.id, - type: 'dimensions', - dimensions, - }); + }; } } - return res; - }, []); + return node; + }); - updateAbsoluteNodePositions(nodeInternals, nodeOrigin); + updateAbsoluteNodePositions(nextNodes, nodeOrigin); const nextFitViewOnInitDone = fitViewOnInitDone || @@ -115,7 +116,7 @@ const createRFStore = () => !!panZoom && fitView( { - nodes: Array.from(nodeInternals.values()), + nodes: nextNodes, width, height, panZoom, @@ -125,7 +126,7 @@ const createRFStore = () => }, fitViewOnInitOptions )); - set({ nodeInternals: new Map(nodeInternals), fitViewOnInitDone: nextFitViewOnInitDone }); + set({ nodes: nextNodes, fitViewOnInitDone: nextFitViewOnInitDone }); if (changes?.length > 0) { onNodesChange?.(changes); @@ -153,13 +154,13 @@ const createRFStore = () => }, triggerNodeChanges: (changes) => { - const { onNodesChange, nodeInternals, hasDefaultNodes, nodeOrigin, getNodes, elevateNodesOnSelect } = get(); + const { onNodesChange, nodes, hasDefaultNodes, nodeOrigin, elevateNodesOnSelect } = get(); if (changes?.length) { if (hasDefaultNodes) { - const nodes = applyNodeChanges(changes, getNodes()); - const nextNodeInternals = createNodeInternals(nodes, nodeInternals, nodeOrigin, elevateNodesOnSelect); - set({ nodeInternals: nextNodeInternals }); + const updatedNodes = applyNodeChanges(changes, nodes); + const nextNodes = createNodeInternals(updatedNodes, nodes, nodeOrigin, elevateNodesOnSelect); + set({ nodes: nextNodes }); } onNodesChange?.(changes); @@ -167,14 +168,14 @@ const createRFStore = () => }, addSelectedNodes: (selectedNodeIds) => { - const { multiSelectionActive, edges, getNodes } = get(); + const { multiSelectionActive, edges, nodes } = get(); let changedNodes: NodeSelectionChange[]; let changedEdges: EdgeSelectionChange[] | null = null; if (multiSelectionActive) { changedNodes = selectedNodeIds.map((nodeId) => createSelectionChange(nodeId, true)) as NodeSelectionChange[]; } else { - changedNodes = getSelectionChanges(getNodes(), selectedNodeIds); + changedNodes = getSelectionChanges(nodes, selectedNodeIds); changedEdges = getSelectionChanges(edges, []); } @@ -186,7 +187,7 @@ const createRFStore = () => }); }, addSelectedEdges: (selectedEdgeIds) => { - const { multiSelectionActive, edges, getNodes } = get(); + const { multiSelectionActive, edges, nodes } = get(); let changedEdges: EdgeSelectionChange[]; let changedNodes: NodeSelectionChange[] | null = null; @@ -194,7 +195,7 @@ const createRFStore = () => changedEdges = selectedEdgeIds.map((edgeId) => createSelectionChange(edgeId, true)) as EdgeSelectionChange[]; } else { changedEdges = getSelectionChanges(edges, selectedEdgeIds); - changedNodes = getSelectionChanges(getNodes(), []); + changedNodes = getSelectionChanges(nodes, []); } updateNodesAndEdgesSelections({ @@ -205,8 +206,8 @@ const createRFStore = () => }); }, unselectNodesAndEdges: ({ nodes, edges }: UnselectNodesAndEdgesParams = {}) => { - const { edges: storeEdges, getNodes } = get(); - const nodesToUnselect = nodes ? nodes : getNodes(); + const { edges: storeEdges, nodes: storeNodes } = get(); + const nodesToUnselect = nodes ? nodes : storeNodes; const edgesToUnselect = edges ? edges : storeEdges; const changedNodes = nodesToUnselect.map((n) => { @@ -242,8 +243,7 @@ const createRFStore = () => set({ translateExtent }); }, resetSelectedElements: () => { - const { edges, getNodes } = get(); - const nodes = getNodes(); + const { edges, nodes } = get(); const nodesToUnselect = nodes .filter((e) => e.selected) @@ -260,15 +260,18 @@ const createRFStore = () => }); }, setNodeExtent: (nodeExtent) => { - const { nodeInternals } = get(); - - nodeInternals.forEach((node) => { - node.positionAbsolute = clampPosition(node.position, nodeExtent); - }); + const { nodes } = get(); set({ nodeExtent, - nodeInternals: new Map(nodeInternals), + nodes: nodes.map((node) => { + const positionAbsolute = clampPosition(node.position, nodeExtent); + + return { + ...node, + positionAbsolute, + }; + }), }); }, panBy: (delta): boolean => { diff --git a/packages/react/src/store/initialState.ts b/packages/react/src/store/initialState.ts index 9bca96d2..3ecb8c2b 100644 --- a/packages/react/src/store/initialState.ts +++ b/packages/react/src/store/initialState.ts @@ -7,7 +7,7 @@ const initialState: ReactFlowStore = { width: 0, height: 0, transform: [0, 0, 1], - nodeInternals: new Map(), + nodes: [], edges: [], onNodesChange: null, onEdgesChange: null, diff --git a/packages/react/src/store/utils.ts b/packages/react/src/store/utils.ts index b78ed257..209a807b 100644 --- a/packages/react/src/store/utils.ts +++ b/packages/react/src/store/utils.ts @@ -7,25 +7,21 @@ import { type NodeOrigin, } from '@xyflow/system'; -import type { Edge, EdgeSelectionChange, Node, NodeInternals, NodeSelectionChange, ReactFlowState } from '../types'; +import type { Edge, EdgeSelectionChange, Node, NodeSelectionChange, ReactFlowState } from '../types'; type ParentNodes = Record; -function calculateXYZPosition( - node: Node, - nodeInternals: NodeInternals, - result: XYZPosition, - nodeOrigin: NodeOrigin -): XYZPosition { +function calculateXYZPosition(node: Node, nodes: Node[], result: XYZPosition, nodeOrigin: NodeOrigin): XYZPosition { if (!node.parentNode) { return result; } - const parentNode = nodeInternals.get(node.parentNode)!; + + const parentNode = nodes.find((n) => n.id === node.parentNode)!; const parentNodePosition = getNodePositionWithOrigin(parentNode, parentNode?.origin || nodeOrigin); return calculateXYZPosition( parentNode, - nodeInternals, + nodes, { x: (result.x ?? 0) + parentNodePosition.x, y: (result.y ?? 0) + parentNodePosition.y, @@ -35,21 +31,17 @@ function calculateXYZPosition( ); } -export function updateAbsoluteNodePositions( - nodeInternals: NodeInternals, - nodeOrigin: NodeOrigin, - parentNodes?: ParentNodes -) { - nodeInternals.forEach((node) => { - if (node.parentNode && !nodeInternals.has(node.parentNode)) { +export function updateAbsoluteNodePositions(nodes: Node[], nodeOrigin: NodeOrigin, parentNodes?: ParentNodes) { + nodes.forEach((node) => { + if (node.parentNode && !nodes.find((n) => n.id === node.parentNode)) { throw new Error(`Parent node ${node.parentNode} not found`); } if (node.parentNode || parentNodes?.[node.id]) { - const parentNode = node.parentNode ? nodeInternals.get(node.parentNode) : null; + const parentNode = node.parentNode ? nodes.find((n) => n.id === node.parentNode) : null; const { x, y, z } = calculateXYZPosition( node, - nodeInternals, + nodes, { ...node.position, z: node[internalsSymbol]?.z ?? 0, @@ -73,19 +65,19 @@ export function updateAbsoluteNodePositions( export function createNodeInternals( nodes: Node[], - nodeInternals: NodeInternals, + storeNodes: Node[], nodeOrigin: NodeOrigin, elevateNodesOnSelect: boolean -): NodeInternals { - const nextNodeInternals = new Map(); +): Node[] { + const nextNodes: Node[] = []; const parentNodes: ParentNodes = {}; const selectedNodeZ: number = elevateNodesOnSelect ? 1000 : 0; nodes.forEach((node) => { const z = (isNumeric(node.zIndex) ? node.zIndex : 0) + (node.selected ? selectedNodeZ : 0); - const currInternals = nodeInternals.get(node.id); + const currInternals = storeNodes.find((n) => n.id === node.id); - const internals: Node = { + const updatedNode: Node = { width: currInternals?.width, height: currInternals?.height, ...node, @@ -96,11 +88,11 @@ export function createNodeInternals( }; if (node.parentNode) { - internals.parentNode = node.parentNode; + updatedNode.parentNode = node.parentNode; parentNodes[node.parentNode] = true; } - Object.defineProperty(internals, internalsSymbol, { + Object.defineProperty(updatedNode, internalsSymbol, { enumerable: false, value: { handleBounds: currInternals?.[internalsSymbol]?.handleBounds, @@ -108,36 +100,26 @@ export function createNodeInternals( }, }); - nextNodeInternals.set(node.id, internals); + nextNodes.push(updatedNode); }); - updateAbsoluteNodePositions(nextNodeInternals, nodeOrigin, parentNodes); + updateAbsoluteNodePositions(nodes, nodeOrigin, parentNodes); - return nextNodeInternals; + return nextNodes; } -export function handleControlledNodeSelectionChange(nodeChanges: NodeSelectionChange[], nodeInternals: NodeInternals) { - nodeChanges.forEach((change) => { - const node = nodeInternals.get(change.id); - if (node) { - nodeInternals.set(node.id, { - ...node, - [internalsSymbol]: node[internalsSymbol], - selected: change.selected, - }); - } - }); +export function handleControlledSelectionChange( + changes: NodeSelectionChange[] | EdgeSelectionChange[], + items: NodeOrEdge[] +): NodeOrEdge[] { + return items.map((item) => { + const change = changes.find((change) => change.id === item.id); - return new Map(nodeInternals); -} - -export function handleControlledEdgeSelectionChange(edgeChanges: EdgeSelectionChange[], edges: Edge[]) { - return edges.map((e) => { - const change = edgeChanges.find((change) => change.id === e.id); if (change) { - e.selected = change.selected; + item.selected = change.selected; } - return e; + + return item; }); } @@ -149,11 +131,11 @@ type UpdateNodesAndEdgesParams = { }; export function updateNodesAndEdgesSelections({ changedNodes, changedEdges, get, set }: UpdateNodesAndEdgesParams) { - const { nodeInternals, edges, onNodesChange, onEdgesChange, hasDefaultNodes, hasDefaultEdges } = get(); + const { nodes, edges, onNodesChange, onEdgesChange, hasDefaultNodes, hasDefaultEdges } = get(); if (changedNodes?.length) { if (hasDefaultNodes) { - set({ nodeInternals: handleControlledNodeSelectionChange(changedNodes, nodeInternals) }); + set({ nodes: handleControlledSelectionChange(changedNodes, nodes) }); } onNodesChange?.(changedNodes); @@ -161,7 +143,7 @@ export function updateNodesAndEdgesSelections({ changedNodes, changedEdges, get, if (changedEdges?.length) { if (hasDefaultEdges) { - set({ edges: handleControlledEdgeSelectionChange(changedEdges, edges) }); + set({ edges: handleControlledSelectionChange(changedEdges, edges) }); } onEdgesChange?.(changedEdges); diff --git a/packages/react/src/types/nodes.ts b/packages/react/src/types/nodes.ts index ee948e54..794a1cf1 100644 --- a/packages/react/src/types/nodes.ts +++ b/packages/react/src/types/nodes.ts @@ -41,5 +41,3 @@ export type WrapNodeProps = Pick< rfId: string; disableKeyboardA11y: boolean; }; - -export type NodeInternals = Map; diff --git a/packages/react/src/types/store.ts b/packages/react/src/types/store.ts index 12240ff9..5e489a41 100644 --- a/packages/react/src/types/store.ts +++ b/packages/react/src/types/store.ts @@ -32,7 +32,6 @@ import type { NodeChange, OnNodesChange, OnEdgesChange, - NodeInternals, DefaultEdgeOptions, FitViewOptions, OnNodesDelete, @@ -46,7 +45,7 @@ export type ReactFlowStore = { width: number; height: number; transform: Transform; - nodeInternals: NodeInternals; + nodes: Node[]; edges: Edge[]; onNodesChange: OnNodesChange | null; onEdgesChange: OnEdgesChange | null; diff --git a/packages/system/src/utils/edges/general.ts b/packages/system/src/utils/edges/general.ts index 2c4d9ab6..3184933b 100644 --- a/packages/system/src/utils/edges/general.ts +++ b/packages/system/src/utils/edges/general.ts @@ -33,13 +33,11 @@ export type GroupedEdges = { export function groupEdgesByZLevel( edges: EdgeType[], - nodes: Map | BaseNode[], + nodes: BaseNode[], elevateEdgesOnSelect = false ): GroupedEdges[] { let maxLevel = -1; - const isNodeInternals = 'get' in nodes; - const levelLookup = edges.reduce>((tree, edge) => { const hasZIndex = isNumeric(edge.zIndex); let z = hasZIndex ? edge.zIndex! : 0; @@ -48,10 +46,8 @@ export function groupEdgesByZLevel( z = hasZIndex ? edge.zIndex! : Math.max( - (isNodeInternals ? nodes.get(edge.source) : nodes.find((n) => n.id === edge.source))?.[internalsSymbol] - ?.z || 0, - (isNodeInternals ? nodes.get(edge.target) : nodes.find((n) => n.id === edge.target))?.[internalsSymbol] - ?.z || 0 + nodes.find((n) => n.id === edge.source)?.[internalsSymbol]?.z || 0, + nodes.find((n) => n.id === edge.target)?.[internalsSymbol]?.z || 0 ); }