feat(handles): validate connection
This commit is contained in:
@@ -19,6 +19,12 @@
|
|||||||
left: 10px;
|
left: 10px;
|
||||||
z-index: 4;
|
z-index: 4;
|
||||||
}
|
}
|
||||||
|
.react-flow__handle.connecting {
|
||||||
|
background: orange;
|
||||||
|
}
|
||||||
|
.react-flow__handle.valid {
|
||||||
|
background: green;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ export default({ data, styles }) => (
|
|||||||
<Handle type="target" position="left" style={{ background: '#999' }} />
|
<Handle type="target" position="left" style={{ background: '#999' }} />
|
||||||
<div>{data.input}</div>
|
<div>{data.input}</div>
|
||||||
<input onChange={(e) => data.onChange(e.target.value, data)} />
|
<input onChange={(e) => data.onChange(e.target.value, data)} />
|
||||||
<Handle type="source" position="right" style={{ background: '#999' }} />
|
<Handle
|
||||||
|
type="source"
|
||||||
|
position="right"
|
||||||
|
style={{ background: '#999' }}
|
||||||
|
isValidConnection={connection => +connection.target % 2 === 0}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -26,7 +26,11 @@ export default ({ data, styles }) => {
|
|||||||
<option value="2">2</option>
|
<option value="2">2</option>
|
||||||
<option value="3">3</option>
|
<option value="3">3</option>
|
||||||
</select>
|
</select>
|
||||||
<Handle type="source" position="bottom" style={{ left: 10, background: '#999' }} />
|
<Handle
|
||||||
|
type="source"
|
||||||
|
position="bottom"
|
||||||
|
style={{ left: 10, background: '#999' }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import React, { memo } from 'react';
|
import React, { memo } from 'react';
|
||||||
import cx from 'classnames';
|
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();
|
const containerBounds = document.querySelector('.react-flow').getBoundingClientRect();
|
||||||
|
let recentHoveredHandle = null;
|
||||||
|
|
||||||
setPosition({
|
setPosition({
|
||||||
x: evt.clientX - containerBounds.x,
|
x: evt.clientX - containerBounds.x,
|
||||||
@@ -10,24 +11,71 @@ function onMouseDown(evt, { nodeId, setSourceId, setPosition, onConnect, isTarg
|
|||||||
});
|
});
|
||||||
setSourceId(nodeId);
|
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) {
|
function onMouseMove(evt) {
|
||||||
setPosition({
|
setPosition({
|
||||||
x: evt.clientX - containerBounds.x,
|
x: evt.clientX - containerBounds.x,
|
||||||
y: evt.clientY - containerBounds.y,
|
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) {
|
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 (isValid) {
|
||||||
if (isTarget) {
|
onConnect(connection);
|
||||||
const sourceId = elementBelow.getAttribute('data-nodeid');
|
}
|
||||||
onConnect({ source: sourceId, target: nodeId });
|
|
||||||
} else {
|
if (recentHoveredHandle) {
|
||||||
const targetId = elementBelow.getAttribute('data-nodeid');
|
recentHoveredHandle.classList.remove('valid');
|
||||||
onConnect({ source: nodeId, target: targetId });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setSourceId(null);
|
setSourceId(null);
|
||||||
@@ -42,7 +90,7 @@ function onMouseDown(evt, { nodeId, setSourceId, setPosition, onConnect, isTarg
|
|||||||
const BaseHandle = memo(({
|
const BaseHandle = memo(({
|
||||||
type, nodeId, onConnect, position,
|
type, nodeId, onConnect, position,
|
||||||
setSourceId, setPosition, className,
|
setSourceId, setPosition, className,
|
||||||
id = false, ...rest
|
id = false, isValidConnection, ...rest
|
||||||
}) => {
|
}) => {
|
||||||
const isTarget = type === 'target';
|
const isTarget = type === 'target';
|
||||||
const handleClasses = cx(
|
const handleClasses = cx(
|
||||||
@@ -59,7 +107,10 @@ const BaseHandle = memo(({
|
|||||||
data-nodeid={nodeIdWithHandleId}
|
data-nodeid={nodeIdWithHandleId}
|
||||||
data-handlepos={position}
|
data-handlepos={position}
|
||||||
className={handleClasses}
|
className={handleClasses}
|
||||||
onMouseDown={evt => onMouseDown(evt, { nodeId: nodeIdWithHandleId, setSourceId, setPosition, onConnect, isTarget })}
|
onMouseDown={evt => onMouseDown(evt, {
|
||||||
|
nodeId: nodeIdWithHandleId, setSourceId, setPosition,
|
||||||
|
onConnect, isTarget, isValidConnection
|
||||||
|
})}
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -33,13 +33,15 @@ Handle.displayName = 'Handle';
|
|||||||
Handle.propTypes = {
|
Handle.propTypes = {
|
||||||
type: PropTypes.oneOf(['source', 'target']),
|
type: PropTypes.oneOf(['source', 'target']),
|
||||||
position: PropTypes.oneOf(['top', 'right', 'bottom', 'left']),
|
position: PropTypes.oneOf(['top', 'right', 'bottom', 'left']),
|
||||||
onConnect: PropTypes.func
|
onConnect: PropTypes.func,
|
||||||
|
isValidConnection: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
Handle.defaultProps = {
|
Handle.defaultProps = {
|
||||||
type: 'source',
|
type: 'source',
|
||||||
position: 'top',
|
position: 'top',
|
||||||
onConnect: () => {}
|
onConnect: () => {},
|
||||||
|
isValidConnection: () => true
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Handle;
|
export default Handle;
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import store from '../../store';
|
|||||||
|
|
||||||
import '../../style.css';
|
import '../../style.css';
|
||||||
|
|
||||||
const Editor = ({
|
const ReactFlow = ({
|
||||||
style, onElementClick, elements, children,
|
style, onElementClick, elements, children,
|
||||||
nodeTypes, edgeTypes, onLoad, onMove,
|
nodeTypes, edgeTypes, onLoad, onMove,
|
||||||
onElementsRemove, onConnect, onNodeDragStop, connectionLineType,
|
onElementsRemove, onConnect, onNodeDragStop, connectionLineType,
|
||||||
@@ -54,9 +54,9 @@ const Editor = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Editor.displayName = 'Editor';
|
ReactFlow.displayName = 'ReactFlow';
|
||||||
|
|
||||||
Editor.defaultProps = {
|
ReactFlow.defaultProps = {
|
||||||
onElementClick: () => {},
|
onElementClick: () => {},
|
||||||
onElementsRemove: () => {},
|
onElementsRemove: () => {},
|
||||||
onNodeDragStop: () => {},
|
onNodeDragStop: () => {},
|
||||||
@@ -79,4 +79,4 @@ Editor.defaultProps = {
|
|||||||
selectionKeyCode: 16
|
selectionKeyCode: 16
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Editor;
|
export default ReactFlow;
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import ReactFlow from './container/Editor';
|
import ReactFlow from './container/ReactFlow';
|
||||||
|
|
||||||
export default ReactFlow;
|
export default ReactFlow;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user