feat(nodes): allow multiple handles
This commit is contained in:
@@ -7,7 +7,8 @@ const SpecialNode = ({ data, styles }) => (
|
||||
<div
|
||||
style={{ background: '#FFCC00', padding: 10, borderRadius: 2, ...styles }}
|
||||
>
|
||||
<TargetHandle style={{ left: 10, background: '#999' }} />
|
||||
<TargetHandle id="a" style={{ left: 10, background: '#999' }} />
|
||||
<TargetHandle id="b" style={{ left: 30, background: '#999' }} />
|
||||
<div>I am <strong>special</strong>!<br />{data.label}</div>
|
||||
<select onChange={(e) => data.onChange(e.target.value, data)}>
|
||||
<option value="1">1</option>
|
||||
@@ -77,6 +78,7 @@ class App extends PureComponent {
|
||||
{ source: '2', target: '3' },
|
||||
{ source: '3', target: '4', type: 'step' },
|
||||
{ source: '3', target: '5' },
|
||||
{ source: '5', target: '6__b' },
|
||||
{ source: '5', target: '6', type: 'step', animated: true, style: { stroke: '#FFCC00' } },
|
||||
{ source: '6', target: '7', style: { stroke: '#FFCC00' }},
|
||||
]
|
||||
|
||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -8378,9 +8378,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"ts-toolbelt": {
|
||||
"version": "3.8.1",
|
||||
"resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-3.8.1.tgz",
|
||||
"integrity": "sha512-XDAWEHVscqto6pG0gPSbYaEfEBcl9V0JK5pQIQJ3M+6sYnxK5ZOPyyRA/aq78WxKlLvBSXCPJ1fPX+7qCCg2Zg=="
|
||||
"version": "3.8.91",
|
||||
"resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-3.8.91.tgz",
|
||||
"integrity": "sha512-HVY9kFtQCtAfuHPf8XK9BWzwUueGtviZ5i/x6hu9uMIy+PWqlwjD1eqRTBaCewOclR5w07j1bgaT7cWNWrJgZw=="
|
||||
},
|
||||
"tty-browserify": {
|
||||
"version": "0.0.0",
|
||||
|
||||
@@ -3,8 +3,13 @@ import cx from 'classnames';
|
||||
|
||||
export default (props) => {
|
||||
const [sourceNode, setSourceNode] = useState(null);
|
||||
const hasHandleId = props.connectionSourceId.includes('__');
|
||||
const sourceIdSplitted = props.connectionSourceId.split('__');
|
||||
const nodeId = sourceIdSplitted[0];
|
||||
const handleId = hasHandleId ? sourceIdSplitted[1] : null;
|
||||
|
||||
useEffect(() => {
|
||||
setSourceNode(props.nodes.find(n => n.id === props.connectionSourceId));
|
||||
setSourceNode(props.nodes.find(n => n.id === nodeId));
|
||||
}, []);
|
||||
|
||||
if (!sourceNode) {
|
||||
@@ -14,7 +19,7 @@ export default (props) => {
|
||||
const style = props.connectionLineStyle || {};
|
||||
const className = cx('react-graph__edge', 'connection', props.className);
|
||||
|
||||
const sourceHandle = sourceNode.__rg.handleBounds.source;
|
||||
const sourceHandle = handleId ? sourceNode.__rg.handleBounds.source.find(d => d.id === handleId) : sourceNode.__rg.handleBounds.source[0];
|
||||
const sourceHandleX = sourceHandle ? sourceHandle.x + (sourceHandle.width / 2) : sourceNode.__rg.width / 2;
|
||||
const sourceHandleY = sourceHandle ? sourceHandle.y + (sourceHandle.height / 2) : sourceNode.__rg.height;
|
||||
const sourceX = sourceNode.__rg.position.x + sourceHandleX;
|
||||
|
||||
@@ -4,27 +4,48 @@ import { useStoreState } from 'easy-peasy';
|
||||
import ConnectionLine from '../ConnectionLine';
|
||||
import { isEdge } from '../graph-utils';
|
||||
|
||||
function getEdgePositions(sourceNode, targetNode) {
|
||||
function getEdgePositions({ sourceNode, targetNode, sourceHandleId, targetHandleId }) {
|
||||
const hasSourceHandle = !!sourceNode.__rg.handleBounds.source;
|
||||
const hasTargetHandle = !!targetNode.__rg.handleBounds.target;
|
||||
|
||||
let sourceHandle = null;
|
||||
let targetHandle = null;
|
||||
|
||||
if (hasSourceHandle) {
|
||||
// there is no sourceHandleId when there are no multiple handles/ handles with ids
|
||||
// so we just pick the first one
|
||||
if (sourceNode.__rg.handleBounds.source.length === 1 || !sourceHandleId) {
|
||||
sourceHandle = sourceNode.__rg.handleBounds.source[0];
|
||||
} else if (sourceHandleId) {
|
||||
sourceHandle = sourceNode.__rg.handleBounds.source.find(d => d.id === sourceHandleId);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasTargetHandle) {
|
||||
if (targetNode.__rg.handleBounds.target.length === 1 || !targetHandleId) {
|
||||
targetHandle = targetNode.__rg.handleBounds.target[0];
|
||||
} else if (targetHandleId) {
|
||||
targetHandle = targetNode.__rg.handleBounds.target.find(d => d.id === targetHandleId);
|
||||
}
|
||||
}
|
||||
|
||||
const sourceHandleX = hasSourceHandle ?
|
||||
sourceNode.__rg.handleBounds.source.x + (sourceNode.__rg.handleBounds.source.width / 2) :
|
||||
sourceHandle.x + (sourceHandle.width / 2) :
|
||||
sourceNode.__rg.width / 2;
|
||||
|
||||
const sourceHandleY = hasSourceHandle ?
|
||||
sourceNode.__rg.handleBounds.source.y + (sourceNode.__rg.handleBounds.source.height / 2) :
|
||||
sourceHandle.y + (sourceHandle.height / 2) :
|
||||
sourceNode.__rg.height;
|
||||
|
||||
const sourceX = sourceNode.__rg.position.x + sourceHandleX;
|
||||
const sourceY = sourceNode.__rg.position.y + sourceHandleY;
|
||||
|
||||
const targetHandleX = hasTargetHandle ?
|
||||
targetNode.__rg.handleBounds.target.x + (targetNode.__rg.handleBounds.target.width / 2) :
|
||||
targetHandle.x + (targetHandle.width / 2) :
|
||||
targetNode.__rg.width / 2;
|
||||
|
||||
const targetHandleY = hasTargetHandle ?
|
||||
targetNode.__rg.handleBounds.target.y + (targetNode.__rg.handleBounds.target.height / 2) :
|
||||
targetHandle.y + (targetHandle.height / 2) :
|
||||
0;
|
||||
|
||||
const targetX = targetNode.__rg.position.x + targetHandleX;
|
||||
@@ -37,22 +58,32 @@ function getEdgePositions(sourceNode, targetNode) {
|
||||
|
||||
function renderEdge(e, props, state) {
|
||||
const edgeType = e.type || 'default';
|
||||
const sourceNode = state.nodes.find(n => n.id === e.source);
|
||||
const targetNode = state.nodes.find(n => n.id === e.target);
|
||||
|
||||
const hasSourceHandleId = e.source.includes('__');
|
||||
const hasTargetHandleId = e.target.includes('__');
|
||||
|
||||
const sourceId = hasSourceHandleId ? e.source.split('__')[0] : e.source;
|
||||
const targetId = hasTargetHandleId ? e.target.split('__')[0] : e.target;
|
||||
|
||||
const sourceHandleId = hasSourceHandleId ? e.source.split('__')[1] : null;
|
||||
const targetHandleId = hasTargetHandleId ? e.target.split('__')[1] : null;
|
||||
|
||||
const sourceNode = state.nodes.find(n => n.id === sourceId);
|
||||
const targetNode = state.nodes.find(n => n.id === targetId);
|
||||
|
||||
if (!sourceNode) {
|
||||
throw new Error(`couldn't create edge for source id: ${e.source}`);
|
||||
throw new Error(`couldn't create edge for source id: ${sourceId}`);
|
||||
}
|
||||
|
||||
if (!targetNode) {
|
||||
throw new Error(`couldn't create edge for target id: ${e.target}`);
|
||||
throw new Error(`couldn't create edge for target id: ${targetId}`);
|
||||
}
|
||||
|
||||
const EdgeComponent = props.edgeTypes[edgeType] || props.edgeTypes.default;
|
||||
const { sourceX, sourceY, targetX, targetY } = getEdgePositions(sourceNode, targetNode);
|
||||
const { sourceX, sourceY, targetX, targetY } = getEdgePositions({ sourceNode, targetNode, sourceHandleId, targetHandleId });
|
||||
const selected = state.selectedElements
|
||||
.filter(isEdge)
|
||||
.find(elm => elm.source === e.source && elm.target === e.target);
|
||||
.find(elm => elm.source === sourceId && elm.target === targetId);
|
||||
|
||||
return (
|
||||
<EdgeComponent
|
||||
@@ -63,8 +94,10 @@ function renderEdge(e, props, state) {
|
||||
selected={selected}
|
||||
animated={e.animated}
|
||||
style={e.style}
|
||||
source={e.source}
|
||||
target={e.target}
|
||||
source={sourceId}
|
||||
target={targetId}
|
||||
sourceHandleId={sourceHandleId}
|
||||
targetHandleId={targetHandleId}
|
||||
sourceX={sourceX}
|
||||
sourceY={sourceY}
|
||||
targetX={targetX}
|
||||
|
||||
@@ -41,7 +41,8 @@ function onMouseDown(evt, { nodeId, setSourceId, setPosition, onConnect, isTarg
|
||||
|
||||
const BaseHandle = memo(({
|
||||
source, target, nodeId, onConnect,
|
||||
setSourceId, setPosition, className, ...rest
|
||||
setSourceId, setPosition, className,
|
||||
id = false, ...rest
|
||||
}) => {
|
||||
const handleClasses = cx(
|
||||
'react-graph__handle',
|
||||
@@ -49,11 +50,13 @@ const BaseHandle = memo(({
|
||||
{ source, target }
|
||||
);
|
||||
|
||||
const handleId = id ? `${nodeId}__${id}` : nodeId;
|
||||
|
||||
return (
|
||||
<div
|
||||
data-nodeid={nodeId}
|
||||
data-nodeid={handleId}
|
||||
className={handleClasses}
|
||||
onMouseDown={evt => onMouseDown(evt, { nodeId, setSourceId, setPosition, onConnect, isTarget: target })}
|
||||
onMouseDown={evt => onMouseDown(evt, { nodeId: handleId, setSourceId, setPosition, onConnect, isTarget: target })}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -13,20 +13,31 @@ const isHandle = e => (
|
||||
);
|
||||
|
||||
const getHandleBounds = (sel, nodeElement, parentBounds, k) => {
|
||||
const handle = nodeElement.querySelector(sel);
|
||||
const handles = nodeElement.querySelectorAll(sel);
|
||||
|
||||
if (!handle) {
|
||||
if (!handles || !handles.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const bounds = handle.getBoundingClientRect();
|
||||
const dimensions = getDimensions(handle);
|
||||
return [].map.call(handles, (handle) => {
|
||||
const bounds = handle.getBoundingClientRect();
|
||||
const dimensions = getDimensions(handle);
|
||||
const nodeIdAttr = handle.getAttribute('data-nodeid');
|
||||
const nodeIdSplitted = nodeIdAttr.split('__');
|
||||
|
||||
return {
|
||||
x: (bounds.x - parentBounds.x) * (1 / k),
|
||||
y: (bounds.y - parentBounds.y) * (1 / k),
|
||||
...dimensions
|
||||
};
|
||||
let handleId = null;
|
||||
|
||||
if (nodeIdSplitted) {
|
||||
handleId = nodeIdSplitted.length ? nodeIdSplitted[1] : nodeIdSplitted;
|
||||
}
|
||||
|
||||
return {
|
||||
id: handleId,
|
||||
x: (bounds.x - parentBounds.x) * (1 / k),
|
||||
y: (bounds.y - parentBounds.y) * (1 / k),
|
||||
...dimensions
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const onStart = (evt, { setOffset, onClick, id, type, data, position, transform }) => {
|
||||
@@ -92,7 +103,6 @@ export default NodeComponent => {
|
||||
source: getHandleBounds('.source', nodeElement.current, bounds, transform[2]),
|
||||
target: getHandleBounds('.target', nodeElement.current, bounds, transform[2])
|
||||
};
|
||||
|
||||
store.dispatch.updateNodeData({ id, ...dimensions, handleBounds });
|
||||
}, []);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user