import React, { memo, ComponentType, useCallback, useState, useMemo } from 'react'; import cc from 'classcat'; import shallow from 'zustand/shallow'; import { useStore, useStoreApi } from '../../store'; import { Edge, EdgeProps, WrapEdgeProps, ReactFlowState } from '../../types'; import { onMouseDown } from '../../components/Handle/handler'; import { EdgeAnchor } from './EdgeAnchor'; import { getMarkerId } from '../../utils/graph'; const selector = (s: ReactFlowState) => ({ addSelectedElements: s.addSelectedElements, setConnectionNodeId: s.setConnectionNodeId, unsetNodesSelection: s.unsetNodesSelection, setPosition: s.setConnectionPosition, connectionMode: s.connectionMode, }); export default (EdgeComponent: ComponentType) => { const EdgeWrapper = ({ id, className, type, data, onClick, onEdgeDoubleClick, isSelected, animated, label, labelStyle, labelShowBg, labelBgStyle, labelBgPadding, labelBgBorderRadius, style, source, target, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, elementsSelectable, isHidden, sourceHandleId, targetHandleId, handleEdgeUpdate, onConnectEdge, onContextMenu, onMouseEnter, onMouseMove, onMouseLeave, edgeUpdaterRadius, onEdgeUpdateStart, onEdgeUpdateEnd, markerEnd, markerStart, }: WrapEdgeProps): JSX.Element | null => { const store = useStoreApi(); const { addSelectedElements, setConnectionNodeId, unsetNodesSelection, setPosition, connectionMode } = useStore( selector, shallow ); const [updating, setUpdating] = useState(false); const inactive = !elementsSelectable && !onClick; const edgeClasses = cc([ 'react-flow__edge', `react-flow__edge-${type}`, className, { selected: isSelected, animated, inactive, updating }, ]); const edgeElement = useMemo(() => { const el: Edge = { id, source, target, type, }; if (sourceHandleId) { el.sourceHandle = sourceHandleId; } if (targetHandleId) { el.targetHandle = targetHandleId; } if (typeof data !== 'undefined') { el.data = data; } return el; }, [id, source, target, type, sourceHandleId, targetHandleId, data]); const onEdgeClick = useCallback( (event: React.MouseEvent): void => { if (elementsSelectable) { unsetNodesSelection(); addSelectedElements([edgeElement]); } onClick?.(event, edgeElement); }, [elementsSelectable, edgeElement, onClick] ); const onEdgeDoubleClickHandler = useCallback( (event: React.MouseEvent) => { onEdgeDoubleClick?.(event, edgeElement); }, [edgeElement, onEdgeDoubleClick] ); const onEdgeContextMenu = useCallback( (event: React.MouseEvent): void => { onContextMenu?.(event, edgeElement); }, [edgeElement, onContextMenu] ); const onEdgeMouseEnter = useCallback( (event: React.MouseEvent): void => { onMouseEnter?.(event, edgeElement); }, [edgeElement, onContextMenu] ); const onEdgeMouseMove = useCallback( (event: React.MouseEvent): void => { onMouseMove?.(event, edgeElement); }, [edgeElement, onContextMenu] ); const onEdgeMouseLeave = useCallback( (event: React.MouseEvent): void => { onMouseLeave?.(event, edgeElement); }, [edgeElement, onContextMenu] ); const handleEdgeUpdater = useCallback( (event: React.MouseEvent, isSourceHandle: boolean) => { const nodeId = isSourceHandle ? target : source; const handleId = isSourceHandle ? targetHandleId : sourceHandleId; const isValidConnection = () => true; const isTarget = isSourceHandle; onEdgeUpdateStart?.(event, edgeElement); const _onEdgeUpdate = onEdgeUpdateEnd ? (evt: MouseEvent): void => onEdgeUpdateEnd(evt, edgeElement) : undefined; onMouseDown( event, handleId, nodeId, setConnectionNodeId, setPosition, onConnectEdge, isTarget, isValidConnection, connectionMode, isSourceHandle ? 'target' : 'source', _onEdgeUpdate, store.getState ); }, [ id, source, target, type, sourceHandleId, targetHandleId, setConnectionNodeId, setPosition, edgeElement, onConnectEdge, ] ); const onEdgeUpdaterSourceMouseDown = useCallback( (event: React.MouseEvent): void => { handleEdgeUpdater(event, true); }, [id, source, sourceHandleId, handleEdgeUpdater] ); const onEdgeUpdaterTargetMouseDown = useCallback( (event: React.MouseEvent): void => { handleEdgeUpdater(event, false); }, [id, target, targetHandleId, handleEdgeUpdater] ); const onEdgeUpdaterMouseEnter = useCallback(() => setUpdating(true), [setUpdating]); const onEdgeUpdaterMouseOut = useCallback(() => setUpdating(false), [setUpdating]); const markerStartUrl = useMemo(() => `url(#${getMarkerId(markerStart)})`, [markerStart]); const markerEndUrl = useMemo(() => `url(#${getMarkerId(markerEnd)})`, [markerEnd]); if (isHidden) { return null; } return ( {handleEdgeUpdate && ( )} {handleEdgeUpdate && ( )} ); }; EdgeWrapper.displayName = 'EdgeWrapper'; return memo(EdgeWrapper); };