import { memo, ReactNode } from 'react'; import { shallow } from 'zustand/shallow'; import cc from 'classcat'; import { errorMessages, ConnectionMode, Position } from '@reactflow/system'; import { useStore } from '../../hooks/useStore'; import useVisibleEdges from '../../hooks/useVisibleEdges'; import MarkerDefinitions from './MarkerDefinitions'; import { getEdgePositions, getNodeData } from './utils'; import { GraphViewProps } from '../GraphView'; import type { Edge, ReactFlowState } from '../../types'; import { getHandle } from '@reactflow/edge-utils'; type EdgeRendererProps = Pick< GraphViewProps, | 'edgeTypes' | 'onEdgeClick' | 'onEdgeDoubleClick' | 'defaultMarkerColor' | 'onlyRenderVisibleElements' | 'onEdgeUpdate' | 'onEdgeContextMenu' | 'onEdgeMouseEnter' | 'onEdgeMouseMove' | 'onEdgeMouseLeave' | 'onEdgeUpdateStart' | 'onEdgeUpdateEnd' | 'edgeUpdaterRadius' | 'noPanClassName' | 'elevateEdgesOnSelect' | 'rfId' | 'disableKeyboardA11y' > & { elevateEdgesOnSelect: boolean; children: ReactNode; }; const selector = (s: ReactFlowState) => ({ nodesConnectable: s.nodesConnectable, edgesFocusable: s.edgesFocusable, elementsSelectable: s.elementsSelectable, width: s.width, height: s.height, connectionMode: s.connectionMode, nodeInternals: s.nodeInternals, onError: s.onError, }); const EdgeRenderer = ({ defaultMarkerColor, onlyRenderVisibleElements, elevateEdgesOnSelect, rfId, edgeTypes, noPanClassName, onEdgeUpdate, onEdgeContextMenu, onEdgeMouseEnter, onEdgeMouseMove, onEdgeMouseLeave, onEdgeClick, edgeUpdaterRadius, onEdgeDoubleClick, onEdgeUpdateStart, onEdgeUpdateEnd, children, }: EdgeRendererProps) => { const { edgesFocusable, elementsSelectable, width, height, connectionMode, nodeInternals, onError } = useStore( selector, shallow ); const edgeTree = useVisibleEdges(onlyRenderVisibleElements, nodeInternals, elevateEdgesOnSelect); if (!width) { return null; } return ( <> {edgeTree.map(({ level, edges, isMaxLevel }) => ( {isMaxLevel && } {edges.map((edge: Edge) => { const [sourceNodeRect, sourceHandleBounds, sourceIsValid] = getNodeData(nodeInternals.get(edge.source)); const [targetNodeRect, targetHandleBounds, targetIsValid] = getNodeData(nodeInternals.get(edge.target)); if (!sourceIsValid || !targetIsValid) { return null; } let edgeType = edge.type || 'default'; if (!edgeTypes[edgeType]) { onError?.('011', errorMessages['011'](edgeType)); edgeType = 'default'; } const EdgeComponent = edgeTypes[edgeType] || edgeTypes.default; // when connection type is loose we can define all handles as sources and connect source -> source const targetNodeHandles = connectionMode === ConnectionMode.Strict ? targetHandleBounds!.target : (targetHandleBounds!.target ?? []).concat(targetHandleBounds!.source ?? []); const sourceHandle = getHandle(sourceHandleBounds!.source!, edge.sourceHandle); const targetHandle = getHandle(targetNodeHandles!, edge.targetHandle); const sourcePosition = sourceHandle?.position || Position.Bottom; const targetPosition = targetHandle?.position || Position.Top; const isFocusable = !!(edge.focusable || (edgesFocusable && typeof edge.focusable === 'undefined')); if (!sourceHandle || !targetHandle) { onError?.('008', errorMessages['008'](sourceHandle, edge)); return null; } const { sourceX, sourceY, targetX, targetY } = getEdgePositions( sourceNodeRect, sourceHandle, sourcePosition, targetNodeRect, targetHandle, targetPosition ); return ( ); })} ))} {children} ); }; EdgeRenderer.displayName = 'EdgeRenderer'; export default memo(EdgeRenderer);