|
|
|
|
@@ -3,7 +3,14 @@
|
|
|
|
|
* The user can then drag the connection to another handle or node. When the user releases the mouse, we check if the
|
|
|
|
|
* connection is valid and if so, we call the onConnect callback.
|
|
|
|
|
*/
|
|
|
|
|
import { memo, HTMLAttributes, forwardRef, MouseEvent as ReactMouseEvent, TouchEvent as ReactTouchEvent } from 'react';
|
|
|
|
|
import {
|
|
|
|
|
memo,
|
|
|
|
|
HTMLAttributes,
|
|
|
|
|
forwardRef,
|
|
|
|
|
MouseEvent as ReactMouseEvent,
|
|
|
|
|
TouchEvent as ReactTouchEvent,
|
|
|
|
|
ForwardedRef,
|
|
|
|
|
} from 'react';
|
|
|
|
|
import cc from 'classcat';
|
|
|
|
|
import { shallow } from 'zustand/shallow';
|
|
|
|
|
import {
|
|
|
|
|
@@ -58,190 +65,186 @@ const connectingSelector =
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const HandleComponent = forwardRef<HTMLDivElement, HandleComponentProps>(
|
|
|
|
|
(
|
|
|
|
|
{
|
|
|
|
|
type = 'source',
|
|
|
|
|
position = Position.Top,
|
|
|
|
|
isValidConnection,
|
|
|
|
|
isConnectable = true,
|
|
|
|
|
isConnectableStart = true,
|
|
|
|
|
isConnectableEnd = true,
|
|
|
|
|
id,
|
|
|
|
|
onConnect,
|
|
|
|
|
children,
|
|
|
|
|
className,
|
|
|
|
|
onMouseDown,
|
|
|
|
|
onTouchStart,
|
|
|
|
|
...rest
|
|
|
|
|
},
|
|
|
|
|
ref
|
|
|
|
|
) => {
|
|
|
|
|
const handleId = id || null;
|
|
|
|
|
const isTarget = type === 'target';
|
|
|
|
|
const store = useStoreApi();
|
|
|
|
|
const nodeId = useNodeId();
|
|
|
|
|
const { connectOnClick, noPanClassName, rfId } = useStore(selector, shallow);
|
|
|
|
|
const { connectingFrom, connectingTo, clickConnecting, isPossibleEndHandle, connectionInProcess, valid } = useStore(
|
|
|
|
|
connectingSelector(nodeId, handleId, type),
|
|
|
|
|
shallow
|
|
|
|
|
);
|
|
|
|
|
function HandleComponent(
|
|
|
|
|
{
|
|
|
|
|
type = 'source',
|
|
|
|
|
position = Position.Top,
|
|
|
|
|
isValidConnection,
|
|
|
|
|
isConnectable = true,
|
|
|
|
|
isConnectableStart = true,
|
|
|
|
|
isConnectableEnd = true,
|
|
|
|
|
id,
|
|
|
|
|
onConnect,
|
|
|
|
|
children,
|
|
|
|
|
className,
|
|
|
|
|
onMouseDown,
|
|
|
|
|
onTouchStart,
|
|
|
|
|
...rest
|
|
|
|
|
}: HandleComponentProps,
|
|
|
|
|
ref: ForwardedRef<HTMLDivElement>
|
|
|
|
|
) {
|
|
|
|
|
const handleId = id || null;
|
|
|
|
|
const isTarget = type === 'target';
|
|
|
|
|
const store = useStoreApi();
|
|
|
|
|
const nodeId = useNodeId();
|
|
|
|
|
const { connectOnClick, noPanClassName, rfId } = useStore(selector, shallow);
|
|
|
|
|
const { connectingFrom, connectingTo, clickConnecting, isPossibleEndHandle, connectionInProcess, valid } = useStore(
|
|
|
|
|
connectingSelector(nodeId, handleId, type),
|
|
|
|
|
shallow
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!nodeId) {
|
|
|
|
|
store.getState().onError?.('010', errorMessages['error010']());
|
|
|
|
|
if (!nodeId) {
|
|
|
|
|
store.getState().onError?.('010', errorMessages['error010']());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const onConnectExtended = (params: Connection) => {
|
|
|
|
|
const { defaultEdgeOptions, onConnect: onConnectAction, hasDefaultEdges } = store.getState();
|
|
|
|
|
|
|
|
|
|
const edgeParams = {
|
|
|
|
|
...defaultEdgeOptions,
|
|
|
|
|
...params,
|
|
|
|
|
};
|
|
|
|
|
if (hasDefaultEdges) {
|
|
|
|
|
const { edges, setEdges } = store.getState();
|
|
|
|
|
setEdges(addEdge(edgeParams, edges));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const onConnectExtended = (params: Connection) => {
|
|
|
|
|
const { defaultEdgeOptions, onConnect: onConnectAction, hasDefaultEdges } = store.getState();
|
|
|
|
|
onConnectAction?.(edgeParams);
|
|
|
|
|
onConnect?.(edgeParams);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const edgeParams = {
|
|
|
|
|
...defaultEdgeOptions,
|
|
|
|
|
...params,
|
|
|
|
|
};
|
|
|
|
|
if (hasDefaultEdges) {
|
|
|
|
|
const { edges, setEdges } = store.getState();
|
|
|
|
|
setEdges(addEdge(edgeParams, edges));
|
|
|
|
|
}
|
|
|
|
|
const onPointerDown = (event: ReactMouseEvent<HTMLDivElement> | ReactTouchEvent<HTMLDivElement>) => {
|
|
|
|
|
if (!nodeId) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onConnectAction?.(edgeParams);
|
|
|
|
|
onConnect?.(edgeParams);
|
|
|
|
|
};
|
|
|
|
|
const isMouseTriggered = isMouseEvent(event.nativeEvent);
|
|
|
|
|
|
|
|
|
|
const onPointerDown = (event: ReactMouseEvent<HTMLDivElement> | ReactTouchEvent<HTMLDivElement>) => {
|
|
|
|
|
if (!nodeId) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (
|
|
|
|
|
isConnectableStart &&
|
|
|
|
|
((isMouseTriggered && (event as ReactMouseEvent<HTMLDivElement>).button === 0) || !isMouseTriggered)
|
|
|
|
|
) {
|
|
|
|
|
const currentStore = store.getState();
|
|
|
|
|
|
|
|
|
|
const isMouseTriggered = isMouseEvent(event.nativeEvent);
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
isConnectableStart &&
|
|
|
|
|
((isMouseTriggered && (event as ReactMouseEvent<HTMLDivElement>).button === 0) || !isMouseTriggered)
|
|
|
|
|
) {
|
|
|
|
|
const currentStore = store.getState();
|
|
|
|
|
|
|
|
|
|
XYHandle.onPointerDown(event.nativeEvent, {
|
|
|
|
|
autoPanOnConnect: currentStore.autoPanOnConnect,
|
|
|
|
|
connectionMode: currentStore.connectionMode,
|
|
|
|
|
connectionRadius: currentStore.connectionRadius,
|
|
|
|
|
domNode: currentStore.domNode,
|
|
|
|
|
nodes: currentStore.nodes,
|
|
|
|
|
lib: currentStore.lib,
|
|
|
|
|
isTarget,
|
|
|
|
|
handleId,
|
|
|
|
|
nodeId,
|
|
|
|
|
flowId: currentStore.rfId,
|
|
|
|
|
panBy: currentStore.panBy,
|
|
|
|
|
cancelConnection: currentStore.cancelConnection,
|
|
|
|
|
onConnectStart: currentStore.onConnectStart,
|
|
|
|
|
onConnectEnd: currentStore.onConnectEnd,
|
|
|
|
|
updateConnection: currentStore.updateConnection,
|
|
|
|
|
onConnect: onConnectExtended,
|
|
|
|
|
isValidConnection: isValidConnection || currentStore.isValidConnection,
|
|
|
|
|
getTransform: () => store.getState().transform,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isMouseTriggered) {
|
|
|
|
|
onMouseDown?.(event as ReactMouseEvent<HTMLDivElement>);
|
|
|
|
|
} else {
|
|
|
|
|
onTouchStart?.(event as ReactTouchEvent<HTMLDivElement>);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const onClick = (event: ReactMouseEvent) => {
|
|
|
|
|
const {
|
|
|
|
|
onClickConnectStart,
|
|
|
|
|
onClickConnectEnd,
|
|
|
|
|
connectionClickStartHandle,
|
|
|
|
|
connectionMode,
|
|
|
|
|
isValidConnection: isValidConnectionStore,
|
|
|
|
|
lib,
|
|
|
|
|
rfId: flowId,
|
|
|
|
|
} = store.getState();
|
|
|
|
|
|
|
|
|
|
if (!nodeId || (!connectionClickStartHandle && !isConnectableStart)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!connectionClickStartHandle) {
|
|
|
|
|
onClickConnectStart?.(event.nativeEvent, { nodeId, handleId, handleType: type });
|
|
|
|
|
store.setState({ connectionClickStartHandle: { nodeId, type, handleId } });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const doc = getHostForElement(event.target as HTMLElement);
|
|
|
|
|
const isValidConnectionHandler = isValidConnection || isValidConnectionStore;
|
|
|
|
|
const { connection, isValid } = XYHandle.isValid(event.nativeEvent, {
|
|
|
|
|
handle: {
|
|
|
|
|
nodeId,
|
|
|
|
|
id: handleId,
|
|
|
|
|
type,
|
|
|
|
|
},
|
|
|
|
|
connectionMode,
|
|
|
|
|
fromNodeId: connectionClickStartHandle.nodeId,
|
|
|
|
|
fromHandleId: connectionClickStartHandle.handleId || null,
|
|
|
|
|
fromType: connectionClickStartHandle.type,
|
|
|
|
|
isValidConnection: isValidConnectionHandler,
|
|
|
|
|
flowId,
|
|
|
|
|
doc,
|
|
|
|
|
lib,
|
|
|
|
|
XYHandle.onPointerDown(event.nativeEvent, {
|
|
|
|
|
autoPanOnConnect: currentStore.autoPanOnConnect,
|
|
|
|
|
connectionMode: currentStore.connectionMode,
|
|
|
|
|
connectionRadius: currentStore.connectionRadius,
|
|
|
|
|
domNode: currentStore.domNode,
|
|
|
|
|
nodes: currentStore.nodes,
|
|
|
|
|
lib: currentStore.lib,
|
|
|
|
|
isTarget,
|
|
|
|
|
handleId,
|
|
|
|
|
nodeId,
|
|
|
|
|
flowId: currentStore.rfId,
|
|
|
|
|
panBy: currentStore.panBy,
|
|
|
|
|
cancelConnection: currentStore.cancelConnection,
|
|
|
|
|
onConnectStart: currentStore.onConnectStart,
|
|
|
|
|
onConnectEnd: currentStore.onConnectEnd,
|
|
|
|
|
updateConnection: currentStore.updateConnection,
|
|
|
|
|
onConnect: onConnectExtended,
|
|
|
|
|
isValidConnection: isValidConnection || currentStore.isValidConnection,
|
|
|
|
|
getTransform: () => store.getState().transform,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isValid && connection) {
|
|
|
|
|
onConnectExtended(connection);
|
|
|
|
|
}
|
|
|
|
|
if (isMouseTriggered) {
|
|
|
|
|
onMouseDown?.(event as ReactMouseEvent<HTMLDivElement>);
|
|
|
|
|
} else {
|
|
|
|
|
onTouchStart?.(event as ReactTouchEvent<HTMLDivElement>);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
onClickConnectEnd?.(event as unknown as MouseEvent);
|
|
|
|
|
const onClick = (event: ReactMouseEvent) => {
|
|
|
|
|
const {
|
|
|
|
|
onClickConnectStart,
|
|
|
|
|
onClickConnectEnd,
|
|
|
|
|
connectionClickStartHandle,
|
|
|
|
|
connectionMode,
|
|
|
|
|
isValidConnection: isValidConnectionStore,
|
|
|
|
|
lib,
|
|
|
|
|
rfId: flowId,
|
|
|
|
|
} = store.getState();
|
|
|
|
|
|
|
|
|
|
store.setState({ connectionClickStartHandle: null });
|
|
|
|
|
};
|
|
|
|
|
if (!nodeId || (!connectionClickStartHandle && !isConnectableStart)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
data-handleid={handleId}
|
|
|
|
|
data-nodeid={nodeId}
|
|
|
|
|
data-handlepos={position}
|
|
|
|
|
data-id={`${rfId}-${nodeId}-${handleId}-${type}`}
|
|
|
|
|
className={cc([
|
|
|
|
|
'react-flow__handle',
|
|
|
|
|
`react-flow__handle-${position}`,
|
|
|
|
|
'nodrag',
|
|
|
|
|
noPanClassName,
|
|
|
|
|
className,
|
|
|
|
|
{
|
|
|
|
|
source: !isTarget,
|
|
|
|
|
target: isTarget,
|
|
|
|
|
connectable: isConnectable,
|
|
|
|
|
connectablestart: isConnectableStart,
|
|
|
|
|
connectableend: isConnectableEnd,
|
|
|
|
|
clickconnecting: clickConnecting,
|
|
|
|
|
connectingfrom: connectingFrom,
|
|
|
|
|
connectingto: connectingTo,
|
|
|
|
|
valid,
|
|
|
|
|
// shows where you can start a connection from
|
|
|
|
|
// and where you can end it while connecting
|
|
|
|
|
connectionindicator:
|
|
|
|
|
isConnectable &&
|
|
|
|
|
(!connectionInProcess || isPossibleEndHandle) &&
|
|
|
|
|
(connectionInProcess ? isConnectableEnd : isConnectableStart),
|
|
|
|
|
},
|
|
|
|
|
])}
|
|
|
|
|
onMouseDown={onPointerDown}
|
|
|
|
|
onTouchStart={onPointerDown}
|
|
|
|
|
onClick={connectOnClick ? onClick : undefined}
|
|
|
|
|
ref={ref}
|
|
|
|
|
{...rest}
|
|
|
|
|
>
|
|
|
|
|
{children}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
if (!connectionClickStartHandle) {
|
|
|
|
|
onClickConnectStart?.(event.nativeEvent, { nodeId, handleId, handleType: type });
|
|
|
|
|
store.setState({ connectionClickStartHandle: { nodeId, type, handleId } });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HandleComponent.displayName = 'Handle';
|
|
|
|
|
const doc = getHostForElement(event.target as HTMLElement);
|
|
|
|
|
const isValidConnectionHandler = isValidConnection || isValidConnectionStore;
|
|
|
|
|
const { connection, isValid } = XYHandle.isValid(event.nativeEvent, {
|
|
|
|
|
handle: {
|
|
|
|
|
nodeId,
|
|
|
|
|
id: handleId,
|
|
|
|
|
type,
|
|
|
|
|
},
|
|
|
|
|
connectionMode,
|
|
|
|
|
fromNodeId: connectionClickStartHandle.nodeId,
|
|
|
|
|
fromHandleId: connectionClickStartHandle.handleId || null,
|
|
|
|
|
fromType: connectionClickStartHandle.type,
|
|
|
|
|
isValidConnection: isValidConnectionHandler,
|
|
|
|
|
flowId,
|
|
|
|
|
doc,
|
|
|
|
|
lib,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (isValid && connection) {
|
|
|
|
|
onConnectExtended(connection);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onClickConnectEnd?.(event as unknown as MouseEvent);
|
|
|
|
|
|
|
|
|
|
store.setState({ connectionClickStartHandle: null });
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
data-handleid={handleId}
|
|
|
|
|
data-nodeid={nodeId}
|
|
|
|
|
data-handlepos={position}
|
|
|
|
|
data-id={`${rfId}-${nodeId}-${handleId}-${type}`}
|
|
|
|
|
className={cc([
|
|
|
|
|
'react-flow__handle',
|
|
|
|
|
`react-flow__handle-${position}`,
|
|
|
|
|
'nodrag',
|
|
|
|
|
noPanClassName,
|
|
|
|
|
className,
|
|
|
|
|
{
|
|
|
|
|
source: !isTarget,
|
|
|
|
|
target: isTarget,
|
|
|
|
|
connectable: isConnectable,
|
|
|
|
|
connectablestart: isConnectableStart,
|
|
|
|
|
connectableend: isConnectableEnd,
|
|
|
|
|
clickconnecting: clickConnecting,
|
|
|
|
|
connectingfrom: connectingFrom,
|
|
|
|
|
connectingto: connectingTo,
|
|
|
|
|
valid,
|
|
|
|
|
// shows where you can start a connection from
|
|
|
|
|
// and where you can end it while connecting
|
|
|
|
|
connectionindicator:
|
|
|
|
|
isConnectable &&
|
|
|
|
|
(!connectionInProcess || isPossibleEndHandle) &&
|
|
|
|
|
(connectionInProcess ? isConnectableEnd : isConnectableStart),
|
|
|
|
|
},
|
|
|
|
|
])}
|
|
|
|
|
onMouseDown={onPointerDown}
|
|
|
|
|
onTouchStart={onPointerDown}
|
|
|
|
|
onClick={connectOnClick ? onClick : undefined}
|
|
|
|
|
ref={ref}
|
|
|
|
|
{...rest}
|
|
|
|
|
>
|
|
|
|
|
{children}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The Handle component is the part of a node that can be used to connect nodes.
|
|
|
|
|
*/
|
|
|
|
|
export const Handle = memo(HandleComponent);
|
|
|
|
|
export const Handle = memo(forwardRef(HandleComponent));
|
|
|
|
|
|