diff --git a/example/src/TouchDevice/index.tsx b/example/src/TouchDevice/index.tsx
new file mode 100644
index 00000000..3a3b11fd
--- /dev/null
+++ b/example/src/TouchDevice/index.tsx
@@ -0,0 +1,49 @@
+import { useCallback } from 'react';
+import ReactFlow, {
+ Node,
+ Edge,
+ useNodesState,
+ useEdgesState,
+ Position,
+ Connection,
+ addEdge,
+} from 'react-flow-renderer';
+
+import './touch-device.css';
+
+const initialNodes: Node[] = [
+ {
+ id: '1',
+ data: { label: 'Node 1' },
+ position: { x: 100, y: 100 },
+ sourcePosition: Position.Right,
+ targetPosition: Position.Left,
+ },
+ {
+ id: '2',
+ data: { label: 'Node 2' },
+ position: { x: 300, y: 100 },
+ sourcePosition: Position.Right,
+ targetPosition: Position.Left,
+ },
+];
+
+const initialEdges: Edge[] = [];
+
+const TouchDeviceFlow = () => {
+ const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
+ const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
+ const onConnect = useCallback((connection: Connection) => setEdges((eds) => addEdge(connection, eds)), []);
+
+ return (
+
+ );
+};
+
+export default TouchDeviceFlow;
diff --git a/example/src/TouchDevice/touch-device.css b/example/src/TouchDevice/touch-device.css
new file mode 100644
index 00000000..c1f8d4bf
--- /dev/null
+++ b/example/src/TouchDevice/touch-device.css
@@ -0,0 +1,19 @@
+.react-flow .react-flow__handle {
+ width: 20px;
+ height: 20px;
+ border-radius: 3px;
+ background-color: #9f7aea;
+}
+
+.react-flow__handle.connecting {
+ animation: bounce 1600ms infinite ease-out;
+}
+
+@keyframes bounce {
+ 0% {
+ transform: translate(0, -50%) scale(1);
+ }
+ 50% {
+ transform: translate(0, -50%) scale(1.1);
+ }
+}
diff --git a/example/src/index.tsx b/example/src/index.tsx
index a4e81a87..ef35a97e 100644
--- a/example/src/index.tsx
+++ b/example/src/index.tsx
@@ -11,6 +11,7 @@ import Layouting from './Layouting';
import NestedNodes from './NestedNodes';
import Hidden from './Hidden';
import UpdatableEdge from './UpdatableEdge';
+import TouchDevice from './TouchDevice';
import './index.css';
@@ -51,6 +52,10 @@ const routes = [
path: '/updatable-edge',
component: UpdatableEdge,
},
+ {
+ path: '/touch-device',
+ component: TouchDevice,
+ },
];
const Header = withRouter(({ history, location }) => {
diff --git a/src/components/Handle/handler.ts b/src/components/Handle/handler.ts
index 8e7e31ae..c695c557 100644
--- a/src/components/Handle/handler.ts
+++ b/src/components/Handle/handler.ts
@@ -23,7 +23,7 @@ type Result = {
};
// checks if element below mouse is a handle and returns connection in form of an object { source: 123, target: 312 }
-function checkElementBelowIsValid(
+export function checkElementBelowIsValid(
event: MouseEvent,
connectionMode: ConnectionMode,
isTarget: boolean,
diff --git a/src/components/Handle/index.tsx b/src/components/Handle/index.tsx
index 4d1e14f3..fee87e81 100644
--- a/src/components/Handle/index.tsx
+++ b/src/components/Handle/index.tsx
@@ -5,7 +5,8 @@ import shallow from 'zustand/shallow';
import { useStore, useStoreApi } from '../../store';
import NodeIdContext from '../../contexts/NodeIdContext';
import { HandleProps, Connection, ReactFlowState, Position } from '../../types';
-import { onMouseDown } from './handler';
+import { checkElementBelowIsValid, onMouseDown } from './handler';
+import { getHostForElement } from '../../utils';
const alwaysValid = () => true;
@@ -17,6 +18,7 @@ const selector = (s: ReactFlowState) => ({
onConnectStop: s.onConnectStop,
onConnectEnd: s.onConnectEnd,
connectionMode: s.connectionMode,
+ connectionStartHandle: s.connectionStartHandle,
});
const Handle = forwardRef(
@@ -36,10 +38,8 @@ const Handle = forwardRef(
) => {
const store = useStoreApi();
const nodeId = useContext(NodeIdContext) as string;
- const { onConnectAction, onConnectStart, onConnectStop, onConnectEnd, connectionMode } = useStore(
- selector,
- shallow
- );
+ const { onConnectAction, onConnectStart, onConnectStop, onConnectEnd, connectionMode, connectionStartHandle } =
+ useStore(selector, shallow);
const handleId = id || null;
const isTarget = type === 'target';
@@ -83,6 +83,47 @@ const Handle = forwardRef(
]
);
+ 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}`,
@@ -92,6 +133,10 @@ const Handle = forwardRef(
source: !isTarget,
target: isTarget,
connectable: isConnectable,
+ connecting:
+ connectionStartHandle?.nodeId === nodeId &&
+ connectionStartHandle?.handleId === handleId &&
+ connectionStartHandle?.type === type,
},
]);
@@ -102,6 +147,7 @@ const Handle = forwardRef(
data-handlepos={position}
className={handleClasses}
onMouseDown={onMouseDownHandler}
+ onClick={onClick}
ref={ref}
{...rest}
>
diff --git a/src/store/initialState.ts b/src/store/initialState.ts
index 98ea8c50..41366ca3 100644
--- a/src/store/initialState.ts
+++ b/src/store/initialState.ts
@@ -42,6 +42,8 @@ const initialState: ReactFlowStore = {
fitViewOnInit: false,
fitViewOnInitDone: false,
+
+ connectionStartHandle: null,
};
export default initialState;
diff --git a/src/types/general.ts b/src/types/general.ts
index 1f67dbc9..0c6a5a7f 100644
--- a/src/types/general.ts
+++ b/src/types/general.ts
@@ -5,7 +5,7 @@ import { XYPosition, Rect, Transform, CoordinateExtent } from './utils';
import { NodeChange, EdgeChange } from './changes';
import { Node, NodeInternals, NodeDimensionUpdate, NodeDiffUpdate } from './nodes';
import { Edge } from './edges';
-import { HandleType } from './handles';
+import { HandleType, StartHandle } from './handles';
export type FlowElement = Node | Edge;
@@ -178,6 +178,8 @@ export type ReactFlowStore = {
fitViewOnInit: boolean;
fitViewOnInitDone: boolean;
+ connectionStartHandle: StartHandle | null;
+
onConnect?: OnConnect;
onConnectStart?: OnConnectStart;
onConnectStop?: OnConnectStop;
diff --git a/src/types/handles.ts b/src/types/handles.ts
index 9114b360..37d1ea41 100644
--- a/src/types/handles.ts
+++ b/src/types/handles.ts
@@ -8,6 +8,12 @@ export interface HandleElement extends XYPosition, Dimensions {
position: Position;
}
+export interface StartHandle {
+ nodeId: string;
+ type: HandleType;
+ handleId?: string | null;
+}
+
export interface HandleProps {
type: HandleType;
position: Position;