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, Connection } from '../../types'; import { onMouseDown } from '../../components/Handle/handler'; import { EdgeAnchor } from './EdgeAnchor'; import { getMarkerId } from '../../utils/graph'; const selector = (s: ReactFlowState) => ({ addSelectedEdges: s.addSelectedEdges, connectionMode: s.connectionMode, }); export default (EdgeComponent: ComponentType) => { const EdgeWrapper = ({ id, className, type, data, onClick, onEdgeDoubleClick, selected, animated, label, labelStyle, labelShowBg, labelBgStyle, labelBgPadding, labelBgBorderRadius, style, source, target, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, elementsSelectable, hidden, sourceHandleId, targetHandleId, onContextMenu, onMouseEnter, onMouseMove, onMouseLeave, edgeUpdaterRadius, onEdgeUpdate, onEdgeUpdateStart, onEdgeUpdateEnd, markerEnd, markerStart, }: WrapEdgeProps): JSX.Element | null => { const store = useStoreApi(); const { addSelectedEdges, connectionMode } = useStore(selector, shallow); const [updating, setUpdating] = useState(false); const inactive = !elementsSelectable && !onClick; const handleEdgeUpdate = typeof onEdgeUpdate !== 'undefined'; const edgeClasses = cc([ 'react-flow__edge', `react-flow__edge-${type}`, className, { selected, 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) { store.setState({ nodesSelectionActive: false }); addSelectedEdges([edgeElement.id]); } 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) || null; const handleType = isSourceHandle ? 'target' : 'source'; const isValidConnection = () => true; const isTarget = isSourceHandle; onEdgeUpdateStart?.(event, edgeElement, handleType); const _onEdgeUpdate = onEdgeUpdateEnd ? (evt: MouseEvent): void => onEdgeUpdateEnd(evt, edgeElement, handleType) : undefined; const onConnectEdge = (connection: Connection) => { const { edges } = store.getState(); const edge = edges.find((e) => e.id === id); if (edge && onEdgeUpdate) { onEdgeUpdate(edge, connection); } }; onMouseDown( event, handleId, nodeId, store.setState, onConnectEdge, isTarget, isValidConnection, connectionMode, handleType, _onEdgeUpdate, store.getState ); }, [id, source, target, type, sourceHandleId, targetHandleId, edgeElement, onEdgeUpdate] ); 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 (hidden) { return null; } return ( {handleEdgeUpdate && ( )} {handleEdgeUpdate && ( )} ); }; EdgeWrapper.displayName = 'EdgeWrapper'; return memo(EdgeWrapper); };