diff --git a/src/components/ConnectionLine/index.tsx b/src/components/ConnectionLine/index.tsx index 43a98d5a..f3839442 100644 --- a/src/components/ConnectionLine/index.tsx +++ b/src/components/ConnectionLine/index.tsx @@ -5,7 +5,6 @@ import { useStore } from '../../store'; import { getBezierPath } from '../Edges/BezierEdge'; import { getSmoothStepPath } from '../Edges/SmoothStepEdge'; import { - NodeInternalsItem, HandleElement, ConnectionLineType, ConnectionLineComponent, @@ -29,7 +28,7 @@ interface ConnectionLineProps { const selector = (s: ReactFlowState) => ({ nodeInternals: s.nodeInternals, transform: s.transform }); -const getSourceHandle = (handleId: string | null, sourceNode: NodeInternalsItem, connectionHandleType: HandleType) => { +const getSourceHandle = (handleId: string | null, sourceNode: Node, connectionHandleType: HandleType) => { const handleTypeInverted = connectionHandleType === 'source' ? 'target' : 'source'; const handleBound = sourceNode.handleBounds?.[connectionHandleType] || sourceNode.handleBounds?.[handleTypeInverted]; @@ -51,7 +50,7 @@ export default ({ const handleId = connectionHandleId; const { nodeInternals, transform } = useStore(selector, shallow); - const sourceNode = useRef(nodeInternals.get(nodeId)); + const sourceNode = useRef(nodeInternals.get(nodeId)); if ( !sourceNode.current || @@ -65,8 +64,8 @@ export default ({ const sourceHandle = getSourceHandle(handleId, sourceNode.current, connectionHandleType); const sourceHandleX = sourceHandle ? sourceHandle.x + sourceHandle.width / 2 : (sourceNode.current?.width ?? 0) / 2; const sourceHandleY = sourceHandle ? sourceHandle.y + sourceHandle.height / 2 : sourceNode.current?.height ?? 0; - const sourceX = sourceNode.current.positionAbsolute.x + sourceHandleX; - const sourceY = sourceNode.current.positionAbsolute.y + sourceHandleY; + const sourceX = (sourceNode.current.positionAbsolute?.x || 0) + sourceHandleX; + const sourceY = (sourceNode.current.positionAbsolute?.y || 0) + sourceHandleY; const targetX = (connectionPositionX - transform[0]) / transform[2]; const targetY = (connectionPositionY - transform[1]) / transform[2]; diff --git a/src/container/EdgeRenderer/utils.ts b/src/container/EdgeRenderer/utils.ts index fd3e86f6..01292e6b 100644 --- a/src/container/EdgeRenderer/utils.ts +++ b/src/container/EdgeRenderer/utils.ts @@ -172,13 +172,13 @@ export function getNodeData(nodeInternals: NodeInternals, nodeId: string): [Rect !node.handleBounds || !node.width || !node.height || - typeof node.positionAbsolute.x === 'undefined' || - typeof node.positionAbsolute.y === 'undefined'; + typeof node.positionAbsolute?.x === 'undefined' || + typeof node.positionAbsolute?.y === 'undefined'; return [ { - x: node?.positionAbsolute.x || 0, - y: node?.positionAbsolute.y || 0, + x: node?.positionAbsolute?.x || 0, + y: node?.positionAbsolute?.y || 0, width: node?.width || 0, height: node?.height || 0, }, diff --git a/src/container/NodeRenderer/index.tsx b/src/container/NodeRenderer/index.tsx index a5dcf4b5..09ad1251 100644 --- a/src/container/NodeRenderer/index.tsx +++ b/src/container/NodeRenderer/index.tsx @@ -92,8 +92,8 @@ const NodeRenderer = (props: NodeRendererProps) => { sourcePosition={node.sourcePosition} targetPosition={node.targetPosition} hidden={node.hidden} - xPos={node.positionAbsolute.x} - yPos={node.positionAbsolute.y} + xPos={node.positionAbsolute?.x ?? 0} + yPos={node.positionAbsolute?.y ?? 0} dragging={!!node.dragging} isInitialized={!!isInitialized} snapGrid={snapGrid} @@ -115,7 +115,7 @@ const NodeRenderer = (props: NodeRendererProps) => { isConnectable={isConnectable} resizeObserver={resizeObserver} dragHandle={node.dragHandle} - zIndex={node.z} + zIndex={node.z ?? 0} isParent={!!node.isParent} noDragClassName={props.noDragClassName} noPanClassName={props.noPanClassName} diff --git a/src/hooks/useReactFlow.ts b/src/hooks/useReactFlow.ts index 65e9eb2e..a66f342b 100644 --- a/src/hooks/useReactFlow.ts +++ b/src/hooks/useReactFlow.ts @@ -1,4 +1,5 @@ import { useCallback } from 'react'; + import useViewportHelper from './useViewportHelper'; import { useStoreApi } from '../store'; import { ReactFlowInstance, Instance } from '../types'; diff --git a/src/store/utils.ts b/src/store/utils.ts index 82375189..f0138a9a 100644 --- a/src/store/utils.ts +++ b/src/store/utils.ts @@ -1,5 +1,8 @@ import { zoomIdentity } from 'd3-zoom'; import { GetState } from 'zustand'; + +import { clampPosition, isNumeric } from '../utils'; +import { getRectOfNodes, getTransformForBounds } from '../utils/graph'; import { CoordinateExtent, Edge, @@ -7,19 +10,16 @@ import { Node, NodeDimensionChange, NodeInternals, - NodeInternalsItem, NodeSelectionChange, ReactFlowState, XYPosition, XYZPosition, } from '../types'; -import { clampPosition, isNumeric } from '../utils'; -import { getRectOfNodes, getTransformForBounds } from '../utils/graph'; type ParentNodes = Record; function calculateXYZPosition( - node: NodeInternalsItem, + node: Node, nodeInternals: NodeInternals, parentNodes: ParentNodes, result: XYZPosition @@ -32,17 +32,17 @@ 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 > node.z ? parentNode.z : node.z, + z: (parentNode.z ?? 0) > (node.z ?? 0) ? parentNode.z ?? 0 : node.z ?? 0, }); } export function createNodeInternals(nodes: Node[], nodeInternals: NodeInternals): NodeInternals { - const nextNodeInternals = new Map(); + const nextNodeInternals = new Map(); const parentNodes: ParentNodes = {}; nodes.forEach((node) => { const z = isNumeric(node.zIndex) ? node.zIndex : node.dragging || node.selected ? 1000 : 0; - const internals: NodeInternalsItem = { + const internals: Node = { ...nodeInternals.get(node.id), ...node, positionAbsolute: { @@ -66,7 +66,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, + z: node.z ?? 0, }); node.positionAbsolute = { @@ -85,7 +85,7 @@ export function createNodeInternals(nodes: Node[], nodeInternals: NodeInternals) return nextNodeInternals; } -export function isParentSelected(node: NodeInternalsItem, nodeInternals: NodeInternals): boolean { +export function isParentSelected(node: Node, nodeInternals: NodeInternals): boolean { if (!node.parentNode) { return false; } @@ -104,7 +104,7 @@ export function isParentSelected(node: NodeInternalsItem, nodeInternals: NodeInt } type CreatePostiionChangeParams = { - node: NodeInternalsItem; + node: Node; nodeExtent: CoordinateExtent; nodeInternals: NodeInternals; diff?: XYPosition; diff --git a/src/types/nodes.ts b/src/types/nodes.ts index 0806dd65..84de1022 100644 --- a/src/types/nodes.ts +++ b/src/types/nodes.ts @@ -27,6 +27,12 @@ export interface Node { zIndex?: number; extent?: 'parent' | CoordinateExtent; expandParent?: boolean; + + // only used internally + positionAbsolute?: XYPosition; + z?: number; + handleBounds?: NodeHandleBounds; + isParent?: boolean; } // props that get passed to a custom node @@ -104,14 +110,7 @@ export type NodeDimensionUpdate = { forceUpdate?: boolean; }; -export type NodeInternalsItem = Node & { - positionAbsolute: XYPosition; - z: number; - handleBounds?: NodeHandleBounds; - isParent?: boolean; -}; - -export type NodeInternals = Map; +export type NodeInternals = Map; export type NodeBounds = XYPosition & { width: number | null; diff --git a/src/utils/graph.ts b/src/utils/graph.ts index 0a6f836d..83df4bbd 100644 --- a/src/utils/graph.ts +++ b/src/utils/graph.ts @@ -1,16 +1,6 @@ import { boxToRect, clamp, getBoundsOfBoxes, rectToBox } from '../utils'; -import { - Node, - Edge, - Connection, - EdgeMarkerType, - Transform, - XYPosition, - Rect, - NodeInternals, - NodeInternalsItem, -} from '../types'; +import { Node, Edge, Connection, EdgeMarkerType, Transform, XYPosition, Rect, NodeInternals } from '../types'; export const isEdge = (element: Node | Connection | Edge): element is Edge => 'id' in element && 'source' in element && 'target' in element; @@ -145,10 +135,10 @@ export const getRectOfNodes = (nodes: Node[]): Rect => { return boxToRect(box); }; -export const getRectOfNodeInternals = (nodes: NodeInternalsItem[]): Rect => { +export const getRectOfNodeInternals = (nodes: Node[]): Rect => { const box = nodes.reduce( (currBox, { positionAbsolute, width, height }) => - getBoundsOfBoxes(currBox, rectToBox({ ...positionAbsolute, width: width || 0, height: height || 0 })), + getBoundsOfBoxes(currBox, rectToBox({ ...positionAbsolute!, width: width || 0, height: height || 0 })), { x: Infinity, y: Infinity, x2: -Infinity, y2: -Infinity } ); @@ -162,7 +152,7 @@ export const getNodesInside = ( partially: boolean = false, // set excludeNonSelectableNodes if you want to pay attention to the nodes "selectable" attribute excludeNonSelectableNodes: boolean = false -): NodeInternalsItem[] => { +): Node[] => { const rBox = rectToBox({ x: (rect.x - tx) / tScale, y: (rect.y - ty) / tScale, @@ -170,7 +160,7 @@ export const getNodesInside = ( height: rect.height / tScale, }); - const visibleNodes: NodeInternalsItem[] = []; + const visibleNodes: Node[] = []; nodeInternals.forEach((node) => { const { positionAbsolute, width, height, dragging, selectable = true } = node; @@ -179,7 +169,7 @@ export const getNodesInside = ( return false; } - const nBox = rectToBox({ ...positionAbsolute, width: width || 0, height: height || 0 }); + const nBox = rectToBox({ ...positionAbsolute!, width: width || 0, height: height || 0 }); const xOverlap = Math.max(0, Math.min(rBox.x2, nBox.x2) - Math.max(rBox.x, nBox.x)); const yOverlap = Math.max(0, Math.min(rBox.y2, nBox.y2) - Math.max(rBox.y, nBox.y)); const overlappingArea = Math.ceil(xOverlap * yOverlap);