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 3f15d9a8..cd470fbe 100644
--- a/example/src/index.js
+++ b/example/src/index.js
@@ -15,6 +15,7 @@ import Hidden from './Hidden';
import EdgeTypes from './EdgeTypes';
import CustomConnectionLine from './CustomConnectionLine';
import NodeTypeChange from './NodeTypeChange';
+import DraggableEdge from './DraggableEdge';
import './index.css';
@@ -78,6 +79,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 d1e881b2..a6611c15 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 addSelectedElements = useStoreActions((actions) => actions.addSelectedElements);
@@ -62,12 +63,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 f6964042..fb3ee857 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,
handleId: ElementId | null,
nodeId: ElementId,
@@ -67,6 +67,7 @@ function onMouseDown(
x: event.clientX - containerBounds.left,
y: event.clientY - containerBounds.top,
});
+
setConnectionNodeId({ connectionNodeId: nodeId, connectionHandleId: handleId, connectionHandleType: handleType });
if (onConnectStart) {
@@ -144,7 +145,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 f37fb718..54395458 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 {
@@ -134,7 +138,9 @@ function renderEdge(
props: EdgeRendererProps,
nodes: Node[],
selectedElements: Elements | null,
- elementsSelectable: boolean
+ elementsSelectable: boolean,
+ setConnectionNodeId: SetSourceIdFunc,
+ setPosition: (pos: XYPosition) => void,
) {
const sourceId = edge.source;
const sourceHandleId = edge.sourceHandle || null;
@@ -186,6 +192,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 (
);
}
@@ -234,6 +265,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;
@@ -248,7 +281,16 @@ const EdgeRenderer = (props: EdgeRendererProps) => {