diff --git a/example/src/UseReactFlow/index.tsx b/example/src/UseReactFlow/index.tsx index 83cb6b33..e9553bf3 100644 --- a/example/src/UseReactFlow/index.tsx +++ b/example/src/UseReactFlow/index.tsx @@ -33,7 +33,18 @@ const UseZoomPanHelperFlow = () => { const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); const onConnect = (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)); - const { project, setCenter, zoomIn, zoomOut, fitView, addNodes, setNodes: setNodesHook, addEdges } = useReactFlow(); + const { + project, + setCenter, + zoomIn, + zoomOut, + fitView, + addNodes, + setNodes: setNodesHook, + addEdges, + getNodes, + getEdges, + } = useReactFlow(); const onPaneClick = useCallback( (evt: MouseEvent) => { @@ -72,6 +83,11 @@ const UseZoomPanHelperFlow = () => { addNodes(newNode); }, [addNodes]); + const logNodes = useCallback(() => { + console.log('nodes', getNodes()); + console.log('edges', getEdges()); + }, [getNodes]); + useEffect(() => { addEdges({ id: 'e3-4', source: '3', target: '4' }); }, [addEdges]); @@ -97,6 +113,7 @@ const UseZoomPanHelperFlow = () => { + diff --git a/src/components/ConnectionLine/index.tsx b/src/components/ConnectionLine/index.tsx index 5a287ebf..2fa29ba0 100644 --- a/src/components/ConnectionLine/index.tsx +++ b/src/components/ConnectionLine/index.tsx @@ -6,6 +6,7 @@ import { getBezierPath } from '../Edges/BezierEdge'; import { getSmoothStepPath } from '../Edges/SmoothStepEdge'; import { ConnectionLineType, ConnectionLineComponent, HandleType, Node, ReactFlowState, Position } from '../../types'; import { getSimpleBezierPath } from '../Edges/SimpleBezierEdge'; +import { internalsSymbol } from '../../utils'; interface ConnectionLineProps { connectionNodeId: string; @@ -37,12 +38,13 @@ export default ({ const { nodeInternals, transform } = useStore(selector, shallow); const fromNode = useRef(nodeInternals.get(nodeId)); + const fromHandleBounds = fromNode.current?.[internalsSymbol]?.handleBounds; - if (!fromNode.current || !isConnectable || !fromNode.current.handleBounds?.[connectionHandleType]) { + if (!fromNode.current || !isConnectable || !fromHandleBounds?.[connectionHandleType]) { return null; } - const handleBound = fromNode.current.handleBounds?.[connectionHandleType]; + const handleBound = fromHandleBounds[connectionHandleType]; const fromHandle = handleId ? handleBound?.find((d) => d.id === handleId) : handleBound?.[0]; const fromHandleX = fromHandle ? fromHandle.x + fromHandle.width / 2 : (fromNode.current?.width ?? 0) / 2; const fromHandleY = fromHandle ? fromHandle.y + fromHandle.height / 2 : fromNode.current?.height ?? 0; diff --git a/src/container/EdgeRenderer/utils.ts b/src/container/EdgeRenderer/utils.ts index caa18181..c02d9361 100644 --- a/src/container/EdgeRenderer/utils.ts +++ b/src/container/EdgeRenderer/utils.ts @@ -13,7 +13,7 @@ import { Transform, XYPosition, } from '../../types'; -import { rectToBox } from '../../utils'; +import { internalsSymbol, rectToBox } from '../../utils'; export type CreateEdgeTypes = (edgeTypes: EdgeTypes) => EdgeTypesWrapped; @@ -168,10 +168,11 @@ export function isEdgeVisible({ export function getNodeData(nodeInternals: NodeInternals, nodeId: string): [Rect, NodeHandleBounds | null, boolean] { const node = nodeInternals.get(nodeId); - const handleBounds = node?.handleBounds; + const handleBounds = node?.[internalsSymbol]?.handleBounds || null; + const isInvalid = !node || - !node.handleBounds || + !handleBounds || !node.width || !node.height || typeof node.positionAbsolute?.x === 'undefined' || @@ -184,7 +185,7 @@ export function getNodeData(nodeInternals: NodeInternals, nodeId: string): [Rect width: node?.width || 0, height: node?.height || 0, }, - handleBounds || null, + handleBounds, !isInvalid, ]; } diff --git a/src/container/NodeRenderer/index.tsx b/src/container/NodeRenderer/index.tsx index 671559e7..ba3ea773 100644 --- a/src/container/NodeRenderer/index.tsx +++ b/src/container/NodeRenderer/index.tsx @@ -11,6 +11,7 @@ import { ReactFlowState, WrapNodeProps, } from '../../types'; +import { internalsSymbol } from '../../utils'; interface NodeRendererProps { nodeTypes: NodeTypesWrapped; @@ -117,8 +118,8 @@ const NodeRenderer = (props: NodeRendererProps) => { isConnectable={isConnectable} resizeObserver={resizeObserver} dragHandle={node.dragHandle} - zIndex={node.z ?? 0} - isParent={!!node.isParent} + zIndex={node[internalsSymbol]?.z ?? 0} + isParent={!!node[internalsSymbol]?.isParent} noDragClassName={props.noDragClassName} noPanClassName={props.noPanClassName} /> diff --git a/src/hooks/useVisibleEdges.ts b/src/hooks/useVisibleEdges.ts index 763f41ee..02f4db53 100644 --- a/src/hooks/useVisibleEdges.ts +++ b/src/hooks/useVisibleEdges.ts @@ -3,7 +3,7 @@ import { useCallback } from 'react'; import { useStore } from '../store'; import { isEdgeVisible } from '../container/EdgeRenderer/utils'; import { ReactFlowState, NodeInternals, Edge } from '../types'; -import { isNumeric } from '../utils'; +import { internalsSymbol, isNumeric } from '../utils'; const defaultEdgeTree = [{ level: 0, isMaxLevel: true, edges: [] }]; @@ -17,7 +17,10 @@ function groupEdgesByZLevel(edges: Edge[], nodeInternals: NodeInternals, elevate if (elevateEdgesOnSelect) { z = hasZIndex ? edge.zIndex! - : Math.max(nodeInternals.get(edge.source)?.z || 0, nodeInternals.get(edge.target)?.z || 0); + : Math.max( + nodeInternals.get(edge.source)?.[internalsSymbol]?.z || 0, + nodeInternals.get(edge.target)?.[internalsSymbol]?.z || 0 + ); } if (tree[z]) { diff --git a/src/store/index.ts b/src/store/index.ts index e5c271a9..cfcd63b8 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,7 +1,7 @@ import create from 'zustand'; import createContext from 'zustand/context'; -import { clampPosition, getDimensions } from '../utils'; +import { clampPosition, getDimensions, internalsSymbol } from '../utils'; import { applyNodeChanges } from '../utils/changes'; import { ReactFlowState, @@ -60,7 +60,10 @@ const createStore = () => const handleBounds = getHandleBounds(update.nodeElement, transform[2]); nodeInternals.set(node.id, { ...node, - handleBounds, + [internalsSymbol]: { + ...node[internalsSymbol], + handleBounds, + }, ...dimensions, }); diff --git a/src/store/utils.ts b/src/store/utils.ts index d7c5921e..3bc89963 100644 --- a/src/store/utils.ts +++ b/src/store/utils.ts @@ -1,7 +1,7 @@ import { zoomIdentity } from 'd3-zoom'; import { GetState, SetState } from 'zustand'; -import { isNumeric } from '../utils'; +import { internalsSymbol, isNumeric } from '../utils'; import { getD3Transition, getRectOfNodes, getTransformForBounds } from '../utils/graph'; import { Edge, @@ -30,7 +30,7 @@ function calculateXYZPosition( return calculateXYZPosition(parentNode, nodeInternals, parentNodes, { x: (result.x ?? 0) + (parentNode.position?.x ?? 0), y: (result.y ?? 0) + (parentNode.position?.y ?? 0), - z: (parentNode.z ?? 0) > (result.z ?? 0) ? parentNode.z ?? 0 : result.z ?? 0, + z: (parentNode[internalsSymbol]?.z ?? 0) > (result.z ?? 0) ? parentNode[internalsSymbol]?.z ?? 0 : result.z ?? 0, }); } @@ -45,18 +45,26 @@ export function createNodeInternals(nodes: Node[], nodeInternals: NodeInternals) const internals: Node = { width: currInternals?.width, height: currInternals?.height, - handleBounds: currInternals?.handleBounds, ...node, positionAbsolute: { x: node.position.x, y: node.position.y, }, - z, }; + if (node.parentNode) { internals.parentNode = node.parentNode; parentNodes[node.parentNode] = true; } + + Object.defineProperty(internals, internalsSymbol, { + enumerable: false, + value: { + handleBounds: currInternals?.[internalsSymbol]?.handleBounds, + z, + }, + }); + nextNodeInternals.set(node.id, internals); }); @@ -68,7 +76,7 @@ export function createNodeInternals(nodes: Node[], nodeInternals: NodeInternals) if (node.parentNode || parentNodes[node.id]) { const { x, y, z } = calculateXYZPosition(node, nextNodeInternals, parentNodes, { ...node.position, - z: node.z ?? 0, + z: node[internalsSymbol]?.z ?? 0, }); node.positionAbsolute = { @@ -76,10 +84,10 @@ export function createNodeInternals(nodes: Node[], nodeInternals: NodeInternals) y, }; - node.z = z; + node[internalsSymbol]!.z = z; if (parentNodes[node.id]) { - node.isParent = true; + node[internalsSymbol]!.isParent = true; } } }); diff --git a/src/types/nodes.ts b/src/types/nodes.ts index b8d0927e..d73b0c97 100644 --- a/src/types/nodes.ts +++ b/src/types/nodes.ts @@ -2,6 +2,7 @@ import { CSSProperties, MouseEvent as ReactMouseEvent } from 'react'; import { XYPosition, Position, CoordinateExtent } from './utils'; import { HandleElement } from './handles'; +import { internalsSymbol } from '../utils'; // interface for the user node items export interface Node { @@ -26,12 +27,14 @@ export interface Node { zIndex?: number; extent?: 'parent' | CoordinateExtent; expandParent?: boolean; + positionAbsolute?: XYPosition; // only used internally - positionAbsolute?: XYPosition; - z?: number; - handleBounds?: NodeHandleBounds; - isParent?: boolean; + [internalsSymbol]?: { + z?: number; + handleBounds?: NodeHandleBounds; + isParent?: boolean; + }; } // props that get passed to a custom node diff --git a/src/utils/index.ts b/src/utils/index.ts index 926977cb..9ef2661b 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -40,3 +40,5 @@ export const getBoundsofRects = (rect1: Rect, rect2: Rect): Rect => boxToRect(getBoundsOfBoxes(rectToBox(rect1), rectToBox(rect2))); export const isNumeric = (n: any): n is number => !isNaN(n) && isFinite(n); + +export const internalsSymbol = Symbol('internals');