feat(props): add connection mode closes #702
This commit is contained in:
@@ -33,6 +33,7 @@ const ProviderFlow = () => {
|
|||||||
onConnect={onConnect}
|
onConnect={onConnect}
|
||||||
onElementsRemove={onElementsRemove}
|
onElementsRemove={onElementsRemove}
|
||||||
onLoad={onLoad}
|
onLoad={onLoad}
|
||||||
|
connectionMode="loose"
|
||||||
>
|
>
|
||||||
<Controls />
|
<Controls />
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React, { memo, ComponentType, useCallback, useState } from 'react';
|
import React, { memo, ComponentType, useCallback, useState } from 'react';
|
||||||
import cc from 'classcat';
|
import cc from 'classcat';
|
||||||
|
|
||||||
import { useStoreActions } from '../../store/hooks';
|
import { useStoreActions, useStoreState } from '../../store/hooks';
|
||||||
import { Edge, EdgeProps, WrapEdgeProps } from '../../types';
|
import { Edge, EdgeProps, WrapEdgeProps } from '../../types';
|
||||||
import { onMouseDown } from '../../components/Handle/BaseHandle';
|
import { onMouseDown } from '../../components/Handle/handler';
|
||||||
|
|
||||||
export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
||||||
const EdgeWrapper = ({
|
const EdgeWrapper = ({
|
||||||
@@ -41,6 +41,8 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
|||||||
const addSelectedElements = useStoreActions((actions) => actions.addSelectedElements);
|
const addSelectedElements = useStoreActions((actions) => actions.addSelectedElements);
|
||||||
const setConnectionNodeId = useStoreActions((actions) => actions.setConnectionNodeId);
|
const setConnectionNodeId = useStoreActions((actions) => actions.setConnectionNodeId);
|
||||||
const setPosition = useStoreActions((actions) => actions.setConnectionPosition);
|
const setPosition = useStoreActions((actions) => actions.setConnectionPosition);
|
||||||
|
const connectionMode = useStoreState((state) => state.connectionMode);
|
||||||
|
|
||||||
const [updating, setUpdating] = useState<boolean>(false);
|
const [updating, setUpdating] = useState<boolean>(false);
|
||||||
|
|
||||||
const inactive = !elementsSelectable && !onClick;
|
const inactive = !elementsSelectable && !onClick;
|
||||||
@@ -85,7 +87,8 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
|||||||
setPosition,
|
setPosition,
|
||||||
onConnectEdge,
|
onConnectEdge,
|
||||||
isTarget,
|
isTarget,
|
||||||
isValidConnection
|
isValidConnection,
|
||||||
|
connectionMode
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[id, source, target, type, sourceHandleId, targetHandleId, setConnectionNodeId, setPosition]
|
[id, source, target, type, sourceHandleId, targetHandleId, setConnectionNodeId, setPosition]
|
||||||
|
|||||||
@@ -1,229 +0,0 @@
|
|||||||
import React, { memo, MouseEvent as ReactMouseEvent, CSSProperties } from 'react';
|
|
||||||
import cc from 'classcat';
|
|
||||||
|
|
||||||
import {
|
|
||||||
HandleType,
|
|
||||||
ElementId,
|
|
||||||
Position,
|
|
||||||
XYPosition,
|
|
||||||
OnConnectFunc,
|
|
||||||
OnConnectStartFunc,
|
|
||||||
OnConnectStopFunc,
|
|
||||||
OnConnectEndFunc,
|
|
||||||
Connection,
|
|
||||||
SetConnectionId,
|
|
||||||
} from '../../types';
|
|
||||||
|
|
||||||
type ValidConnectionFunc = (connection: Connection) => boolean;
|
|
||||||
export type SetSourceIdFunc = (params: SetConnectionId) => void;
|
|
||||||
|
|
||||||
interface BaseHandleProps {
|
|
||||||
type: HandleType;
|
|
||||||
nodeId: ElementId;
|
|
||||||
onConnect: OnConnectFunc;
|
|
||||||
onConnectStart?: OnConnectStartFunc;
|
|
||||||
onConnectStop?: OnConnectStopFunc;
|
|
||||||
onConnectEnd?: OnConnectEndFunc;
|
|
||||||
position: Position;
|
|
||||||
setConnectionNodeId: SetSourceIdFunc;
|
|
||||||
setPosition: (pos: XYPosition) => void;
|
|
||||||
isValidConnection: ValidConnectionFunc;
|
|
||||||
id?: ElementId | null;
|
|
||||||
className?: string;
|
|
||||||
style?: CSSProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
type Result = {
|
|
||||||
elementBelow: Element | null;
|
|
||||||
isValid: boolean;
|
|
||||||
connection: Connection;
|
|
||||||
isHoveringHandle: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function onMouseDown(
|
|
||||||
event: ReactMouseEvent,
|
|
||||||
handleId: ElementId | null,
|
|
||||||
nodeId: ElementId,
|
|
||||||
setConnectionNodeId: SetSourceIdFunc,
|
|
||||||
setPosition: (pos: XYPosition) => void,
|
|
||||||
onConnect: OnConnectFunc,
|
|
||||||
isTarget: boolean,
|
|
||||||
isValidConnection: ValidConnectionFunc,
|
|
||||||
onConnectStart?: OnConnectStartFunc,
|
|
||||||
onConnectStop?: OnConnectStopFunc,
|
|
||||||
onConnectEnd?: OnConnectEndFunc
|
|
||||||
): void {
|
|
||||||
const reactFlowNode = (event.target as Element).closest('.react-flow');
|
|
||||||
|
|
||||||
if (!reactFlowNode) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleType = isTarget ? 'target' : 'source';
|
|
||||||
const containerBounds = reactFlowNode.getBoundingClientRect();
|
|
||||||
let recentHoveredHandle: Element;
|
|
||||||
|
|
||||||
setPosition({
|
|
||||||
x: event.clientX - containerBounds.left,
|
|
||||||
y: event.clientY - containerBounds.top,
|
|
||||||
});
|
|
||||||
|
|
||||||
setConnectionNodeId({ connectionNodeId: nodeId, connectionHandleId: handleId, connectionHandleType: handleType });
|
|
||||||
|
|
||||||
if (onConnectStart) {
|
|
||||||
onConnectStart(event, { nodeId, handleId, handleType });
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetRecentHandle(): void {
|
|
||||||
if (!recentHoveredHandle) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
recentHoveredHandle.classList.remove('react-flow__handle-valid');
|
|
||||||
recentHoveredHandle.classList.remove('react-flow__handle-connecting');
|
|
||||||
}
|
|
||||||
|
|
||||||
// checks if element below mouse is a handle and returns connection in form of an object { source: 123, target: 312 }
|
|
||||||
function checkElementBelowIsValid(event: MouseEvent) {
|
|
||||||
const elementBelow = document.elementFromPoint(event.clientX, event.clientY);
|
|
||||||
|
|
||||||
const result: Result = {
|
|
||||||
elementBelow,
|
|
||||||
isValid: false,
|
|
||||||
connection: { source: null, target: null, sourceHandle: null, targetHandle: null },
|
|
||||||
isHoveringHandle: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (elementBelow && (elementBelow.classList.contains('target') || elementBelow.classList.contains('source'))) {
|
|
||||||
result.isHoveringHandle = true;
|
|
||||||
if (
|
|
||||||
(isTarget && elementBelow.classList.contains('source')) ||
|
|
||||||
(!isTarget && elementBelow.classList.contains('target'))
|
|
||||||
) {
|
|
||||||
let connection: Connection = { source: null, target: null, sourceHandle: null, targetHandle: null };
|
|
||||||
|
|
||||||
if (isTarget) {
|
|
||||||
const sourceId = elementBelow.getAttribute('data-nodeid');
|
|
||||||
const sourcehandleId = elementBelow.getAttribute('data-handleid');
|
|
||||||
connection = { source: sourceId, sourceHandle: sourcehandleId, target: nodeId, targetHandle: handleId };
|
|
||||||
} else {
|
|
||||||
const targetId = elementBelow.getAttribute('data-nodeid');
|
|
||||||
const targetHandleId = elementBelow.getAttribute('data-handleid');
|
|
||||||
connection = { source: nodeId, sourceHandle: handleId, target: targetId, targetHandle: targetHandleId };
|
|
||||||
}
|
|
||||||
|
|
||||||
const isValid = isValidConnection(connection);
|
|
||||||
|
|
||||||
result.connection = connection;
|
|
||||||
result.isValid = isValid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onMouseMove(event: MouseEvent) {
|
|
||||||
setPosition({
|
|
||||||
x: event.clientX - containerBounds.left,
|
|
||||||
y: event.clientY - containerBounds.top,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { connection, elementBelow, isValid, isHoveringHandle } = checkElementBelowIsValid(event);
|
|
||||||
|
|
||||||
if (!isHoveringHandle) {
|
|
||||||
return resetRecentHandle();
|
|
||||||
}
|
|
||||||
|
|
||||||
const isOwnHandle = connection.source === connection.target;
|
|
||||||
|
|
||||||
if (!isOwnHandle && elementBelow) {
|
|
||||||
recentHoveredHandle = elementBelow;
|
|
||||||
elementBelow.classList.add('react-flow__handle-connecting');
|
|
||||||
elementBelow.classList.toggle('react-flow__handle-valid', isValid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onMouseUp(event: MouseEvent) {
|
|
||||||
const { connection, isValid } = checkElementBelowIsValid(event);
|
|
||||||
|
|
||||||
if (onConnectStop) {
|
|
||||||
onConnectStop(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isValid && onConnect) {
|
|
||||||
onConnect(connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onConnectEnd) {
|
|
||||||
onConnectEnd(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
resetRecentHandle();
|
|
||||||
setConnectionNodeId({ connectionNodeId: null, connectionHandleId: null, connectionHandleType: null });
|
|
||||||
|
|
||||||
document.removeEventListener('mousemove', onMouseMove);
|
|
||||||
document.removeEventListener('mouseup', onMouseUp);
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('mousemove', onMouseMove);
|
|
||||||
document.addEventListener('mouseup', onMouseUp);
|
|
||||||
}
|
|
||||||
|
|
||||||
const BaseHandle = ({
|
|
||||||
type,
|
|
||||||
nodeId,
|
|
||||||
onConnect,
|
|
||||||
onConnectStart,
|
|
||||||
onConnectStop,
|
|
||||||
onConnectEnd,
|
|
||||||
position,
|
|
||||||
setConnectionNodeId,
|
|
||||||
setPosition,
|
|
||||||
className,
|
|
||||||
id = null,
|
|
||||||
isValidConnection,
|
|
||||||
...rest
|
|
||||||
}: BaseHandleProps) => {
|
|
||||||
const isTarget = type === 'target';
|
|
||||||
const handleClasses = cc([
|
|
||||||
'react-flow__handle',
|
|
||||||
`react-flow__handle-${position}`,
|
|
||||||
'nodrag',
|
|
||||||
className,
|
|
||||||
{
|
|
||||||
source: !isTarget,
|
|
||||||
target: isTarget,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleId = id || null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-handleid={handleId}
|
|
||||||
data-nodeid={nodeId}
|
|
||||||
data-handlepos={position}
|
|
||||||
className={handleClasses}
|
|
||||||
onMouseDown={(event) =>
|
|
||||||
onMouseDown(
|
|
||||||
event,
|
|
||||||
handleId,
|
|
||||||
nodeId,
|
|
||||||
setConnectionNodeId,
|
|
||||||
setPosition,
|
|
||||||
onConnect,
|
|
||||||
isTarget,
|
|
||||||
isValidConnection,
|
|
||||||
onConnectStart,
|
|
||||||
onConnectStop,
|
|
||||||
onConnectEnd
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{...rest}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
BaseHandle.displayName = 'BaseHandle';
|
|
||||||
|
|
||||||
export default memo(BaseHandle);
|
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
import { MouseEvent as ReactMouseEvent } from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ElementId,
|
||||||
|
XYPosition,
|
||||||
|
OnConnectFunc,
|
||||||
|
OnConnectStartFunc,
|
||||||
|
OnConnectStopFunc,
|
||||||
|
OnConnectEndFunc,
|
||||||
|
ConnectionMode,
|
||||||
|
SetConnectionId,
|
||||||
|
Connection,
|
||||||
|
} from '../../types';
|
||||||
|
|
||||||
|
type ValidConnectionFunc = (connection: Connection) => boolean;
|
||||||
|
export type SetSourceIdFunc = (params: SetConnectionId) => void;
|
||||||
|
|
||||||
|
type Result = {
|
||||||
|
elementBelow: Element | null;
|
||||||
|
isValid: boolean;
|
||||||
|
connection: Connection;
|
||||||
|
isHoveringHandle: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
// checks if element below mouse is a handle and returns connection in form of an object { source: 123, target: 312 }
|
||||||
|
function checkElementBelowIsValid(
|
||||||
|
event: MouseEvent,
|
||||||
|
connectionMode: ConnectionMode,
|
||||||
|
isTarget: boolean,
|
||||||
|
nodeId: ElementId,
|
||||||
|
handleId: ElementId | null,
|
||||||
|
isValidConnection: ValidConnectionFunc
|
||||||
|
) {
|
||||||
|
const elementBelow = document.elementFromPoint(event.clientX, event.clientY);
|
||||||
|
const elementBelowIsTarget = elementBelow?.classList.contains('target') || false;
|
||||||
|
const elementBelowIsSource = elementBelow?.classList.contains('source') || false;
|
||||||
|
|
||||||
|
const result: Result = {
|
||||||
|
elementBelow,
|
||||||
|
isValid: false,
|
||||||
|
connection: { source: null, target: null, sourceHandle: null, targetHandle: null },
|
||||||
|
isHoveringHandle: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (elementBelow && (elementBelowIsTarget || elementBelowIsSource)) {
|
||||||
|
result.isHoveringHandle = true;
|
||||||
|
|
||||||
|
// in strict mode we don't allow target to target or source to source connections
|
||||||
|
const isValid =
|
||||||
|
connectionMode === ConnectionMode.Strict
|
||||||
|
? (isTarget && elementBelowIsSource) || (!isTarget && elementBelowIsTarget)
|
||||||
|
: true;
|
||||||
|
|
||||||
|
if (isValid) {
|
||||||
|
const elementBelowNodeId = elementBelow.getAttribute('data-nodeid');
|
||||||
|
const elementBelowHandleId = elementBelow.getAttribute('data-handleid');
|
||||||
|
const connection: Connection = isTarget
|
||||||
|
? {
|
||||||
|
source: elementBelowNodeId,
|
||||||
|
sourceHandle: elementBelowHandleId,
|
||||||
|
target: nodeId,
|
||||||
|
targetHandle: handleId,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
source: nodeId,
|
||||||
|
sourceHandle: handleId,
|
||||||
|
target: elementBelowNodeId,
|
||||||
|
targetHandle: elementBelowHandleId,
|
||||||
|
};
|
||||||
|
|
||||||
|
result.connection = connection;
|
||||||
|
result.isValid = isValidConnection(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetRecentHandle(hoveredHandle: Element): void {
|
||||||
|
hoveredHandle?.classList.remove('react-flow__handle-valid');
|
||||||
|
hoveredHandle?.classList.remove('react-flow__handle-connecting');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function onMouseDown(
|
||||||
|
event: ReactMouseEvent,
|
||||||
|
handleId: ElementId | null,
|
||||||
|
nodeId: ElementId,
|
||||||
|
setConnectionNodeId: SetSourceIdFunc,
|
||||||
|
setPosition: (pos: XYPosition) => void,
|
||||||
|
onConnect: OnConnectFunc,
|
||||||
|
isTarget: boolean,
|
||||||
|
isValidConnection: ValidConnectionFunc,
|
||||||
|
connectionMode: ConnectionMode,
|
||||||
|
onConnectStart?: OnConnectStartFunc,
|
||||||
|
onConnectStop?: OnConnectStopFunc,
|
||||||
|
onConnectEnd?: OnConnectEndFunc
|
||||||
|
): void {
|
||||||
|
const reactFlowNode = (event.target as Element).closest('.react-flow');
|
||||||
|
|
||||||
|
if (!reactFlowNode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleType = isTarget ? 'target' : 'source';
|
||||||
|
const containerBounds = reactFlowNode.getBoundingClientRect();
|
||||||
|
let recentHoveredHandle: Element;
|
||||||
|
|
||||||
|
setPosition({
|
||||||
|
x: event.clientX - containerBounds.left,
|
||||||
|
y: event.clientY - containerBounds.top,
|
||||||
|
});
|
||||||
|
|
||||||
|
setConnectionNodeId({ connectionNodeId: nodeId, connectionHandleId: handleId, connectionHandleType: handleType });
|
||||||
|
onConnectStart?.(event, { nodeId, handleId, handleType });
|
||||||
|
|
||||||
|
function onMouseMove(event: MouseEvent) {
|
||||||
|
setPosition({
|
||||||
|
x: event.clientX - containerBounds.left,
|
||||||
|
y: event.clientY - containerBounds.top,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { connection, elementBelow, isValid, isHoveringHandle } = checkElementBelowIsValid(
|
||||||
|
event,
|
||||||
|
connectionMode,
|
||||||
|
isTarget,
|
||||||
|
nodeId,
|
||||||
|
handleId,
|
||||||
|
isValidConnection
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isHoveringHandle) {
|
||||||
|
return resetRecentHandle(recentHoveredHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isOwnHandle = connection.source === connection.target;
|
||||||
|
|
||||||
|
if (!isOwnHandle && elementBelow) {
|
||||||
|
recentHoveredHandle = elementBelow;
|
||||||
|
elementBelow.classList.add('react-flow__handle-connecting');
|
||||||
|
elementBelow.classList.toggle('react-flow__handle-valid', isValid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseUp(event: MouseEvent) {
|
||||||
|
const { connection, isValid } = checkElementBelowIsValid(
|
||||||
|
event,
|
||||||
|
connectionMode,
|
||||||
|
isTarget,
|
||||||
|
nodeId,
|
||||||
|
handleId,
|
||||||
|
isValidConnection
|
||||||
|
);
|
||||||
|
|
||||||
|
onConnectStop?.(event);
|
||||||
|
|
||||||
|
if (isValid) {
|
||||||
|
onConnect?.(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
onConnectEnd?.(event);
|
||||||
|
|
||||||
|
resetRecentHandle(recentHoveredHandle);
|
||||||
|
setConnectionNodeId({ connectionNodeId: null, connectionHandleId: null, connectionHandleType: null });
|
||||||
|
|
||||||
|
document.removeEventListener('mousemove', onMouseMove);
|
||||||
|
document.removeEventListener('mouseup', onMouseUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', onMouseMove);
|
||||||
|
document.addEventListener('mouseup', onMouseUp);
|
||||||
|
}
|
||||||
@@ -1,16 +1,18 @@
|
|||||||
import React, { memo, useContext } from 'react';
|
import React, { memo, useContext, useCallback } from 'react';
|
||||||
import cc from 'classcat';
|
import cc from 'classcat';
|
||||||
|
|
||||||
import { useStoreActions, useStoreState } from '../../store/hooks';
|
import { useStoreActions, useStoreState } from '../../store/hooks';
|
||||||
import BaseHandle from './BaseHandle';
|
|
||||||
import NodeIdContext from '../../contexts/NodeIdContext';
|
import NodeIdContext from '../../contexts/NodeIdContext';
|
||||||
|
import { HandleProps, Connection, ElementId, Position } from '../../types';
|
||||||
|
|
||||||
import { HandleProps, ElementId, Position, Connection } from '../../types';
|
import { onMouseDown } from './handler';
|
||||||
|
|
||||||
|
const alwaysValid = () => true;
|
||||||
|
|
||||||
const Handle = ({
|
const Handle = ({
|
||||||
type = 'source',
|
type = 'source',
|
||||||
position = Position.Top,
|
position = Position.Top,
|
||||||
isValidConnection = () => true,
|
isValidConnection = alwaysValid,
|
||||||
isConnectable = true,
|
isConnectable = true,
|
||||||
style,
|
style,
|
||||||
className,
|
className,
|
||||||
@@ -24,32 +26,69 @@ const Handle = ({
|
|||||||
const onConnectStart = useStoreState((state) => state.onConnectStart);
|
const onConnectStart = useStoreState((state) => state.onConnectStart);
|
||||||
const onConnectStop = useStoreState((state) => state.onConnectStop);
|
const onConnectStop = useStoreState((state) => state.onConnectStop);
|
||||||
const onConnectEnd = useStoreState((state) => state.onConnectEnd);
|
const onConnectEnd = useStoreState((state) => state.onConnectEnd);
|
||||||
|
const connectionMode = useStoreState((state) => state.connectionMode);
|
||||||
|
const handleId = id || null;
|
||||||
|
const isTarget = type === 'target';
|
||||||
|
|
||||||
const onConnectExtended = (params: Connection) => {
|
const onConnectExtended = useCallback(
|
||||||
if (onConnectAction) {
|
(params: Connection) => {
|
||||||
onConnectAction(params);
|
onConnectAction?.(params);
|
||||||
}
|
onConnect?.(params);
|
||||||
|
},
|
||||||
|
[onConnectAction, onConnect]
|
||||||
|
);
|
||||||
|
|
||||||
if (onConnect) {
|
const onMouseDownHandler = useCallback(
|
||||||
onConnect(params);
|
(event: React.MouseEvent) => {
|
||||||
}
|
onMouseDown(
|
||||||
};
|
event,
|
||||||
const handleClasses = cc([className, { connectable: isConnectable }]);
|
handleId,
|
||||||
|
nodeId,
|
||||||
|
setConnectionNodeId,
|
||||||
|
setPosition,
|
||||||
|
onConnectExtended,
|
||||||
|
isTarget,
|
||||||
|
isValidConnection,
|
||||||
|
connectionMode,
|
||||||
|
onConnectStart,
|
||||||
|
onConnectStop,
|
||||||
|
onConnectEnd
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[
|
||||||
|
handleId,
|
||||||
|
nodeId,
|
||||||
|
setConnectionNodeId,
|
||||||
|
setPosition,
|
||||||
|
onConnectExtended,
|
||||||
|
isTarget,
|
||||||
|
isValidConnection,
|
||||||
|
connectionMode,
|
||||||
|
onConnectStart,
|
||||||
|
onConnectStop,
|
||||||
|
onConnectEnd,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleClasses = cc([
|
||||||
|
'react-flow__handle',
|
||||||
|
`react-flow__handle-${position}`,
|
||||||
|
'nodrag',
|
||||||
|
className,
|
||||||
|
{
|
||||||
|
source: !isTarget,
|
||||||
|
target: isTarget,
|
||||||
|
connectable: isConnectable,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseHandle
|
<div
|
||||||
|
data-handleid={handleId}
|
||||||
|
data-nodeid={nodeId}
|
||||||
|
data-handlepos={position}
|
||||||
className={handleClasses}
|
className={handleClasses}
|
||||||
id={id}
|
onMouseDown={onMouseDownHandler}
|
||||||
nodeId={nodeId}
|
|
||||||
setPosition={setPosition}
|
|
||||||
setConnectionNodeId={setConnectionNodeId}
|
|
||||||
onConnect={onConnectExtended}
|
|
||||||
onConnectStart={onConnectStart}
|
|
||||||
onConnectStop={onConnectStop}
|
|
||||||
onConnectEnd={onConnectEnd}
|
|
||||||
type={type}
|
|
||||||
position={position}
|
|
||||||
isValidConnection={isValidConnection}
|
|
||||||
style={style}
|
style={style}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ const GraphView = ({
|
|||||||
onSelectionDrag,
|
onSelectionDrag,
|
||||||
onSelectionDragStop,
|
onSelectionDragStop,
|
||||||
onSelectionContextMenu,
|
onSelectionContextMenu,
|
||||||
|
connectionMode,
|
||||||
connectionLineType,
|
connectionLineType,
|
||||||
connectionLineStyle,
|
connectionLineStyle,
|
||||||
connectionLineComponent,
|
connectionLineComponent,
|
||||||
@@ -94,6 +95,7 @@ const GraphView = ({
|
|||||||
const setMinZoom = useStoreActions((actions) => actions.setMinZoom);
|
const setMinZoom = useStoreActions((actions) => actions.setMinZoom);
|
||||||
const setMaxZoom = useStoreActions((actions) => actions.setMaxZoom);
|
const setMaxZoom = useStoreActions((actions) => actions.setMaxZoom);
|
||||||
const setTranslateExtent = useStoreActions((actions) => actions.setTranslateExtent);
|
const setTranslateExtent = useStoreActions((actions) => actions.setTranslateExtent);
|
||||||
|
const setConnectionMode = useStoreActions((actions) => actions.setConnectionMode);
|
||||||
const currentStore = useStore();
|
const currentStore = useStore();
|
||||||
const { zoomIn, zoomOut, zoomTo, transform, fitView, initialized } = useZoomPanHelper();
|
const { zoomIn, zoomOut, zoomTo, transform, fitView, initialized } = useZoomPanHelper();
|
||||||
|
|
||||||
@@ -188,6 +190,12 @@ const GraphView = ({
|
|||||||
}
|
}
|
||||||
}, [translateExtent]);
|
}, [translateExtent]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof connectionMode !== 'undefined') {
|
||||||
|
setConnectionMode(connectionMode);
|
||||||
|
}
|
||||||
|
}, [connectionMode]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FlowRenderer
|
<FlowRenderer
|
||||||
onPaneClick={onPaneClick}
|
onPaneClick={onPaneClick}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
Node,
|
Node,
|
||||||
Edge,
|
Edge,
|
||||||
Connection,
|
Connection,
|
||||||
|
ConnectionMode,
|
||||||
ConnectionLineType,
|
ConnectionLineType,
|
||||||
ConnectionLineComponent,
|
ConnectionLineComponent,
|
||||||
FlowTransform,
|
FlowTransform,
|
||||||
@@ -74,6 +75,7 @@ export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'on
|
|||||||
onPaneContextMenu?: (event: MouseEvent) => void;
|
onPaneContextMenu?: (event: MouseEvent) => void;
|
||||||
nodeTypes?: NodeTypesType;
|
nodeTypes?: NodeTypesType;
|
||||||
edgeTypes?: EdgeTypesType;
|
edgeTypes?: EdgeTypesType;
|
||||||
|
connectionMode?: ConnectionMode;
|
||||||
connectionLineType?: ConnectionLineType;
|
connectionLineType?: ConnectionLineType;
|
||||||
connectionLineStyle?: CSSProperties;
|
connectionLineStyle?: CSSProperties;
|
||||||
connectionLineComponent?: ConnectionLineComponent;
|
connectionLineComponent?: ConnectionLineComponent;
|
||||||
@@ -129,6 +131,7 @@ const ReactFlow = ({
|
|||||||
onSelectionDrag,
|
onSelectionDrag,
|
||||||
onSelectionDragStop,
|
onSelectionDragStop,
|
||||||
onSelectionContextMenu,
|
onSelectionContextMenu,
|
||||||
|
connectionMode,
|
||||||
connectionLineType = ConnectionLineType.Bezier,
|
connectionLineType = ConnectionLineType.Bezier,
|
||||||
connectionLineStyle,
|
connectionLineStyle,
|
||||||
connectionLineComponent,
|
connectionLineComponent,
|
||||||
@@ -183,6 +186,7 @@ const ReactFlow = ({
|
|||||||
onNodeDragStop={onNodeDragStop}
|
onNodeDragStop={onNodeDragStop}
|
||||||
nodeTypes={nodeTypesParsed}
|
nodeTypes={nodeTypesParsed}
|
||||||
edgeTypes={edgeTypesParsed}
|
edgeTypes={edgeTypesParsed}
|
||||||
|
connectionMode={connectionMode}
|
||||||
connectionLineType={connectionLineType}
|
connectionLineType={connectionLineType}
|
||||||
connectionLineStyle={connectionLineStyle}
|
connectionLineStyle={connectionLineStyle}
|
||||||
connectionLineComponent={connectionLineComponent}
|
connectionLineComponent={connectionLineComponent}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
NodeDiffUpdate,
|
NodeDiffUpdate,
|
||||||
TranslateExtent,
|
TranslateExtent,
|
||||||
SnapGrid,
|
SnapGrid,
|
||||||
|
ConnectionMode,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
|
|
||||||
type NodeDimensionUpdate = {
|
type NodeDimensionUpdate = {
|
||||||
@@ -70,6 +71,7 @@ export interface StoreModel {
|
|||||||
connectionHandleId: ElementId | null;
|
connectionHandleId: ElementId | null;
|
||||||
connectionHandleType: HandleType | null;
|
connectionHandleType: HandleType | null;
|
||||||
connectionPosition: XYPosition;
|
connectionPosition: XYPosition;
|
||||||
|
connectionMode: ConnectionMode;
|
||||||
|
|
||||||
snapToGrid: boolean;
|
snapToGrid: boolean;
|
||||||
snapGrid: SnapGrid;
|
snapGrid: SnapGrid;
|
||||||
@@ -136,6 +138,8 @@ export interface StoreModel {
|
|||||||
unsetUserSelection: Action<StoreModel>;
|
unsetUserSelection: Action<StoreModel>;
|
||||||
|
|
||||||
setMultiSelectionActive: Action<StoreModel, boolean>;
|
setMultiSelectionActive: Action<StoreModel, boolean>;
|
||||||
|
|
||||||
|
setConnectionMode: Action<StoreModel, ConnectionMode>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const storeModel: StoreModel = {
|
export const storeModel: StoreModel = {
|
||||||
@@ -175,6 +179,7 @@ export const storeModel: StoreModel = {
|
|||||||
connectionHandleId: null,
|
connectionHandleId: null,
|
||||||
connectionHandleType: 'source',
|
connectionHandleType: 'source',
|
||||||
connectionPosition: { x: 0, y: 0 },
|
connectionPosition: { x: 0, y: 0 },
|
||||||
|
connectionMode: ConnectionMode.Strict,
|
||||||
|
|
||||||
snapGrid: [15, 15],
|
snapGrid: [15, 15],
|
||||||
snapToGrid: false,
|
snapToGrid: false,
|
||||||
@@ -500,6 +505,10 @@ export const storeModel: StoreModel = {
|
|||||||
setMultiSelectionActive: action((state, isActive) => {
|
setMultiSelectionActive: action((state, isActive) => {
|
||||||
state.multiSelectionActive = isActive;
|
state.multiSelectionActive = isActive;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
setConnectionMode: action((state, connectionMode) => {
|
||||||
|
state.connectionMode = connectionMode;
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
const nodeEnv: string = (typeof __ENV__ !== 'undefined' && __ENV__) as string;
|
const nodeEnv: string = (typeof __ENV__ !== 'undefined' && __ENV__) as string;
|
||||||
|
|||||||
@@ -264,6 +264,11 @@ export interface Connection {
|
|||||||
targetHandle: ElementId | null;
|
targetHandle: ElementId | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ConnectionMode {
|
||||||
|
Strict = 'strict',
|
||||||
|
Loose = 'loose',
|
||||||
|
}
|
||||||
|
|
||||||
export enum ConnectionLineType {
|
export enum ConnectionLineType {
|
||||||
Bezier = 'default',
|
Bezier = 'default',
|
||||||
Straight = 'straight',
|
Straight = 'straight',
|
||||||
|
|||||||
Reference in New Issue
Block a user