diff --git a/README.md b/README.md
index 3b068061..53f8bb05 100644
--- a/README.md
+++ b/README.md
@@ -105,6 +105,7 @@ const BasicFlow = () => ;
- `onPaneClick(event: MouseEvent)`: called when user clicks directly on the canvas
- `onPaneContextMenu(event: MouseEvent)`: called when user does a right-click on the canvas
- `onPaneScroll(event: WheelEvent)`: called when user scrolls pane (only works when `zoomOnScroll` is set to `false)
+- `onEdgeUpdate(oldEdge: Edge, newConnection: Connection)`: called when user press at either end of the edge and point to the target node
#### Interaction
- `nodesDraggable`: default: `true`. This applies to all nodes. You can also change the behavior of a specific node with the `draggable` node option
@@ -586,6 +587,12 @@ Returns an array with elements with the added edge.
`addEdge = (edgeParams: Edge, elements: Elements): Elements`
+### updateEdge
+
+Returns an array with elements with the updated edge.
+
+`updateEdge = (oldEdge: Edge, newConnection: Connection, elements: Elements): Elements`
+
### getOutgoers
Returns all direct child nodes of the passed node.
diff --git a/example/src/DraggableEdge/index.js b/example/src/DraggableEdge/index.js
new file mode 100644
index 00000000..3c01f098
--- /dev/null
+++ b/example/src/DraggableEdge/index.js
@@ -0,0 +1,87 @@
+import React, { useState } from 'react';
+
+import ReactFlow, { MiniMap, Controls, Background, updateEdge } from 'react-flow-renderer';
+
+const onLoad = (reactFlowInstance) => {
+ console.log('flow loaded:', reactFlowInstance);
+ reactFlowInstance.fitView();
+};
+
+const initialElements = [
+ {
+ id: '1',
+ type: 'input',
+ data: {
+ label: (
+ <>
+ Node A
+ >
+ ),
+ },
+ position: { x: 250, y: 0 },
+ },
+ {
+ id: '2',
+ data: {
+ label: (
+ <>
+ Node B
+ >
+ ),
+ },
+ position: { x: 100, y: 100 },
+ },
+ {
+ id: '3',
+ data: {
+ label: (
+ <>
+ Node C
+ >
+ ),
+ },
+ position: { x: 400, y: 100 },
+ style: { background: '#D6D5E6', color: '#333', border: '1px solid #222138', width: 180 },
+ },
+ { id: 'e1-2', source: '1', target: '2', label: 'This is a draggable edge' },
+];
+
+const snapGrid = [16, 16];
+
+const DragEdge = () => {
+ const [elements, setElements] = useState(initialElements);
+ const onEdgeUpdate = (oldEdge, newConnection) => {
+ setElements((els) => updateEdge(oldEdge, newConnection, els));
+ }
+
+ return (
+
+ {
+ if (n.style?.background) return n.style.background;
+ if (n.type === 'input') return '#0041d0';
+ if (n.type === 'output') return '#ff0072';
+ if (n.type === 'default') return '#1a192b';
+
+ return '#eee';
+ }}
+ nodeColor={(n) => {
+ if (n.style?.background) return n.style.background;
+
+ return '#fff';
+ }}
+ borderRadius={2}
+ />
+
+
+
+ );
+};
+
+export default DragEdge;
diff --git a/example/src/index.js b/example/src/index.js
index 639183b4..0a4473e9 100644
--- a/example/src/index.js
+++ b/example/src/index.js
@@ -16,6 +16,7 @@ import Hidden from './Hidden';
import EdgeTypes from './EdgeTypes';
import CustomConnectionLine from './CustomConnectionLine';
import NodeTypeChange from './NodeTypeChange';
+import DraggableEdge from './DraggableEdge';
import './index.css';
@@ -84,6 +85,11 @@ const routes = [
path: '/nodetype-change',
component: NodeTypeChange,
},
+ {
+ path: '/draggable-edge',
+ component: DraggableEdge,
+ label: 'Draggable Edge',
+ },
];
const navLinks = routes.filter((route) => route.label);
diff --git a/src/components/Edges/wrapEdge.tsx b/src/components/Edges/wrapEdge.tsx
index f2bba6d4..166c158b 100644
--- a/src/components/Edges/wrapEdge.tsx
+++ b/src/components/Edges/wrapEdge.tsx
@@ -32,6 +32,7 @@ export default (EdgeComponent: ComponentType) => {
elementsSelectable,
markerEndId,
isHidden,
+ onEitherEndOfEdgePress,
}: WrapEdgeProps) => {
const setSelectedElements = useStoreActions((actions) => actions.setSelectedElements);
@@ -63,12 +64,53 @@ export default (EdgeComponent: ComponentType) => {
[elementsSelectable, id, source, target, type, data, onClick]
);
+ const handleEitherEndOfEdgePress = useCallback(
+ (event: React.MouseEvent, isEdgeHeader?: boolean): void => {
+ if (elementsSelectable) {
+ setSelectedElements({ id, source, target });
+ }
+
+ const edgeElement: Edge = { id, source, target, type };
+
+ if (typeof data !== 'undefined') {
+ edgeElement.data = data;
+ }
+
+ onEitherEndOfEdgePress(event, edgeElement, isEdgeHeader);
+ },
+ [elementsSelectable, id, source, target, type, data, onEitherEndOfEdgePress]
+ );
+
+ const handleEdgeHeaderPress = useCallback(
+ (event: React.MouseEvent): void => {
+ handleEitherEndOfEdgePress(event, true);
+ },
+ [handleEitherEndOfEdgePress],
+ );
+
+ const handleEdgeFooterPress = useCallback(
+ (event: React.MouseEvent): void => {
+ handleEitherEndOfEdgePress(event);
+ },
+ [handleEitherEndOfEdgePress],
+ );
+
if (isHidden) {
return null;
}
return (
+
+
+
) => {
targetPosition={targetPosition}
markerEndId={markerEndId}
/>
+
+
+
);
};
diff --git a/src/components/Handle/BaseHandle.tsx b/src/components/Handle/BaseHandle.tsx
index e5250cec..221e704b 100644
--- a/src/components/Handle/BaseHandle.tsx
+++ b/src/components/Handle/BaseHandle.tsx
@@ -15,7 +15,7 @@ import {
} from '../../types';
type ValidConnectionFunc = (connection: Connection) => boolean;
-type SetSourceIdFunc = (params: SetConnectionId) => void;
+export type SetSourceIdFunc = (params: SetConnectionId) => void;
interface BaseHandleProps {
type: HandleType;
@@ -40,7 +40,7 @@ type Result = {
isHoveringHandle: boolean;
};
-function onMouseDown(
+export function onMouseDown(
event: ReactMouseEvent,
nodeId: ElementId,
setConnectionNodeId: SetSourceIdFunc,
@@ -67,7 +67,7 @@ function onMouseDown(
y: event.clientY - containerBounds.top,
});
setConnectionNodeId({ connectionNodeId: nodeId, connectionHandleType: handleType });
-
+
if (onConnectStart) {
onConnectStart(event, { nodeId, handleType });
}
@@ -136,7 +136,7 @@ function onMouseDown(
function onMouseUp(event: MouseEvent) {
const { connection, isValid } = checkElementBelowIsValid(event);
-
+
if (onConnectStop) {
onConnectStop(event);
}
diff --git a/src/container/EdgeRenderer/index.tsx b/src/container/EdgeRenderer/index.tsx
index 5de86c49..73b03562 100644
--- a/src/container/EdgeRenderer/index.tsx
+++ b/src/container/EdgeRenderer/index.tsx
@@ -1,6 +1,6 @@
import React, { memo, CSSProperties } from 'react';
-import { useStoreState } from '../../store/hooks';
+import { useStoreState, useStoreActions } from '../../store/hooks';
import ConnectionLine from '../../components/ConnectionLine/index';
import { isEdge } from '../../utils/graph';
import MarkerDefinitions from './MarkerDefinitions';
@@ -14,7 +14,10 @@ import {
Elements,
ConnectionLineType,
ConnectionLineComponent,
+ Connection,
+ OnEdgeUpdateFunc
} from '../../types';
+import { onMouseDown, SetSourceIdFunc } from '../../components/Handle/BaseHandle';
interface EdgeRendererProps {
edgeTypes: any;
@@ -24,6 +27,7 @@ interface EdgeRendererProps {
arrowHeadColor: string;
markerEndId?: string;
connectionLineComponent?: ConnectionLineComponent;
+ onEdgeUpdate?: OnEdgeUpdateFunc;
}
interface EdgePositions {
@@ -130,7 +134,9 @@ function renderEdge(
props: EdgeRendererProps,
nodes: Node[],
selectedElements: Elements | null,
- elementsSelectable: boolean
+ elementsSelectable: boolean,
+ setConnectionNodeId: SetSourceIdFunc,
+ setPosition: (pos: XYPosition) => void,
) {
const [sourceId, sourceHandleId] = edge.source.split('__');
const [targetId, targetHandleId] = edge.target.split('__');
@@ -168,6 +174,30 @@ function renderEdge(
const isSelected = selectedElements ? selectedElements.some((elm) => isEdge(elm) && elm.id === edge.id) : false;
+ const onConnect = (connection: Connection) => {
+ const { onEdgeUpdate } = props;
+ if (onEdgeUpdate) {
+ onEdgeUpdate(edge, connection);
+ }
+ }
+
+ const handleEitherEndOfEdgePress = (event: React.MouseEvent, edge: Edge, isEdgeHeader = false) => {
+ const { source, target } = edge;
+ const nodeId = isEdgeHeader ? source : target;
+ const isValidConnection = () => true;
+ const isTarget = !isEdgeHeader;
+
+ onMouseDown(
+ event,
+ nodeId,
+ setConnectionNodeId,
+ setPosition,
+ onConnect,
+ isTarget,
+ isValidConnection,
+ )
+ }
+
return (
);
}
@@ -215,6 +246,8 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
const elementsSelectable = useStoreState((state) => state.elementsSelectable);
const width = useStoreState((state) => state.width);
const height = useStoreState((state) => state.height);
+ const setConnectionNodeId = useStoreActions((actions) => actions.setConnectionNodeId);
+ const setPosition = useStoreActions((actions) => actions.setConnectionPosition);
const { connectionLineType, arrowHeadColor, connectionLineStyle, connectionLineComponent } = props;
@@ -229,7 +262,16 @@ const EdgeRenderer = (props: EdgeRendererProps) => {