185 lines
4.7 KiB
TypeScript
185 lines
4.7 KiB
TypeScript
import React, { memo, useContext, useCallback, HTMLAttributes, forwardRef } from 'react';
|
|
import cc from 'classcat';
|
|
import shallow from 'zustand/shallow';
|
|
|
|
import { useStore, useStoreApi } from '../../store';
|
|
import NodeIdContext from '../../contexts/NodeIdContext';
|
|
import { HandleProps, Connection, ReactFlowState, Position } from '../../types';
|
|
import { checkElementBelowIsValid, onMouseDown } from './handler';
|
|
import { getHostForElement } from '../../utils';
|
|
import { addEdge } from '../../utils/graph';
|
|
|
|
const alwaysValid = () => true;
|
|
|
|
export type HandleComponentProps = HandleProps & Omit<HTMLAttributes<HTMLDivElement>, 'id'>;
|
|
|
|
const selector = (s: ReactFlowState) => ({
|
|
onConnectAction: s.onConnect,
|
|
onConnectStart: s.onConnectStart,
|
|
onConnectStop: s.onConnectStop,
|
|
onConnectEnd: s.onConnectEnd,
|
|
connectionMode: s.connectionMode,
|
|
connectionStartHandle: s.connectionStartHandle,
|
|
connectOnClick: s.connectOnClick,
|
|
isControlled: s.isControlled,
|
|
});
|
|
|
|
const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
|
|
(
|
|
{
|
|
type = 'source',
|
|
position = Position.Top,
|
|
isValidConnection = alwaysValid,
|
|
isConnectable = true,
|
|
id,
|
|
onConnect,
|
|
children,
|
|
className,
|
|
...rest
|
|
},
|
|
ref
|
|
) => {
|
|
const store = useStoreApi();
|
|
const nodeId = useContext(NodeIdContext) as string;
|
|
const {
|
|
onConnectAction,
|
|
onConnectStart,
|
|
onConnectStop,
|
|
onConnectEnd,
|
|
connectionMode,
|
|
connectionStartHandle,
|
|
connectOnClick,
|
|
isControlled,
|
|
} = useStore(selector, shallow);
|
|
|
|
const handleId = id || null;
|
|
const isTarget = type === 'target';
|
|
|
|
const onConnectExtended = useCallback(
|
|
(params: Connection) => {
|
|
const { defaultEdgeOptions } = store.getState();
|
|
|
|
const edgeParams = {
|
|
...defaultEdgeOptions,
|
|
...params,
|
|
};
|
|
if (isControlled) {
|
|
const { edges } = store.getState();
|
|
store.setState({ edges: addEdge(edgeParams, edges) });
|
|
} else {
|
|
onConnectAction?.(edgeParams);
|
|
}
|
|
onConnect?.(edgeParams);
|
|
},
|
|
[isControlled, onConnectAction, onConnect]
|
|
);
|
|
|
|
const onMouseDownHandler = useCallback(
|
|
(event: React.MouseEvent) => {
|
|
onMouseDown(
|
|
event,
|
|
handleId,
|
|
nodeId,
|
|
store.setState,
|
|
onConnectExtended,
|
|
isTarget,
|
|
isValidConnection,
|
|
connectionMode,
|
|
undefined,
|
|
undefined,
|
|
onConnectStart,
|
|
onConnectStop,
|
|
onConnectEnd
|
|
);
|
|
},
|
|
[
|
|
handleId,
|
|
nodeId,
|
|
onConnectExtended,
|
|
isTarget,
|
|
isValidConnection,
|
|
connectionMode,
|
|
onConnectStart,
|
|
onConnectStop,
|
|
onConnectEnd,
|
|
]
|
|
);
|
|
|
|
const onClick = useCallback(
|
|
(event: React.MouseEvent) => {
|
|
if (!connectionStartHandle) {
|
|
onConnectStart?.(event, { nodeId, handleId, handleType: type });
|
|
store.setState({ connectionStartHandle: { nodeId, type, handleId } });
|
|
} else {
|
|
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
|
|
);
|
|
|
|
onConnectStop?.(event as unknown as MouseEvent);
|
|
|
|
if (isValid) {
|
|
onConnectExtended(connection);
|
|
}
|
|
|
|
onConnectEnd?.(event as unknown as MouseEvent);
|
|
|
|
store.setState({ connectionStartHandle: null });
|
|
}
|
|
},
|
|
[
|
|
connectionStartHandle,
|
|
onConnectStart,
|
|
onConnectExtended,
|
|
onConnectStop,
|
|
onConnectEnd,
|
|
isTarget,
|
|
nodeId,
|
|
handleId,
|
|
type,
|
|
]
|
|
);
|
|
|
|
const handleClasses = cc([
|
|
'react-flow__handle',
|
|
`react-flow__handle-${position}`,
|
|
'nodrag',
|
|
className,
|
|
{
|
|
source: !isTarget,
|
|
target: isTarget,
|
|
connectable: isConnectable,
|
|
connecting:
|
|
connectionStartHandle?.nodeId === nodeId &&
|
|
connectionStartHandle?.handleId === handleId &&
|
|
connectionStartHandle?.type === type,
|
|
},
|
|
]);
|
|
|
|
return (
|
|
<div
|
|
data-handleid={handleId}
|
|
data-nodeid={nodeId}
|
|
data-handlepos={position}
|
|
className={handleClasses}
|
|
onMouseDown={onMouseDownHandler}
|
|
onClick={connectOnClick ? onClick : undefined}
|
|
ref={ref}
|
|
{...rest}
|
|
>
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|
|
);
|
|
|
|
Handle.displayName = 'Handle';
|
|
|
|
export default memo(Handle);
|