diff --git a/example/src/CustomNode/ColorSelectorNode.js b/example/src/CustomNode/ColorSelectorNode.js index 1b6381f3..cd76d3b0 100644 --- a/example/src/CustomNode/ColorSelectorNode.js +++ b/example/src/CustomNode/ColorSelectorNode.js @@ -15,8 +15,8 @@ export default memo(({ data }) => { Custom Color Picker Node: {data.color} - - + + ); }); diff --git a/example/src/CustomNode/index.js b/example/src/CustomNode/index.js index 6fa29443..7f184011 100644 --- a/example/src/CustomNode/index.js +++ b/example/src/CustomNode/index.js @@ -56,8 +56,8 @@ const CustomNodeFlow = () => { { id: '4', type: 'output', data: { label: 'Output B' }, position: { x: 550, y: 100 }, targetPosition: 'left' }, { id: 'e1-2', source: '1', target: '2', animated: true, style: { stroke: '#fff' } }, - { id: 'e2a-3', source: '2__a', target: '3', animated: true, style: { stroke: '#fff' } }, - { id: 'e2b-4', source: '2__b', target: '4', animated: true, style: { stroke: '#fff' } }, + { id: 'e2a-3', source: '2', sourceHandle: '0', target: '3', animated: true, style: { stroke: '#fff' } }, + { id: 'e2b-4', source: '2', sourceHandle: '1', target: '4', animated: true, style: { stroke: '#fff' } }, ]); }, []); diff --git a/example/src/Validation/index.js b/example/src/Validation/index.js index 02d4bc57..713f3dbb 100644 --- a/example/src/Validation/index.js +++ b/example/src/Validation/index.js @@ -20,7 +20,7 @@ const onConnectEnd = (event) => console.log('on connect end', event); const CustomInput = () => ( <>
Only connectable with B
- + ); diff --git a/src/components/Handle/BaseHandle.tsx b/src/components/Handle/BaseHandle.tsx index e5250cec..97d3ebf4 100644 --- a/src/components/Handle/BaseHandle.tsx +++ b/src/components/Handle/BaseHandle.tsx @@ -42,6 +42,7 @@ type Result = { function onMouseDown( event: ReactMouseEvent, + handleId: ElementId, nodeId: ElementId, setConnectionNodeId: SetSourceIdFunc, setPosition: (pos: XYPosition) => void, @@ -88,26 +89,30 @@ function onMouseDown( const result: Result = { elementBelow, isValid: false, - connection: { source: null, target: null }, + connection: { source: null, target: null, sourceHandle: null, targetHandle: null }, isHoveringHandle: false, }; if (elementBelow && (elementBelow.classList.contains('target') || elementBelow.classList.contains('source'))) { - let connection: Connection = { source: null, target: null }; - - if (isTarget) { - const sourceId = elementBelow.getAttribute('data-nodeid'); - connection = { source: sourceId, target: nodeId }; - } else { - const targetId = elementBelow.getAttribute('data-nodeid'); - connection = { source: nodeId, target: targetId }; - } - - const isValid = isValidConnection(connection); - - result.connection = connection; - result.isValid = isValid; 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; @@ -187,17 +192,18 @@ const BaseHandle = ({ }, ]); - const nodeIdWithHandleId = id ? `${nodeId}__${id}` : nodeId; - + const handleId = id ? `${id}`: '' return (
onMouseDown( event, - nodeIdWithHandleId, + handleId, + nodeId, setConnectionNodeId, setPosition, onConnect, diff --git a/src/components/Nodes/utils.ts b/src/components/Nodes/utils.ts index 82d9322e..fea1f681 100644 --- a/src/components/Nodes/utils.ts +++ b/src/components/Nodes/utils.ts @@ -19,15 +19,8 @@ export const getHandleBounds = ( (handle): HandleElement => { const bounds = handle.getBoundingClientRect(); const dimensions = getDimensions(handle); - const nodeIdAttr = handle.getAttribute('data-nodeid'); + const handleId = handle.getAttribute('data-handleid'); const handlePosition = (handle.getAttribute('data-handlepos') as unknown) as Position; - const nodeIdSplitted = nodeIdAttr ? nodeIdAttr.split('__') : null; - - let handleId = null; - - if (nodeIdSplitted) { - handleId = (nodeIdSplitted.length ? nodeIdSplitted[1] : nodeIdSplitted) as string; - } return { id: handleId, diff --git a/src/container/EdgeRenderer/index.tsx b/src/container/EdgeRenderer/index.tsx index 5de86c49..72b1b32c 100644 --- a/src/container/EdgeRenderer/index.tsx +++ b/src/container/EdgeRenderer/index.tsx @@ -132,8 +132,10 @@ function renderEdge( selectedElements: Elements | null, elementsSelectable: boolean ) { - const [sourceId, sourceHandleId] = edge.source.split('__'); - const [targetId, targetHandleId] = edge.target.split('__'); + const sourceId = edge.source + const sourceHandleId = edge.sourceHandle! + const targetId = edge.target + const targetHandleId = edge.targetHandle! const sourceNode = nodes.find((n) => n.id === sourceId); const targetNode = nodes.find((n) => n.id === targetId); diff --git a/src/types/index.ts b/src/types/index.ts index 60b70e13..65d90984 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -60,6 +60,8 @@ export interface Edge { type?: string; source: ElementId; target: ElementId; + sourceHandle?: ElementId; + targetHandle?: ElementId; label?: string; labelStyle?: CSSProperties; labelShowBg?: boolean; @@ -243,6 +245,8 @@ export type OnLoadFunc = (params: OnLoadParams) => void; export interface Connection { source: ElementId | null; target: ElementId | null; + sourceHandle: ElementId | null; + targetHandle: ElementId | null; } export enum ConnectionLineType { diff --git a/src/utils/graph.ts b/src/utils/graph.ts index 771c8cd9..bd170c6d 100644 --- a/src/utils/graph.ts +++ b/src/utils/graph.ts @@ -40,7 +40,19 @@ export const removeElements = (elementsToRemove: Elements, elements: Elements): }); }; -const getEdgeId = ({ source, target }: Connection): ElementId => `reactflow__edge-${source}-${target}`; +const getEdgeId = ({ source, sourceHandle, target, targetHandle }: Connection): ElementId => `reactflow__edge-${source}${sourceHandle}-${target}${targetHandle}`; + +const existingConnection = (edge: Edge, elements: Elements) => { + for (const element of elements) { + if (isEdge(element)) { + if (element.source === edge.source && element.sourceHandle === edge.sourceHandle && element.target === edge.target && element.targetHandle === edge.targetHandle) { + return true + } + } + } + return false +} + export const addEdge = (edgeParams: Edge | Connection, elements: Elements): Elements => { if (!edgeParams.source || !edgeParams.target) { @@ -49,22 +61,31 @@ export const addEdge = (edgeParams: Edge | Connection, elements: Elements): Elem // make sure that there is node with the target and one with the source id [edgeParams.source, edgeParams.target].forEach((id) => { - const nodeId = id.includes('__') ? id.split('__')[0] : id; - if (!elements.find((e) => isNode(e) && e.id === nodeId)) { - throw new Error(`Can't create edge. Node with id=${nodeId} does not exist.`); + if (!elements.find((e) => isNode(e) && e.id === id)) { + throw new Error(`Can't create edge. Node with id=${id} does not exist.`); } }); + // make sure that the handles exists in each node + const handleElements = Array.from(document.getElementsByClassName("react-flow__handle")) as HTMLDivElement[] + [[edgeParams.source, edgeParams.sourceHandle], [edgeParams.target, edgeParams.targetHandle]].forEach(([nodeId, handleId]) => { + if (!handleElements.find((he) => he.getAttribute('data-nodeid') === nodeId && he.getAttribute('data-handleid') === handleId)) { + throw new Error(`Can't create edge. Handle with id=${handleId} does not exist within Node with id=${nodeId}.`); + } + }) + + let edge: Edge if (isEdge(edgeParams)) { - return elements.concat({ ...edgeParams }); + edge = {...edgeParams} + } else { + edge = { + ...edgeParams, + id: getEdgeId(edgeParams), + } as Edge; } - const edge = { - ...edgeParams, - id: getEdgeId(edgeParams), - } as Edge; - - return elements.concat(edge); + if (existingConnection(edge, elements)) {return elements} + return elements.concat(edge) }; export const pointToRendererPoint = ( @@ -112,6 +133,8 @@ export const parseElement = (element: Node | Edge): Node | Edge => { ...element, source: element.source.toString(), target: element.target.toString(), + sourceHandle: element.sourceHandle ? element.sourceHandle.toString() : '', + targetHandle: element.targetHandle ? element.targetHandle.toString() : '', id: element.id.toString(), type: element.type || 'default', };