import React, { memo, CSSProperties, useCallback } from 'react'; import shallow from 'zustand/shallow'; import { useStore } from '../../store'; import ConnectionLine from '../../components/ConnectionLine/index'; import MarkerDefinitions from './MarkerDefinitions'; import { getEdgePositions, getHandle, getSourceTargetNodes } from './utils'; import { Position, Edge, Connection, ConnectionLineType, ConnectionLineComponent, ConnectionMode, OnEdgeUpdateFunc, ReactFlowState, NodeHandleBounds, } from '../../types'; import useVisibleEdges from '../../hooks/useVisibleEdges'; interface EdgeRendererProps { edgeTypes: any; connectionLineType: ConnectionLineType; connectionLineStyle?: CSSProperties; connectionLineComponent?: ConnectionLineComponent; onEdgeClick?: (event: React.MouseEvent, node: Edge) => void; onEdgeDoubleClick?: (event: React.MouseEvent, edge: Edge) => void; defaultMarkerColor: string; onlyRenderVisibleElements: boolean; onEdgeUpdate?: OnEdgeUpdateFunc; onEdgeContextMenu?: (event: React.MouseEvent, edge: Edge) => void; onEdgeMouseEnter?: (event: React.MouseEvent, edge: Edge) => void; onEdgeMouseMove?: (event: React.MouseEvent, edge: Edge) => void; onEdgeMouseLeave?: (event: React.MouseEvent, edge: Edge) => void; onEdgeUpdateStart?: (event: React.MouseEvent, edge: Edge) => void; onEdgeUpdateEnd?: (event: MouseEvent, edge: Edge) => void; edgeUpdaterRadius?: number; } interface EdgeWrapperProps { edge: Edge; edgeTypes: any; markerEndId?: string; onEdgeClick?: (event: React.MouseEvent, node: Edge) => void; onEdgeContextMenu?: (event: React.MouseEvent, edge: Edge) => void; onEdgeMouseEnter?: (event: React.MouseEvent, edge: Edge) => void; onEdgeMouseMove?: (event: React.MouseEvent, edge: Edge) => void; onEdgeMouseLeave?: (event: React.MouseEvent, edge: Edge) => void; edgeUpdaterRadius?: number; onEdgeDoubleClick?: (event: React.MouseEvent, edge: Edge) => void; onEdgeUpdateStart?: (event: React.MouseEvent, edge: Edge) => void; onEdgeUpdateEnd?: (event: MouseEvent, edge: Edge) => void; onEdgeUpdate?: OnEdgeUpdateFunc; elementsSelectable: boolean; connectionMode?: ConnectionMode; sourceNodeWidth?: number | null; sourceNodeHeight?: number | null; sourceNodeX?: number; sourceNodeY?: number; sourceNodeHandleBounds?: NodeHandleBounds; targetNodeWidth?: number | null; targetNodeHeight?: number | null; targetNodeX?: number; targetNodeY?: number; targetNodeHandleBounds?: NodeHandleBounds; } const Edge = memo( ({ edge, edgeTypes, markerEndId, onEdgeClick, onEdgeContextMenu, onEdgeMouseEnter, onEdgeMouseMove, onEdgeMouseLeave, edgeUpdaterRadius, onEdgeDoubleClick, onEdgeUpdateStart, onEdgeUpdateEnd, onEdgeUpdate, connectionMode, elementsSelectable, sourceNodeWidth, sourceNodeHeight, sourceNodeX, sourceNodeY, sourceNodeHandleBounds, targetNodeWidth, targetNodeHeight, targetNodeX, targetNodeY, targetNodeHandleBounds, }: EdgeWrapperProps) => { const sourceHandleId = edge.sourceHandle || null; const targetHandleId = edge.targetHandle || null; const onConnectEdge = useCallback( (connection: Connection) => { onEdgeUpdate?.(edge, connection); }, [edge, onEdgeUpdate] ); // source and target node need to be initialized if (!sourceNodeHandleBounds || !targetNodeHandleBounds) { return null; } if ( !sourceNodeWidth || !sourceNodeHeight || typeof sourceNodeX === 'undefined' || typeof sourceNodeY === 'undefined' ) { console.warn(`couldn't create edge for source id: ${edge.source}; edge id: ${edge.id}`); return null; } if ( !targetNodeWidth || !targetNodeHeight || typeof targetNodeX === 'undefined' || typeof targetNodeY === 'undefined' ) { console.warn(`couldn't create edge for target id: ${edge.target}; edge id: ${edge.id}`); return null; } const edgeType = edge.type || 'default'; const EdgeComponent = edgeTypes[edgeType] || edgeTypes.default; // when connection type is loose we can define all handles as sources const targetNodeHandles = connectionMode === ConnectionMode.Strict ? targetNodeHandleBounds.target : targetNodeHandleBounds.target || targetNodeHandleBounds.source; const sourceHandle = getHandle(sourceNodeHandleBounds.source!, sourceHandleId); const targetHandle = getHandle(targetNodeHandles!, targetHandleId); const sourcePosition = sourceHandle ? sourceHandle.position : Position.Bottom; const targetPosition = targetHandle ? targetHandle.position : Position.Top; if (!sourceHandle) { console.warn(`couldn't create edge for source handle id: ${sourceHandleId}; edge id: ${edge.id}`); return null; } if (!targetHandle) { console.warn(`couldn't create edge for target handle id: ${targetHandleId}; edge id: ${edge.id}`); return null; } const { sourceX, sourceY, targetX, targetY } = getEdgePositions( { x: sourceNodeX, y: sourceNodeY, width: sourceNodeWidth, height: sourceNodeHeight }, sourceHandle, sourcePosition, { x: targetNodeX, y: targetNodeY, width: targetNodeWidth, height: targetNodeHeight }, targetHandle, targetPosition ); return ( ); } ); const selector = (s: ReactFlowState) => ({ transform: s.transform, connectionNodeId: s.connectionNodeId, connectionHandleId: s.connectionHandleId, connectionHandleType: s.connectionHandleType, connectionPosition: s.connectionPosition, nodesConnectable: s.nodesConnectable, elementsSelectable: s.elementsSelectable, width: s.width, height: s.height, connectionMode: s.connectionMode, nodes: s.nodes, }); const EdgeRenderer = (props: EdgeRendererProps) => { const { transform, connectionNodeId, connectionHandleId, connectionHandleType, connectionPosition, nodesConnectable, elementsSelectable, width, height, connectionMode, nodes, } = useStore(selector, shallow); const edges = useVisibleEdges(props.onlyRenderVisibleElements); if (!width) { return null; } const { connectionLineType, defaultMarkerColor, connectionLineStyle, connectionLineComponent } = props; const renderConnectionLine = connectionNodeId && connectionHandleType; return ( {edges.map((edge: Edge) => { const { sourceNode, targetNode } = getSourceTargetNodes(edge, nodes); return ( ); })} {renderConnectionLine && ( )} ); }; EdgeRenderer.displayName = 'EdgeRenderer'; export default memo(EdgeRenderer);