import { memo, useContext, HTMLAttributes, forwardRef, MouseEvent as ReactMouseEvent } from 'react';
import cc from 'classcat';
import shallow from 'zustand/shallow';
import { useStore, useStoreApi } from '../../hooks/useStore';
import NodeIdContext from '../../contexts/NodeIdContext';
import { checkElementBelowIsValid, handleMouseDown } from './handler';
import { getHostForElement } from '../../utils';
import { addEdge } from '../../utils/graph';
import { Position } from '../../types';
import type { HandleProps, Connection, ReactFlowState } from '../../types';
const alwaysValid = () => true;
export type HandleComponentProps = HandleProps & Omit, 'id'>;
const selector = (s: ReactFlowState) => ({
connectionStartHandle: s.connectionStartHandle,
connectOnClick: s.connectOnClick,
noPanClassName: s.noPanClassName,
});
const Handle = forwardRef(
(
{
type = 'source',
position = Position.Top,
isValidConnection = alwaysValid,
isConnectable = true,
id,
onConnect,
children,
className,
onMouseDown,
...rest
},
ref
) => {
const store = useStoreApi();
const nodeId = useContext(NodeIdContext) as string;
const { connectionStartHandle, connectOnClick, noPanClassName } = useStore(selector, shallow);
const handleId = id || null;
const isTarget = type === 'target';
const onConnectExtended = (params: Connection) => {
const { defaultEdgeOptions, onConnect: onConnectAction, hasDefaultEdges } = store.getState();
const edgeParams = {
...defaultEdgeOptions,
...params,
};
if (hasDefaultEdges) {
const { edges } = store.getState();
store.setState({ edges: addEdge(edgeParams, edges) });
}
onConnectAction?.(edgeParams);
onConnect?.(edgeParams);
};
const onMouseDownHandler = (event: ReactMouseEvent) => {
if (event.button === 0) {
handleMouseDown({
event,
handleId,
nodeId,
onConnect: onConnectExtended,
isTarget,
getState: store.getState,
setState: store.setState,
isValidConnection,
});
}
onMouseDown?.(event);
};
const onClick = (event: ReactMouseEvent) => {
const { onClickConnectStart, onClickConnectEnd, connectionMode } = store.getState();
if (!connectionStartHandle) {
onClickConnectStart?.(event, { nodeId, handleId, handleType: type });
store.setState({ connectionStartHandle: { nodeId, type, handleId } });
return;
}
const doc = getHostForElement(event.target as HTMLElement);
const { connection, isValid } = checkElementBelowIsValid(
event as unknown as MouseEvent,
connectionMode,
connectionStartHandle.type === 'target',
connectionStartHandle.nodeId,
connectionStartHandle.handleId || null,
isValidConnection,
doc
);
if (isValid) {
onConnectExtended(connection);
}
onClickConnectEnd?.(event as unknown as MouseEvent);
store.setState({ connectionStartHandle: null });
};
return (
{children}
);
}
);
Handle.displayName = 'Handle';
export default memo(Handle);