diff --git a/examples/advanced.html b/examples/advanced.html index 2f5e161a..0f8a7662 100644 --- a/examples/advanced.html +++ b/examples/advanced.html @@ -19,6 +19,12 @@ left: 10px; z-index: 4; } + .react-flow__handle.connecting { + background: orange; + } + .react-flow__handle.valid { + background: green; + } diff --git a/examples/advanced/scripts/InputNode.js b/examples/advanced/scripts/InputNode.js index e3f76d58..bdf5622b 100644 --- a/examples/advanced/scripts/InputNode.js +++ b/examples/advanced/scripts/InputNode.js @@ -9,6 +9,11 @@ export default({ data, styles }) => (
{data.input}
data.onChange(e.target.value, data)} /> - + +connection.target % 2 === 0} + /> ); diff --git a/examples/advanced/scripts/SpecialNode.js b/examples/advanced/scripts/SpecialNode.js index 52e17b4d..2a738eae 100644 --- a/examples/advanced/scripts/SpecialNode.js +++ b/examples/advanced/scripts/SpecialNode.js @@ -26,7 +26,11 @@ export default ({ data, styles }) => { - + ); }; diff --git a/src/components/Handle/BaseHandle.js b/src/components/Handle/BaseHandle.js index 27415be5..4eaf4494 100644 --- a/src/components/Handle/BaseHandle.js +++ b/src/components/Handle/BaseHandle.js @@ -1,8 +1,9 @@ import React, { memo } from 'react'; import cx from 'classnames'; -function onMouseDown(evt, { nodeId, setSourceId, setPosition, onConnect, isTarget }) { +function onMouseDown(evt, { nodeId, setSourceId, setPosition, onConnect, isTarget, isValidConnection }) { const containerBounds = document.querySelector('.react-flow').getBoundingClientRect(); + let recentHoveredHandle = null; setPosition({ x: evt.clientX - containerBounds.x, @@ -10,24 +11,71 @@ function onMouseDown(evt, { nodeId, setSourceId, setPosition, onConnect, isTarg }); setSourceId(nodeId); + // checks if element below mouse is a handle and returns connection in form of an object { source: 123, target: 312 } + function checkElementBelowIsValid(evt) { + const elementBelow = document.elementFromPoint(evt.clientX, evt.clientY); + const result = { + elementBelow, + isValid: false, + connection: null, + isHoveringHandle: false + }; + + if (elementBelow && (elementBelow.classList.contains('target') || elementBelow.classList.contains('source'))) { + let connection = 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; + } + + return result; + } + function onMouseMove(evt) { setPosition({ x: evt.clientX - containerBounds.x, y: evt.clientY - containerBounds.y, }); + + const { connection, elementBelow, isValid, isHoveringHandle } = checkElementBelowIsValid(evt); + + if (!isHoveringHandle) { + if (recentHoveredHandle) { + recentHoveredHandle.classList.remove('valid'); + recentHoveredHandle.classList.remove('connecting'); + } + return false; + } + + const isOwnHandle = connection.source === connection.target; + + if (!isOwnHandle) { + recentHoveredHandle = elementBelow; + elementBelow.classList.add('connecting'); + elementBelow.classList.toggle('valid', isValid); + } } function onMouseUp(evt) { - const elementBelow = document.elementFromPoint(evt.clientX, evt.clientY); + const { connection, isValid } = checkElementBelowIsValid(evt); - if (elementBelow && (elementBelow.classList.contains('target') || elementBelow.classList.contains('source'))) { - if (isTarget) { - const sourceId = elementBelow.getAttribute('data-nodeid'); - onConnect({ source: sourceId, target: nodeId }); - } else { - const targetId = elementBelow.getAttribute('data-nodeid'); - onConnect({ source: nodeId, target: targetId }); - } + if (isValid) { + onConnect(connection); + } + + if (recentHoveredHandle) { + recentHoveredHandle.classList.remove('valid'); } setSourceId(null); @@ -42,7 +90,7 @@ function onMouseDown(evt, { nodeId, setSourceId, setPosition, onConnect, isTarg const BaseHandle = memo(({ type, nodeId, onConnect, position, setSourceId, setPosition, className, - id = false, ...rest + id = false, isValidConnection, ...rest }) => { const isTarget = type === 'target'; const handleClasses = cx( @@ -59,7 +107,10 @@ const BaseHandle = memo(({ data-nodeid={nodeIdWithHandleId} data-handlepos={position} className={handleClasses} - onMouseDown={evt => onMouseDown(evt, { nodeId: nodeIdWithHandleId, setSourceId, setPosition, onConnect, isTarget })} + onMouseDown={evt => onMouseDown(evt, { + nodeId: nodeIdWithHandleId, setSourceId, setPosition, + onConnect, isTarget, isValidConnection + })} {...rest} /> ); diff --git a/src/components/Handle/index.js b/src/components/Handle/index.js index 4579305a..791f19f0 100644 --- a/src/components/Handle/index.js +++ b/src/components/Handle/index.js @@ -33,13 +33,15 @@ Handle.displayName = 'Handle'; Handle.propTypes = { type: PropTypes.oneOf(['source', 'target']), position: PropTypes.oneOf(['top', 'right', 'bottom', 'left']), - onConnect: PropTypes.func + onConnect: PropTypes.func, + isValidConnection: PropTypes.func }; Handle.defaultProps = { type: 'source', position: 'top', - onConnect: () => {} + onConnect: () => {}, + isValidConnection: () => true }; export default Handle; diff --git a/src/container/Editor/index.js b/src/container/ReactFlow/index.js similarity index 95% rename from src/container/Editor/index.js rename to src/container/ReactFlow/index.js index 5187cfd3..4aaa54a8 100644 --- a/src/container/Editor/index.js +++ b/src/container/ReactFlow/index.js @@ -21,7 +21,7 @@ import store from '../../store'; import '../../style.css'; -const Editor = ({ +const ReactFlow = ({ style, onElementClick, elements, children, nodeTypes, edgeTypes, onLoad, onMove, onElementsRemove, onConnect, onNodeDragStop, connectionLineType, @@ -54,9 +54,9 @@ const Editor = ({ ); }; -Editor.displayName = 'Editor'; +ReactFlow.displayName = 'ReactFlow'; -Editor.defaultProps = { +ReactFlow.defaultProps = { onElementClick: () => {}, onElementsRemove: () => {}, onNodeDragStop: () => {}, @@ -79,4 +79,4 @@ Editor.defaultProps = { selectionKeyCode: 16 }; -export default Editor; +export default ReactFlow; diff --git a/src/index.js b/src/index.js index 2ec15a60..492e447e 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,4 @@ -import ReactFlow from './container/Editor'; +import ReactFlow from './container/ReactFlow'; export default ReactFlow;