* fix(ts): use strict mode strictNullChecks etc

* chore: Use extended React.HTMLAttributes<> (#41)

* refactor(code-format): add prettier closes #42

* feat(renderer): add snap to grid option closes #20

* chore(dependabot): use develop as target branch
This commit is contained in:
Moritz
2019-10-21 20:58:28 +02:00
committed by GitHub
parent ce210eb5dc
commit a793f8c2ff
80 changed files with 2634 additions and 1247 deletions
+6 -5
View File
@@ -1,9 +1,10 @@
version: 1 version: 1
update_configs: update_configs:
- package_manager: "javascript" - package_manager: 'javascript'
directory: "/" directory: '/'
update_schedule: "weekly" update_schedule: 'weekly'
target_branch: 'develop'
automerged_updates: automerged_updates:
- match: - match:
dependency_type: "all" dependency_type: 'all'
update_type: "semver:minor" update_type: 'semver:minor'
+1
View File
@@ -0,0 +1 @@
example/src
+306 -175
View File
@@ -5756,6 +5756,8 @@ var storeModel = {
selection: null, selection: null,
connectionSourceId: null, connectionSourceId: null,
connectionPosition: { x: 0, y: 0 }, connectionPosition: { x: 0, y: 0 },
snapGrid: [16, 16],
snapToGrid: true,
onConnect: function () { }, onConnect: function () { },
setOnConnect: action(function (state, onConnect) { setOnConnect: action(function (state, onConnect) {
state.onConnect = onConnect; state.onConnect = onConnect;
@@ -5776,9 +5778,18 @@ var storeModel = {
}), }),
updateNodePos: action(function (state, _a) { updateNodePos: action(function (state, _a) {
var id = _a.id, pos = _a.pos; var id = _a.id, pos = _a.pos;
var position = pos;
if (state.snapToGrid) {
var transformedGridSizeX = state.snapGrid[0] * state.transform[2];
var transformedGridSizeY = state.snapGrid[1] * state.transform[2];
position = {
x: transformedGridSizeX * Math.round(pos.x / transformedGridSizeX),
y: transformedGridSizeY * Math.round(pos.y / transformedGridSizeY),
};
}
state.nodes.forEach(function (n) { state.nodes.forEach(function (n) {
if (n.id === id) { if (n.id === id) {
n.__rg = __assign(__assign({}, n.__rg), { position: pos }); n.__rg = __assign(__assign({}, n.__rg), { position: position });
} }
}); });
}), }),
@@ -5787,7 +5798,7 @@ var storeModel = {
}), }),
setNodesSelection: action(function (state, _a) { setNodesSelection: action(function (state, _a) {
var isActive = _a.isActive, selection = _a.selection; var isActive = _a.isActive, selection = _a.selection;
if (!isActive) { if (!isActive || typeof selection === 'undefined') {
state.nodesSelectionActive = false; state.nodesSelectionActive = false;
state.selectedElements = []; state.selectedElements = [];
return; return;
@@ -5802,7 +5813,9 @@ var storeModel = {
setSelectedElements: action(function (state, elements) { setSelectedElements: action(function (state, elements) {
var selectedElementsArr = Array.isArray(elements) ? elements : [elements]; var selectedElementsArr = Array.isArray(elements) ? elements : [elements];
var selectedElementsUpdated = !fastDeepEqual(selectedElementsArr, state.selectedElements); var selectedElementsUpdated = !fastDeepEqual(selectedElementsArr, state.selectedElements);
var selectedElements = selectedElementsUpdated ? selectedElementsArr : state.selectedElements; var selectedElements = selectedElementsUpdated
? selectedElementsArr
: state.selectedElements;
state.selectedElements = selectedElements; state.selectedElements = selectedElements;
}), }),
updateSelection: action(function (state, selection) { updateSelection: action(function (state, selection) {
@@ -5811,7 +5824,9 @@ var storeModel = {
var nextSelectedElements = __spreadArrays(selectedNodes, selectedEdges); var nextSelectedElements = __spreadArrays(selectedNodes, selectedEdges);
var selectedElementsUpdated = !fastDeepEqual(nextSelectedElements, state.selectedElements); var selectedElementsUpdated = !fastDeepEqual(nextSelectedElements, state.selectedElements);
state.selection = selection; state.selection = selection;
state.selectedElements = selectedElementsUpdated ? nextSelectedElements : state.selectedElements; state.selectedElements = selectedElementsUpdated
? nextSelectedElements
: state.selectedElements;
}), }),
updateTransform: action(function (state, transform) { updateTransform: action(function (state, transform) {
state.transform = [transform.x, transform.y, transform.k]; state.transform = [transform.x, transform.y, transform.k];
@@ -5831,7 +5846,12 @@ var storeModel = {
}), }),
setConnectionSourceId: action(function (state, sourceId) { setConnectionSourceId: action(function (state, sourceId) {
state.connectionSourceId = sourceId; state.connectionSourceId = sourceId;
}) }),
setSnapGrid: action(function (state, _a) {
var snapToGrid = _a.snapToGrid, snapGrid = _a.snapGrid;
state.snapToGrid = snapToGrid;
state.snapGrid = snapGrid;
}),
}; };
var store = createStore$1(storeModel); var store = createStore$1(storeModel);
@@ -5845,7 +5865,9 @@ var getOutgoers = function (node, elements) {
if (!isNode(node)) { if (!isNode(node)) {
return []; return [];
} }
var outgoerIds = elements.filter(function (e) { return e.source === node.id; }).map(function (e) { return e.target; }); var outgoerIds = elements
.filter(function (e) { return e.source === node.id; })
.map(function (e) { return e.target; });
return elements.filter(function (e) { return outgoerIds.includes(e.id); }); return elements.filter(function (e) { return outgoerIds.includes(e.id); });
}; };
var removeElements = function (elementsToRemove, elements) { var removeElements = function (elementsToRemove, elements) {
@@ -5864,7 +5886,9 @@ var addEdge = function (edgeParams, elements) {
if (!edgeParams.source || !edgeParams.target) { if (!edgeParams.source || !edgeParams.target) {
throw new Error('Can not create edge. An edge needs a source and a target'); throw new Error('Can not create edge. An edge needs a source and a target');
} }
return elements.concat(__assign(__assign({}, edgeParams), { id: typeof edgeParams.id !== 'undefined' ? edgeParams.id : getEdgeId(edgeParams) })); return elements.concat(__assign(__assign({}, edgeParams), { id: typeof edgeParams.id !== 'undefined'
? edgeParams.id
: getEdgeId(edgeParams) }));
}; };
var pointToRendererPoint = function (_a, transform) { var pointToRendererPoint = function (_a, transform) {
var x = _a.x, y = _a.y; var x = _a.x, y = _a.y;
@@ -5872,10 +5896,11 @@ var pointToRendererPoint = function (_a, transform) {
var rendererY = (y - transform[1]) * (1 / transform[2]); var rendererY = (y - transform[1]) * (1 / transform[2]);
return { return {
x: rendererX, x: rendererX,
y: rendererY y: rendererY,
}; };
}; };
var parseElement = function (element, transform) { var parseElement = function (element, transform) {
if (transform === void 0) { transform = [0, 0, 1]; }
if (!element.id) { if (!element.id) {
throw new Error('All elements (nodes and edges) need to have an id.'); throw new Error('All elements (nodes and edges) need to have an id.');
} }
@@ -5887,7 +5912,7 @@ var parseElement = function (element, transform) {
position: pointToRendererPoint(nodeElement.position, transform), position: pointToRendererPoint(nodeElement.position, transform),
width: null, width: null,
height: null, height: null,
handleBounds: {} handleBounds: {},
} }); } });
}; };
var getBoundingBox = function (nodes) { var getBoundingBox = function (nodes) {
@@ -5912,23 +5937,22 @@ var getBoundingBox = function (nodes) {
minX: Number.MAX_VALUE, minX: Number.MAX_VALUE,
minY: Number.MAX_VALUE, minY: Number.MAX_VALUE,
maxX: 0, maxX: 0,
maxY: 0 maxY: 0,
}); });
return { return {
x: bbox.minX, x: bbox.minX,
y: bbox.minY, y: bbox.minY,
width: bbox.maxX - bbox.minX, width: bbox.maxX - bbox.minX,
height: bbox.maxY - bbox.minY height: bbox.maxY - bbox.minY,
}; };
}; };
var getNodesInside = function (nodes, bbox, transform, partially) { var getNodesInside = function (nodes, bbox, transform, partially) {
if (transform === void 0) { transform = [0, 0, 1]; } if (transform === void 0) { transform = [0, 0, 1]; }
if (partially === void 0) { partially = false; } if (partially === void 0) { partially = false; }
return nodes return nodes.filter(function (n) {
.filter(function (n) {
var bboxPos = { var bboxPos = {
x: (bbox.x - transform[0]) * (1 / transform[2]), x: (bbox.x - transform[0]) * (1 / transform[2]),
y: (bbox.y - transform[1]) * (1 / transform[2]) y: (bbox.y - transform[1]) * (1 / transform[2]),
}; };
var bboxWidth = bbox.width * (1 / transform[2]); var bboxWidth = bbox.width * (1 / transform[2]);
var bboxHeight = bbox.height * (1 / transform[2]); var bboxHeight = bbox.height * (1 / transform[2]);
@@ -5937,8 +5961,10 @@ var getNodesInside = function (nodes, bbox, transform, partially) {
var nodeHeight = partially ? 0 : height; var nodeHeight = partially ? 0 : height;
var offsetX = partially ? width : 0; var offsetX = partially ? width : 0;
var offsetY = partially ? height : 0; var offsetY = partially ? height : 0;
return ((position.x + offsetX > bboxPos.x && (position.x + nodeWidth) < (bboxPos.x + bboxWidth)) && return (position.x + offsetX > bboxPos.x &&
(position.y + offsetY > bboxPos.y && (position.y + nodeHeight) < (bboxPos.y + bboxHeight))); position.x + nodeWidth < bboxPos.x + bboxWidth &&
(position.y + offsetY > bboxPos.y &&
position.y + nodeHeight < bboxPos.y + bboxHeight));
}); });
}; };
var getConnectedEdges = function (nodes, edges) { var getConnectedEdges = function (nodes, edges) {
@@ -5954,21 +5980,36 @@ var getConnectedEdges = function (nodes, edges) {
var fitView = function (_a) { var fitView = function (_a) {
var padding = (_a === void 0 ? { padding: 0 } : _a).padding; var padding = (_a === void 0 ? { padding: 0 } : _a).padding;
var state = store.getState(); var state = store.getState();
if (!state.d3Selection || !state.d3Zoom) {
return;
}
var bounds = getBoundingBox(state.nodes); var bounds = getBoundingBox(state.nodes);
var maxBoundsSize = Math.max(bounds.width, bounds.height); var maxBoundsSize = Math.max(bounds.width, bounds.height);
var k = Math.min(state.width, state.height) / (maxBoundsSize + (maxBoundsSize * padding)); var k = Math.min(state.width, state.height) /
var boundsCenterX = bounds.x + (bounds.width / 2); (maxBoundsSize + maxBoundsSize * padding);
var boundsCenterY = bounds.y + (bounds.height / 2); var boundsCenterX = bounds.x + bounds.width / 2;
var transform = [(state.width / 2) - (boundsCenterX * k), (state.height / 2) - (boundsCenterY * k)]; var boundsCenterY = bounds.y + bounds.height / 2;
var fittedTransform = identity$1.translate(transform[0], transform[1]).scale(k); var transform = [
state.width / 2 - boundsCenterX * k,
state.height / 2 - boundsCenterY * k,
];
var fittedTransform = identity$1
.translate(transform[0], transform[1])
.scale(k);
state.d3Selection.call(state.d3Zoom.transform, fittedTransform); state.d3Selection.call(state.d3Zoom.transform, fittedTransform);
}; };
var zoomIn = function () { var zoomIn = function () {
var state = store.getState(); var state = store.getState();
if (!state.d3Zoom || !state.d3Selection) {
return;
}
state.d3Zoom.scaleTo(state.d3Selection, state.transform[2] + 0.2); state.d3Zoom.scaleTo(state.d3Selection, state.transform[2] + 0.2);
}; };
var zoomOut = function () { var zoomOut = function () {
var state = store.getState(); var state = store.getState();
if (!state.d3Zoom || !state.d3Selection) {
return;
}
state.d3Zoom.scaleTo(state.d3Selection, state.transform[2] - 0.2); state.d3Zoom.scaleTo(state.d3Selection, state.transform[2] - 0.2);
}; };
@@ -5977,21 +6018,24 @@ function renderNode(node, props, state) {
if (!props.nodeTypes[nodeType]) { if (!props.nodeTypes[nodeType]) {
console.warn("No node type found for type \"" + nodeType + "\". Using fallback type \"default\"."); console.warn("No node type found for type \"" + nodeType + "\". Using fallback type \"default\".");
} }
var NodeComponent = (props.nodeTypes[nodeType] || props.nodeTypes.default); var NodeComponent = (props.nodeTypes[nodeType] ||
props.nodeTypes.default);
var selected = state.selectedElements var selected = state.selectedElements
.filter(isNode) .filter(isNode)
.map(function (e) { return e.id; }) .map(function (e) { return e.id; })
.includes(node.id); .includes(node.id);
return (React.createElement(NodeComponent, { key: node.id, id: node.id, type: node.type, data: node.data, xPos: node.__rg.position.x, yPos: node.__rg.position.y, onClick: props.onElementClick, onNodeDragStop: props.onNodeDragStop, transform: state.transform, selected: selected, style: node.style })); return (React.createElement(NodeComponent, { key: node.id, id: node.id, type: nodeType, data: node.data, xPos: node.__rg.position.x, yPos: node.__rg.position.y, onClick: props.onElementClick, onNodeDragStop: props.onNodeDragStop, transform: state.transform, selected: selected, style: node.style }));
} }
var NodeRenderer = memo(function (props) { var NodeRenderer = memo(function (props) {
var state = useStoreState$1(function (s) { return ({ var state = useStoreState$1(function (s) { return ({
nodes: s.nodes, nodes: s.nodes,
transform: s.transform, transform: s.transform,
selectedElements: s.selectedElements selectedElements: s.selectedElements,
}); }); }); });
var transform = state.transform, nodes = state.nodes; var transform = state.transform, nodes = state.nodes;
var transformStyle = { transform: "translate(" + transform[0] + "px," + transform[1] + "px) scale(" + transform[2] + ")" }; var transformStyle = {
transform: "translate(" + transform[0] + "px," + transform[1] + "px) scale(" + transform[2] + ")",
};
return (React.createElement("div", { className: "react-flow__nodes", style: transformStyle }, nodes.map(function (node) { return renderNode(node, props, state); }))); return (React.createElement("div", { className: "react-flow__nodes", style: transformStyle }, nodes.map(function (node) { return renderNode(node, props, state); })));
}); });
NodeRenderer.displayName = 'NodeRenderer'; NodeRenderer.displayName = 'NodeRenderer';
@@ -6068,11 +6112,15 @@ var ConnectionLine = (function (_a) {
return null; return null;
} }
var edgeClasses = classnames('react-flow__edge', 'connection', className); var edgeClasses = classnames('react-flow__edge', 'connection', className);
var sourceHandle = handleId ? var sourceHandle = handleId
sourceNode.__rg.handleBounds.source.find(function (d) { return d.id === handleId; }) : ? sourceNode.__rg.handleBounds.source.find(function (d) { return d.id === handleId; })
sourceNode.__rg.handleBounds.source[0]; : sourceNode.__rg.handleBounds.source[0];
var sourceHandleX = sourceHandle ? sourceHandle.x + (sourceHandle.width / 2) : sourceNode.__rg.width / 2; var sourceHandleX = sourceHandle
var sourceHandleY = sourceHandle ? sourceHandle.y + (sourceHandle.height / 2) : sourceNode.__rg.height; ? sourceHandle.x + sourceHandle.width / 2
: sourceNode.__rg.width / 2;
var sourceHandleY = sourceHandle
? sourceHandle.y + sourceHandle.height / 2
: sourceNode.__rg.height;
var sourceX = sourceNode.__rg.position.x + sourceHandleX; var sourceX = sourceNode.__rg.position.x + sourceHandleX;
var sourceY = sourceNode.__rg.position.y + sourceHandleY; var sourceY = sourceNode.__rg.position.y + sourceHandleY;
var targetX = (connectionPositionX - transform[0]) * (1 / transform[2]); var targetX = (connectionPositionX - transform[0]) * (1 / transform[2]);
@@ -6094,42 +6142,49 @@ function getHandlePosition(position, node, handle) {
if (handle === void 0) { handle = null; } if (handle === void 0) { handle = null; }
if (!handle) { if (!handle) {
switch (position) { switch (position) {
case 'top': return { case 'top':
x: node.__rg.width / 2, return {
y: 0 x: node.__rg.width / 2,
}; y: 0,
case 'right': return { };
x: node.__rg.width, case 'right':
y: node.__rg.height / 2 return {
}; x: node.__rg.width,
case 'bottom': return { y: node.__rg.height / 2,
x: node.__rg.width / 2, };
y: node.__rg.height case 'bottom':
}; return {
case 'left': return { x: node.__rg.width / 2,
x: 0, y: node.__rg.height,
y: node.__rg.height / 2 };
}; case 'left':
return {
x: 0,
y: node.__rg.height / 2,
};
} }
return null;
} }
switch (position) { switch (position) {
case 'top': return { case 'top':
x: handle.x + (handle.width / 2), return {
y: handle.y x: handle.x + handle.width / 2,
}; y: handle.y,
case 'right': return { };
x: handle.x + handle.width, case 'right':
y: handle.y + (handle.height / 2) return {
}; x: handle.x + handle.width,
case 'bottom': return { y: handle.y + handle.height / 2,
x: handle.x + (handle.width / 2), };
y: handle.y + handle.height case 'bottom':
}; return {
case 'left': return { x: handle.x + handle.width / 2,
x: handle.x, y: handle.y + handle.height,
y: handle.y + (handle.height / 2) };
}; case 'left':
return {
x: handle.x,
y: handle.y + handle.height / 2,
};
} }
} }
function getHandle(bounds, handleId) { function getHandle(bounds, handleId) {
@@ -6155,7 +6210,10 @@ function getEdgePositions(sourceNode, sourceHandle, sourcePosition, targetNode,
var targetX = targetNode.__rg.position.x + targetHandlePos.x; var targetX = targetNode.__rg.position.x + targetHandlePos.x;
var targetY = targetNode.__rg.position.y + targetHandlePos.y; var targetY = targetNode.__rg.position.y + targetHandlePos.y;
return { return {
sourceX: sourceX, sourceY: sourceY, targetX: targetX, targetY: targetY sourceX: sourceX,
sourceY: sourceY,
targetX: targetX,
targetY: targetY,
}; };
} }
function renderEdge(edge, props, state) { function renderEdge(edge, props, state) {
@@ -6193,7 +6251,7 @@ var EdgeRenderer = memo(function (_a) {
transform: s.transform, transform: s.transform,
selectedElements: s.selectedElements, selectedElements: s.selectedElements,
connectionSourceId: s.connectionSourceId, connectionSourceId: s.connectionSourceId,
position: s.connectionPosition position: s.connectionPosition,
}); }); }); });
if (!width) { if (!width) {
return null; return null;
@@ -6202,7 +6260,12 @@ var EdgeRenderer = memo(function (_a) {
var transformStyle = "translate(" + transform[0] + "," + transform[1] + ") scale(" + transform[2] + ")"; var transformStyle = "translate(" + transform[0] + "," + transform[1] + ") scale(" + transform[2] + ")";
return (React.createElement("svg", { width: width, height: height, className: "react-flow__edges" }, return (React.createElement("svg", { width: width, height: height, className: "react-flow__edges" },
React.createElement("g", { transform: transformStyle }, React.createElement("g", { transform: transformStyle },
edges.map(function (e) { return renderEdge(e, __assign({ width: width, height: height, connectionLineStyle: connectionLineStyle, connectionLineType: connectionLineType }, rest), state); }), edges.map(function (e) {
return renderEdge(e, __assign({ width: width,
height: height,
connectionLineStyle: connectionLineStyle,
connectionLineType: connectionLineType }, rest), state);
}),
connectionSourceId && (React.createElement(ConnectionLine, { nodes: nodes, connectionSourceId: connectionSourceId, connectionPositionX: position.x, connectionPositionY: position.y, transform: transform, connectionLineStyle: connectionLineStyle, connectionLineType: connectionLineType }))))); connectionSourceId && (React.createElement(ConnectionLine, { nodes: nodes, connectionSourceId: connectionSourceId, connectionPositionX: position.x, connectionPositionY: position.y, transform: transform, connectionLineStyle: connectionLineStyle, connectionLineType: connectionLineType })))));
}); });
EdgeRenderer.displayName = 'EdgeRenderer'; EdgeRenderer.displayName = 'EdgeRenderer';
@@ -6214,7 +6277,7 @@ var initialRect = {
y: 0, y: 0,
width: 0, width: 0,
height: 0, height: 0,
draw: false draw: false,
}; };
function getMousePosition(evt) { function getMousePosition(evt) {
var reactFlowNode = document.querySelector('.react-flow'); var reactFlowNode = document.querySelector('.react-flow');
@@ -6237,7 +6300,7 @@ var UserSelection = memo(function () {
function onMouseDown(evt) { function onMouseDown(evt) {
var mousePos = getMousePosition(evt); var mousePos = getMousePosition(evt);
if (!mousePos) { if (!mousePos) {
return false; return;
} }
setRect(function (currentRect) { return (__assign(__assign({}, currentRect), { startX: mousePos.x, startY: mousePos.y, x: mousePos.x, y: mousePos.y, draw: true })); }); setRect(function (currentRect) { return (__assign(__assign({}, currentRect), { startX: mousePos.x, startY: mousePos.y, x: mousePos.x, y: mousePos.y, draw: true })); });
setSelection(true); setSelection(true);
@@ -6253,7 +6316,11 @@ var UserSelection = memo(function () {
} }
var negativeX = mousePos.x < currentRect.startX; var negativeX = mousePos.x < currentRect.startX;
var negativeY = mousePos.y < currentRect.startY; var negativeY = mousePos.y < currentRect.startY;
var nextRect = __assign(__assign({}, currentRect), { x: negativeX ? mousePos.x : currentRect.x, y: negativeY ? mousePos.y : currentRect.y, width: negativeX ? currentRect.startX - mousePos.x : mousePos.x - currentRect.startX, height: negativeY ? currentRect.startY - mousePos.y : mousePos.y - currentRect.startY }); var nextRect = __assign(__assign({}, currentRect), { x: negativeX ? mousePos.x : currentRect.x, y: negativeY ? mousePos.y : currentRect.y, width: negativeX
? currentRect.startX - mousePos.x
: mousePos.x - currentRect.startX, height: negativeY
? currentRect.startY - mousePos.y
: mousePos.y - currentRect.startY });
updateSelection(nextRect); updateSelection(nextRect);
return nextRect; return nextRect;
}); });
@@ -6265,19 +6332,25 @@ var UserSelection = memo(function () {
return __assign(__assign({}, currentRect), { draw: false }); return __assign(__assign({}, currentRect), { draw: false });
}); });
} }
selectionPane.current.addEventListener('mousedown', onMouseDown); if (selectionPane.current) {
selectionPane.current.addEventListener('mousemove', onMouseMove); selectionPane.current.addEventListener('mousedown', onMouseDown);
selectionPane.current.addEventListener('mouseup', onMouseUp); selectionPane.current.addEventListener('mousemove', onMouseMove);
return function () { selectionPane.current.addEventListener('mouseup', onMouseUp);
selectionPane.current.removeEventListener('mousedown', onMouseDown); return function () {
selectionPane.current.removeEventListener('mousemove', onMouseMove); if (!selectionPane.current) {
selectionPane.current.removeEventListener('mouseup', onMouseUp); return;
}; }
}, []); selectionPane.current.removeEventListener('mousedown', onMouseDown);
selectionPane.current.removeEventListener('mousemove', onMouseMove);
selectionPane.current.removeEventListener('mouseup', onMouseUp);
};
}
return;
}, [selectionPane.current]);
return (React.createElement("div", { className: "react-flow__selectionpane", ref: selectionPane }, rect.draw && (React.createElement("div", { className: "react-flow__selection", style: { return (React.createElement("div", { className: "react-flow__selectionpane", ref: selectionPane }, rect.draw && (React.createElement("div", { className: "react-flow__selection", style: {
width: rect.width, width: rect.width,
height: rect.height, height: rect.height,
transform: "translate(" + rect.x + "px, " + rect.y + "px)" transform: "translate(" + rect.x + "px, " + rect.y + "px)",
} })))); } }))));
}); });
@@ -7948,16 +8021,15 @@ reactDraggable.default = default_1;
reactDraggable.DraggableCore = DraggableCore; reactDraggable.DraggableCore = DraggableCore;
function getStartPositions(elements) { function getStartPositions(elements) {
return elements var startPositions = {};
.filter(isNode) return elements.filter(isNode).reduce(function (res, node) {
.reduce(function (res, node) {
var startPosition = { var startPosition = {
x: node.__rg.position.x || node.position.x, x: node.__rg.position.x || node.position.x,
y: node.__rg.position.y || node.position.x y: node.__rg.position.y || node.position.x,
}; };
res[node.id] = startPosition; res[node.id] = startPosition;
return res; return res;
}, {}); }, startPositions);
} }
var NodesSelection = memo(function () { var NodesSelection = memo(function () {
var _a = useState({ x: 0, y: 0 }), offset = _a[0], setOffset = _a[1]; var _a = useState({ x: 0, y: 0 }), offset = _a[0], setOffset = _a[1];
@@ -7965,7 +8037,7 @@ var NodesSelection = memo(function () {
var state = useStoreState$1(function (s) { return ({ var state = useStoreState$1(function (s) { return ({
transform: s.transform, transform: s.transform,
selectedNodesBbox: s.selectedNodesBbox, selectedNodesBbox: s.selectedNodesBbox,
selectedElements: s.selectedElements selectedElements: s.selectedElements,
}); }); }); });
var updateNodePos = useStoreActions$1(function (a) { return a.updateNodePos; }); var updateNodePos = useStoreActions$1(function (a) { return a.updateNodePos; });
var _c = state.transform, x = _c[0], y = _c[1], k = _c[2]; var _c = state.transform, x = _c[0], y = _c[1], k = _c[2];
@@ -7973,37 +8045,46 @@ var NodesSelection = memo(function () {
var onStart = function (evt) { var onStart = function (evt) {
var scaledClient = { var scaledClient = {
x: evt.clientX * (1 / k), x: evt.clientX * (1 / k),
y: evt.clientY * (1 / k) y: evt.clientY * (1 / k),
}; };
var offsetX = scaledClient.x - position.x - x; var offsetX = scaledClient.x - position.x - x;
var offsetY = scaledClient.y - position.y - y; var offsetY = scaledClient.y - position.y - y;
var startPositions = getStartPositions(state.selectedElements); var nextStartPositions = getStartPositions(state.selectedElements);
setOffset({ x: offsetX, y: offsetY }); if (nextStartPositions) {
setStartPositions(startPositions); setOffset({ x: offsetX, y: offsetY });
setStartPositions(nextStartPositions);
}
}; };
var onDrag = function (evt) { var onDrag = function (evt) {
var scaledClient = { var scaledClient = {
x: evt.clientX * (1 / k), x: evt.clientX * (1 / k),
y: evt.clientY * (1 / k) y: evt.clientY * (1 / k),
}; };
state.selectedElements state.selectedElements.filter(isNode).forEach(function (node) {
.filter(isNode) var pos = {
.forEach(function (node) { x: startPositions[node.id].x +
updateNodePos({ id: node.id, pos: { scaledClient.x -
x: startPositions[node.id].x + scaledClient.x - position.x - offset.x - x, position.x -
y: startPositions[node.id].y + scaledClient.y - position.y - offset.y - y offset.x -
} }); x,
y: startPositions[node.id].y +
scaledClient.y -
position.y -
offset.y -
y,
};
updateNodePos({ id: node.id, pos: pos });
}); });
}; };
return (React.createElement("div", { className: "react-flow__nodesselection", style: { return (React.createElement("div", { className: "react-flow__nodesselection", style: {
transform: "translate(" + x + "px," + y + "px) scale(" + k + ")" transform: "translate(" + x + "px," + y + "px) scale(" + k + ")",
} }, } },
React.createElement(reactDraggable, { scale: k, onStart: function (evt) { return onStart(evt); }, onDrag: function (evt) { return onDrag(evt); } }, React.createElement(reactDraggable, { scale: k, onStart: function (evt) { return onStart(evt); }, onDrag: function (evt) { return onDrag(evt); } },
React.createElement("div", { className: "react-flow__nodesselection-rect", style: { React.createElement("div", { className: "react-flow__nodesselection-rect", style: {
width: state.selectedNodesBbox.width, width: state.selectedNodesBbox.width,
height: state.selectedNodesBbox.height, height: state.selectedNodesBbox.height,
top: state.selectedNodesBbox.y, top: state.selectedNodesBbox.y,
left: state.selectedNodesBbox.x left: state.selectedNodesBbox.x,
} })))); } }))));
}); });
@@ -8032,13 +8113,14 @@ var createGridDots = function (width, height, xOffset, yOffset, gap, size) {
var x = col * gap + xOffset; var x = col * gap + xOffset;
return Array.from({ length: lineCountY }, function (_, row) { return Array.from({ length: lineCountY }, function (_, row) {
var y = row * gap + yOffset; var y = row * gap + yOffset;
return "M" + x + " " + (y - size) + " l" + size + " " + size + " l" + -size + " " + size + " l" + -size + " " + -size + "z"; return "M" + x + " " + (y -
size) + " l" + size + " " + size + " l" + -size + " " + size + " l" + -size + " " + -size + "z";
}).join(' '); }).join(' ');
}); });
return values.join(' '); return values.join(' ');
}; };
var Grid = memo(function (_a) { var Grid = memo(function (_a) {
var _b = _a.gap, gap = _b === void 0 ? 24 : _b, _c = _a.color, color = _c === void 0 ? '#aaa' : _c, _d = _a.size, size = _d === void 0 ? 0.5 : _d, _e = _a.style, style = _e === void 0 ? {} : _e, _f = _a.className, className = _f === void 0 ? null : _f, _g = _a.backgroundType, backgroundType = _g === void 0 ? GridType.Dots : _g; var _b = _a.gap, gap = _b === void 0 ? 24 : _b, _c = _a.color, color = _c === void 0 ? '#aaa' : _c, _d = _a.size, size = _d === void 0 ? 0.5 : _d, _e = _a.style, style = _e === void 0 ? {} : _e, _f = _a.className, className = _f === void 0 ? '' : _f, _g = _a.backgroundType, backgroundType = _g === void 0 ? GridType.Dots : _g;
var _h = useStoreState$1(function (s) { return s; }), width = _h.width, height = _h.height, _j = _h.transform, x = _j[0], y = _j[1], scale = _j[2]; var _h = useStoreState$1(function (s) { return s; }), width = _h.width, height = _h.height, _j = _h.transform, x = _j[0], y = _j[1], scale = _j[2];
var gridClasses = classnames('react-flow__grid', className); var gridClasses = classnames('react-flow__grid', className);
var scaledGap = gap * scale; var scaledGap = gap * scale;
@@ -8057,11 +8139,11 @@ Grid.displayName = 'Grid';
var isInputDOMNode = function (e) { var isInputDOMNode = function (e) {
var target = e.target; var target = e.target;
return e && target && ['INPUT', 'SELECT', 'TEXTAREA'].includes(target.nodeName); return (e && target && ['INPUT', 'SELECT', 'TEXTAREA'].includes(target.nodeName));
}; };
var getDimensions = function (node) { return ({ var getDimensions = function (node) { return ({
width: node.offsetWidth, width: node.offsetWidth,
height: node.offsetHeight height: node.offsetHeight,
}); }; }); };
var useKeyPress = (function (keyCode) { var useKeyPress = (function (keyCode) {
@@ -8099,8 +8181,10 @@ var useD3Zoom = (function (zoomPane, onMove, shiftPressed) {
var initD3 = useStoreActions$1(function (actions) { return actions.initD3; }); var initD3 = useStoreActions$1(function (actions) { return actions.initD3; });
var updateTransform = useStoreActions$1(function (actions) { return actions.updateTransform; }); var updateTransform = useStoreActions$1(function (actions) { return actions.updateTransform; });
useEffect(function () { useEffect(function () {
var selection = select(zoomPane.current).call(d3ZoomInstance); if (zoomPane.current) {
initD3({ zoom: d3ZoomInstance, selection: selection }); var selection = select(zoomPane.current).call(d3ZoomInstance);
initD3({ zoom: d3ZoomInstance, selection: selection });
}
}, []); }, []);
useEffect(function () { useEffect(function () {
if (shiftPressed) { if (shiftPressed) {
@@ -8108,13 +8192,14 @@ var useD3Zoom = (function (zoomPane, onMove, shiftPressed) {
} }
else { else {
d3ZoomInstance.on('zoom', function () { d3ZoomInstance.on('zoom', function () {
if (event.sourceEvent && event.sourceEvent.target !== zoomPane.current) { if (event.sourceEvent &&
return false; event.sourceEvent.target !== zoomPane.current) {
return;
} }
updateTransform(event.transform); updateTransform(event.transform);
onMove(); onMove();
}); });
if (state.d3Selection) { if (state.d3Selection && state.d3Zoom) {
// we need to restore the graph transform otherwise d3 zoom transform and graph transform are not synced // we need to restore the graph transform otherwise d3 zoom transform and graph transform are not synced
var graphTransform = identity$1 var graphTransform = identity$1
.translate(state.transform[0], state.transform[1]) .translate(state.transform[0], state.transform[1])
@@ -8130,14 +8215,18 @@ var useD3Zoom = (function (zoomPane, onMove, shiftPressed) {
var useGlobalKeyHandler = (function (_a) { var useGlobalKeyHandler = (function (_a) {
var deleteKeyCode = _a.deleteKeyCode, onElementsRemove = _a.onElementsRemove; var deleteKeyCode = _a.deleteKeyCode, onElementsRemove = _a.onElementsRemove;
var state = useStoreState$1(function (s) { return ({ selectedElements: s.selectedElements, edges: s.edges }); }); var state = useStoreState$1(function (s) { return ({
selectedElements: s.selectedElements,
edges: s.edges,
}); });
var setNodesSelection = useStoreActions$1(function (a) { return a.setNodesSelection; }); var setNodesSelection = useStoreActions$1(function (a) { return a.setNodesSelection; });
var deleteKeyPressed = useKeyPress(deleteKeyCode); var deleteKeyPressed = useKeyPress(deleteKeyCode);
useEffect(function () { useEffect(function () {
if (deleteKeyPressed && state.selectedElements.length) { if (deleteKeyPressed && state.selectedElements.length) {
var elementsToRemove = state.selectedElements; var elementsToRemove = state.selectedElements;
// we also want to remove the edges if only one node is selected // we also want to remove the edges if only one node is selected
if (state.selectedElements.length === 1 && !isEdge(state.selectedElements[0])) { if (state.selectedElements.length === 1 &&
!isEdge(state.selectedElements[0])) {
var node = state.selectedElements[0]; var node = state.selectedElements[0];
var connectedEdges = getConnectedEdges([node], state.edges); var connectedEdges = getConnectedEdges([node], state.edges);
elementsToRemove = __spreadArrays(state.selectedElements, connectedEdges); elementsToRemove = __spreadArrays(state.selectedElements, connectedEdges);
@@ -8146,14 +8235,13 @@ var useGlobalKeyHandler = (function (_a) {
setNodesSelection({ isActive: false }); setNodesSelection({ isActive: false });
} }
}, [deleteKeyPressed]); }, [deleteKeyPressed]);
return null;
}); });
var useElementUpdater = function (elements) { var useElementUpdater = function (elements) {
var state = useStoreState$1(function (s) { return ({ var state = useStoreState$1(function (s) { return ({
nodes: s.nodes, nodes: s.nodes,
edges: s.edges, edges: s.edges,
transform: s.transform transform: s.transform,
}); }); }); });
var setNodes = useStoreActions$1(function (a) { return a.setNodes; }); var setNodes = useStoreActions$1(function (a) { return a.setNodes; });
var setEdges = useStoreActions$1(function (a) { return a.setEdges; }); var setEdges = useStoreActions$1(function (a) { return a.setEdges; });
@@ -8163,7 +8251,8 @@ var useElementUpdater = function (elements) {
var nextNodes = nodes.map(function (propNode) { var nextNodes = nodes.map(function (propNode) {
var existingNode = state.nodes.find(function (n) { return n.id === propNode.id; }); var existingNode = state.nodes.find(function (n) { return n.id === propNode.id; });
if (existingNode) { if (existingNode) {
var data = !fastDeepEqual(existingNode.data, propNode.data) ? __assign(__assign({}, existingNode.data), propNode.data) : existingNode.data; var data = !fastDeepEqual(existingNode.data, propNode.data)
? __assign(__assign({}, existingNode.data), propNode.data) : existingNode.data;
return __assign(__assign({}, existingNode), { data: data }); return __assign(__assign({}, existingNode), { data: data });
} }
return parseElement(propNode, state.transform); return parseElement(propNode, state.transform);
@@ -8177,11 +8266,10 @@ var useElementUpdater = function (elements) {
setEdges(edges); setEdges(edges);
} }
}); });
return null;
}; };
var GraphView = memo(function (_a) { var GraphView = memo(function (_a) {
var nodeTypes = _a.nodeTypes, edgeTypes = _a.edgeTypes, onMove = _a.onMove, onLoad = _a.onLoad, onElementClick = _a.onElementClick, onNodeDragStop = _a.onNodeDragStop, connectionLineType = _a.connectionLineType, connectionLineStyle = _a.connectionLineStyle, selectionKeyCode = _a.selectionKeyCode, onElementsRemove = _a.onElementsRemove, deleteKeyCode = _a.deleteKeyCode, elements = _a.elements, showBackground = _a.showBackground, backgroundGap = _a.backgroundGap, backgroundColor = _a.backgroundColor, backgroundType = _a.backgroundType, onConnect = _a.onConnect; var nodeTypes = _a.nodeTypes, edgeTypes = _a.edgeTypes, onMove = _a.onMove, onLoad = _a.onLoad, onElementClick = _a.onElementClick, onNodeDragStop = _a.onNodeDragStop, connectionLineType = _a.connectionLineType, connectionLineStyle = _a.connectionLineStyle, selectionKeyCode = _a.selectionKeyCode, onElementsRemove = _a.onElementsRemove, deleteKeyCode = _a.deleteKeyCode, elements = _a.elements, showBackground = _a.showBackground, backgroundGap = _a.backgroundGap, backgroundColor = _a.backgroundColor, backgroundType = _a.backgroundType, onConnect = _a.onConnect, snapToGrid = _a.snapToGrid, snapGrid = _a.snapGrid;
var zoomPane = useRef(null); var zoomPane = useRef(null);
var rendererNode = useRef(null); var rendererNode = useRef(null);
var state = useStoreState$1(function (s) { return ({ var state = useStoreState$1(function (s) { return ({
@@ -8190,14 +8278,18 @@ var GraphView = memo(function (_a) {
nodes: s.nodes, nodes: s.nodes,
edges: s.edges, edges: s.edges,
d3Initialised: s.d3Initialised, d3Initialised: s.d3Initialised,
nodesSelectionActive: s.nodesSelectionActive nodesSelectionActive: s.nodesSelectionActive,
}); }); }); });
var updateSize = useStoreActions$1(function (actions) { return actions.updateSize; }); var updateSize = useStoreActions$1(function (actions) { return actions.updateSize; });
var setNodesSelection = useStoreActions$1(function (actions) { return actions.setNodesSelection; }); var setNodesSelection = useStoreActions$1(function (actions) { return actions.setNodesSelection; });
var setOnConnect = useStoreActions$1(function (a) { return a.setOnConnect; }); var setOnConnect = useStoreActions$1(function (a) { return a.setOnConnect; });
var setSnapGrid = useStoreActions$1(function (actions) { return actions.setSnapGrid; });
var selectionKeyPressed = useKeyPress(selectionKeyCode); var selectionKeyPressed = useKeyPress(selectionKeyCode);
var onZoomPaneClick = function () { return setNodesSelection({ isActive: false }); }; var onZoomPaneClick = function () { return setNodesSelection({ isActive: false }); };
var updateDimensions = function () { var updateDimensions = function () {
if (!rendererNode.current) {
return;
}
var size = getDimensions(rendererNode.current); var size = getDimensions(rendererNode.current);
updateSize(size); updateSize(size);
}; };
@@ -8215,10 +8307,13 @@ var GraphView = memo(function (_a) {
onLoad({ onLoad({
fitView: fitView, fitView: fitView,
zoomIn: zoomIn, zoomIn: zoomIn,
zoomOut: zoomOut zoomOut: zoomOut,
}); });
} }
}, [state.d3Initialised]); }, [state.d3Initialised]);
useEffect(function () {
setSnapGrid({ snapToGrid: snapToGrid, snapGrid: snapGrid });
}, [snapToGrid]);
useGlobalKeyHandler({ onElementsRemove: onElementsRemove, deleteKeyCode: deleteKeyCode }); useGlobalKeyHandler({ onElementsRemove: onElementsRemove, deleteKeyCode: deleteKeyCode });
useElementUpdater(elements); useElementUpdater(elements);
return (React.createElement("div", { className: "react-flow__renderer", ref: rendererNode }, return (React.createElement("div", { className: "react-flow__renderer", ref: rendererNode },
@@ -8234,10 +8329,10 @@ GraphView.displayName = 'GraphView';
function onMouseDown(evt, nodeId, setSourceId, setPosition, onConnect, isTarget, isValidConnection) { function onMouseDown(evt, nodeId, setSourceId, setPosition, onConnect, isTarget, isValidConnection) {
var reactFlowNode = document.querySelector('.react-flow'); var reactFlowNode = document.querySelector('.react-flow');
if (!reactFlowNode) { if (!reactFlowNode) {
return null; return;
} }
var containerBounds = reactFlowNode.getBoundingClientRect(); var containerBounds = reactFlowNode.getBoundingClientRect();
var recentHoveredHandle = null; var recentHoveredHandle;
setPosition({ setPosition({
x: evt.clientX - containerBounds.left, x: evt.clientX - containerBounds.left,
y: evt.clientY - containerBounds.top, y: evt.clientY - containerBounds.top,
@@ -8245,7 +8340,7 @@ function onMouseDown(evt, nodeId, setSourceId, setPosition, onConnect, isTarget,
setSourceId(nodeId); setSourceId(nodeId);
function resetRecentHandle() { function resetRecentHandle() {
if (!recentHoveredHandle) { if (!recentHoveredHandle) {
return false; return;
} }
recentHoveredHandle.classList.remove('valid'); recentHoveredHandle.classList.remove('valid');
recentHoveredHandle.classList.remove('connecting'); recentHoveredHandle.classList.remove('connecting');
@@ -8257,9 +8352,11 @@ function onMouseDown(evt, nodeId, setSourceId, setPosition, onConnect, isTarget,
elementBelow: elementBelow, elementBelow: elementBelow,
isValid: false, isValid: false,
connection: { source: null, target: null }, connection: { source: null, target: null },
isHoveringHandle: false isHoveringHandle: false,
}; };
if (elementBelow && (elementBelow.classList.contains('target') || elementBelow.classList.contains('source'))) { if (elementBelow &&
(elementBelow.classList.contains('target') ||
elementBelow.classList.contains('source'))) {
var connection = { source: null, target: null }; var connection = { source: null, target: null };
if (isTarget) { if (isTarget) {
var sourceId = elementBelow.getAttribute('data-nodeid'); var sourceId = elementBelow.getAttribute('data-nodeid');
@@ -8286,7 +8383,7 @@ function onMouseDown(evt, nodeId, setSourceId, setPosition, onConnect, isTarget,
return resetRecentHandle(); return resetRecentHandle();
} }
var isOwnHandle = connection.source === connection.target; var isOwnHandle = connection.source === connection.target;
if (!isOwnHandle) { if (!isOwnHandle && elementBelow) {
recentHoveredHandle = elementBelow; recentHoveredHandle = elementBelow;
elementBelow.classList.add('connecting'); elementBelow.classList.add('connecting');
elementBelow.classList.toggle('valid', isValid); elementBelow.classList.toggle('valid', isValid);
@@ -8308,9 +8405,14 @@ function onMouseDown(evt, nodeId, setSourceId, setPosition, onConnect, isTarget,
var BaseHandle = memo(function (_a) { var BaseHandle = memo(function (_a) {
var type = _a.type, nodeId = _a.nodeId, onConnect = _a.onConnect, position = _a.position, setSourceId = _a.setSourceId, setPosition = _a.setPosition, className = _a.className, _b = _a.id, id = _b === void 0 ? false : _b, isValidConnection = _a.isValidConnection, rest = __rest(_a, ["type", "nodeId", "onConnect", "position", "setSourceId", "setPosition", "className", "id", "isValidConnection"]); var type = _a.type, nodeId = _a.nodeId, onConnect = _a.onConnect, position = _a.position, setSourceId = _a.setSourceId, setPosition = _a.setPosition, className = _a.className, _b = _a.id, id = _b === void 0 ? false : _b, isValidConnection = _a.isValidConnection, rest = __rest(_a, ["type", "nodeId", "onConnect", "position", "setSourceId", "setPosition", "className", "id", "isValidConnection"]);
var isTarget = type === 'target'; var isTarget = type === 'target';
var handleClasses = classnames('react-flow__handle', className, position, { source: !isTarget, target: isTarget }); var handleClasses = classnames('react-flow__handle', className, position, {
source: !isTarget,
target: isTarget,
});
var nodeIdWithHandleId = id ? nodeId + "__" + id : nodeId; var nodeIdWithHandleId = id ? nodeId + "__" + id : nodeId;
return (React.createElement("div", __assign({ "data-nodeid": nodeIdWithHandleId, "data-handlepos": position, className: handleClasses, onMouseDown: function (evt) { return onMouseDown(evt, nodeIdWithHandleId, setSourceId, setPosition, onConnect, isTarget, isValidConnection); } }, rest))); return (React.createElement("div", __assign({ "data-nodeid": nodeIdWithHandleId, "data-handlepos": position, className: handleClasses, onMouseDown: function (evt) {
return onMouseDown(evt, nodeIdWithHandleId, setSourceId, setPosition, onConnect, isTarget, isValidConnection);
} }, rest)));
}); });
BaseHandle.displayName = 'BaseHandle'; BaseHandle.displayName = 'BaseHandle';
@@ -8323,7 +8425,7 @@ var Handle = memo(function (_a) {
var nodeId = useContext(NodeIdContext); var nodeId = useContext(NodeIdContext);
var _f = useStoreActions$1(function (a) { return ({ var _f = useStoreActions$1(function (a) { return ({
setPosition: a.setConnectionPosition, setPosition: a.setConnectionPosition,
setSourceId: a.setConnectionSourceId setSourceId: a.setConnectionSourceId,
}); }), setPosition = _f.setPosition, setSourceId = _f.setSourceId; }); }), setPosition = _f.setPosition, setSourceId = _f.setSourceId;
var onConnectAction = useStoreState$1(function (s) { return s.onConnect; }); var onConnectAction = useStoreState$1(function (s) { return s.onConnect; });
var onConnectExtended = function (params) { var onConnectExtended = function (params) {
@@ -8338,7 +8440,7 @@ var nodeStyles = {
background: '#ff6060', background: '#ff6060',
padding: 10, padding: 10,
borderRadius: 5, borderRadius: 5,
width: 150 width: 150,
}; };
var DefaultNode = (function (_a) { var DefaultNode = (function (_a) {
var data = _a.data, style = _a.style; var data = _a.data, style = _a.style;
@@ -8352,7 +8454,7 @@ var nodeStyles$1 = {
background: '#9999ff', background: '#9999ff',
padding: 10, padding: 10,
borderRadius: 5, borderRadius: 5,
width: 150 width: 150,
}; };
var InputNode = (function (_a) { var InputNode = (function (_a) {
var data = _a.data, style = _a.style; var data = _a.data, style = _a.style;
@@ -8365,7 +8467,7 @@ var nodeStyles$2 = {
background: '#55dd99', background: '#55dd99',
padding: 10, padding: 10,
borderRadius: 5, borderRadius: 5,
width: 150 width: 150,
}; };
var OutputNode = (function (_a) { var OutputNode = (function (_a) {
var data = _a.data, style = _a.style; var data = _a.data, style = _a.style;
@@ -8642,15 +8744,18 @@ var getHandleBounds = function (selector, nodeElement, parentBounds, k) {
if (!handles || !handles.length) { if (!handles || !handles.length) {
return null; return null;
} }
return [].map.call(handles, function (handle) { var handlesArray = Array.from(handles);
return handlesArray.map(function (handle) {
var bounds = handle.getBoundingClientRect(); var bounds = handle.getBoundingClientRect();
var dimensions = getDimensions(handle); var dimensions = getDimensions(handle);
var nodeIdAttr = handle.getAttribute('data-nodeid'); var nodeIdAttr = handle.getAttribute('data-nodeid');
var handlePosition = handle.getAttribute('data-handlepos'); var handlePosition = handle.getAttribute('data-handlepos');
var nodeIdSplitted = nodeIdAttr.split('__'); var nodeIdSplitted = nodeIdAttr ? nodeIdAttr.split('__') : null;
var handleId = null; var handleId = null;
if (nodeIdSplitted) { if (nodeIdSplitted) {
handleId = (nodeIdSplitted.length ? nodeIdSplitted[1] : nodeIdSplitted); handleId = (nodeIdSplitted.length
? nodeIdSplitted[1]
: nodeIdSplitted);
} }
return __assign({ id: handleId, position: handlePosition, x: (bounds.left - parentBounds.left) * (1 / k), y: (bounds.top - parentBounds.top) * (1 / k) }, dimensions); return __assign({ id: handleId, position: handlePosition, x: (bounds.left - parentBounds.left) * (1 / k), y: (bounds.top - parentBounds.top) * (1 / k) }, dimensions);
}); });
@@ -8661,7 +8766,7 @@ var onStart = function (evt, onClick, id, type, data, setOffset, transform, posi
} }
var scaledClient = { var scaledClient = {
x: evt.clientX * (1 / transform[2]), x: evt.clientX * (1 / transform[2]),
y: evt.clientY * (1 / transform[2]) y: evt.clientY * (1 / transform[2]),
}; };
var offsetX = scaledClient.x - position.x - transform[0]; var offsetX = scaledClient.x - position.x - transform[0];
var offsetY = scaledClient.y - position.y - transform[1]; var offsetY = scaledClient.y - position.y - transform[1];
@@ -8673,19 +8778,25 @@ var onStart = function (evt, onClick, id, type, data, setOffset, transform, posi
var onDrag = function (evt, setDragging, id, offset, transform) { var onDrag = function (evt, setDragging, id, offset, transform) {
var scaledClient = { var scaledClient = {
x: evt.clientX * (1 / transform[2]), x: evt.clientX * (1 / transform[2]),
y: evt.clientY * (1 / transform[2]) y: evt.clientY * (1 / transform[2]),
}; };
setDragging(true); setDragging(true);
store.dispatch.updateNodePos({ id: id, pos: { store.dispatch.updateNodePos({
id: id,
pos: {
x: scaledClient.x - transform[0] - offset.x, x: scaledClient.x - transform[0] - offset.x,
y: scaledClient.y - transform[1] - offset.y y: scaledClient.y - transform[1] - offset.y,
} }); },
});
}; };
var onStop = function (onNodeDragStop, isDragging, setDragging, id, type, position, data) { var onStop = function (onNodeDragStop, isDragging, setDragging, id, type, position, data) {
if (isDragging) { if (isDragging) {
setDragging(false); setDragging(false);
onNodeDragStop({ onNodeDragStop({
id: id, type: type, position: position, data: data id: id,
type: type,
position: position,
data: data,
}); });
} }
}; };
@@ -8697,17 +8808,20 @@ var wrapNode = (function (NodeComponent) {
var _c = useState(false), isDragging = _c[0], setDragging = _c[1]; var _c = useState(false), isDragging = _c[0], setDragging = _c[1];
var position = { x: xPos, y: yPos }; var position = { x: xPos, y: yPos };
var nodeClasses = classnames('react-flow__node', { selected: selected }); var nodeClasses = classnames('react-flow__node', { selected: selected });
var nodeStyle = { zIndex: selected ? 10 : 3, transform: "translate(" + xPos + "px," + yPos + "px)" }; var nodeStyle = {
zIndex: selected ? 10 : 3,
transform: "translate(" + xPos + "px," + yPos + "px)",
};
var updateNode = function () { var updateNode = function () {
if (!nodeElement.current) { if (!nodeElement.current) {
return false; return;
} }
var storeState = store.getState(); var storeState = store.getState();
var bounds = nodeElement.current.getBoundingClientRect(); var bounds = nodeElement.current.getBoundingClientRect();
var dimensions = getDimensions(nodeElement.current); var dimensions = getDimensions(nodeElement.current);
var handleBounds = { var handleBounds = {
source: getHandleBounds('.source', nodeElement.current, bounds, storeState.transform[2]), source: getHandleBounds('.source', nodeElement.current, bounds, storeState.transform[2]),
target: getHandleBounds('.target', nodeElement.current, bounds, storeState.transform[2]) target: getHandleBounds('.target', nodeElement.current, bounds, storeState.transform[2]),
}; };
store.dispatch.updateNodeData(__assign(__assign({ id: id }, dimensions), { handleBounds: handleBounds })); store.dispatch.updateNodeData(__assign(__assign({ id: id }, dimensions), { handleBounds: handleBounds }));
}; };
@@ -8727,8 +8841,15 @@ var wrapNode = (function (NodeComponent) {
} }
}; };
} }
return;
}, [nodeElement.current]); }, [nodeElement.current]);
return (React.createElement(DraggableCore, { onStart: function (evt) { return onStart(evt, onClick, id, type, data, setOffset, transform, position); }, onDrag: function (evt) { return onDrag(evt, setDragging, id, offset, transform); }, onStop: function () { return onStop(onNodeDragStop, isDragging, setDragging, id, type, position, data); }, scale: transform[2] }, return (React.createElement(DraggableCore, { onStart: function (evt) {
return onStart(evt, onClick, id, type, data, setOffset, transform, position);
}, onDrag: function (evt) {
return onDrag(evt, setDragging, id, offset, transform);
}, onStop: function () {
return onStop(onNodeDragStop, isDragging, setDragging, id, type, position, data);
}, scale: transform[2] },
React.createElement("div", { className: nodeClasses, ref: nodeElement, style: nodeStyle }, React.createElement("div", { className: nodeClasses, ref: nodeElement, style: nodeStyle },
React.createElement(Provider, { value: id }, React.createElement(Provider, { value: id },
React.createElement(NodeComponent, { id: id, data: data, type: type, style: style, selected: selected }))))); React.createElement(NodeComponent, { id: id, data: data, type: type, style: style, selected: selected })))));
@@ -8741,15 +8862,15 @@ function createNodeTypes(nodeTypes) {
var standardTypes = { var standardTypes = {
input: wrapNode((nodeTypes.input || InputNode)), input: wrapNode((nodeTypes.input || InputNode)),
default: wrapNode((nodeTypes.default || DefaultNode)), default: wrapNode((nodeTypes.default || DefaultNode)),
output: wrapNode((nodeTypes.output || OutputNode)) output: wrapNode((nodeTypes.output || OutputNode)),
}; };
var specialTypes = Object var wrappedTypes = {};
.keys(nodeTypes) var specialTypes = Object.keys(nodeTypes)
.filter(function (k) { return !['input', 'default', 'output'].includes(k); }) .filter(function (k) { return !['input', 'default', 'output'].includes(k); })
.reduce(function (res, key) { .reduce(function (res, key) {
res[key] = wrapNode((nodeTypes[key] || DefaultNode)); res[key] = wrapNode((nodeTypes[key] || DefaultNode));
return res; return res;
}, {}); }, wrappedTypes);
return __assign(__assign({}, standardTypes), specialTypes); return __assign(__assign({}, standardTypes), specialTypes);
} }
@@ -8758,15 +8879,17 @@ var BezierEdge = memo(function (_a) {
var yOffset = Math.abs(targetY - sourceY) / 2; var yOffset = Math.abs(targetY - sourceY) / 2;
var centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset; var centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset;
var dAttr = "M" + sourceX + "," + sourceY + " C" + sourceX + "," + centerY + " " + targetX + "," + centerY + " " + targetX + "," + targetY; var dAttr = "M" + sourceX + "," + sourceY + " C" + sourceX + "," + centerY + " " + targetX + "," + centerY + " " + targetX + "," + targetY;
if (['left', 'right'].includes(sourcePosition) && ['left', 'right'].includes(targetPosition)) { if (['left', 'right'].includes(sourcePosition) &&
['left', 'right'].includes(targetPosition)) {
var xOffset = Math.abs(targetX - sourceX) / 2; var xOffset = Math.abs(targetX - sourceX) / 2;
var centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset; var centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset;
dAttr = "M" + sourceX + "," + sourceY + " C" + centerX + "," + sourceY + " " + centerX + "," + targetY + " " + targetX + "," + targetY; dAttr = "M" + sourceX + "," + sourceY + " C" + centerX + "," + sourceY + " " + centerX + "," + targetY + " " + targetX + "," + targetY;
} }
else if (['left', 'right'].includes(sourcePosition) || ['left', 'right'].includes(targetPosition)) { else if (['left', 'right'].includes(sourcePosition) ||
['left', 'right'].includes(targetPosition)) {
dAttr = "M" + sourceX + "," + sourceY + " C" + sourceX + "," + targetY + " " + sourceX + "," + targetY + " " + targetX + "," + targetY; dAttr = "M" + sourceX + "," + sourceY + " C" + sourceX + "," + targetY + " " + sourceX + "," + targetY + " " + targetX + "," + targetY;
} }
return (React.createElement("path", __assign({}, style, { d: dAttr }))); return React.createElement("path", __assign({}, style, { d: dAttr }));
}); });
var StraightEdge = memo(function (_a) { var StraightEdge = memo(function (_a) {
@@ -8787,7 +8910,7 @@ var wrapEdge = (function (EdgeComponent) {
var edgeClasses = classnames('react-flow__edge', { selected: selected, animated: animated }); var edgeClasses = classnames('react-flow__edge', { selected: selected, animated: animated });
var onEdgeClick = function (evt) { var onEdgeClick = function (evt) {
if (isInputDOMNode(evt)) { if (isInputDOMNode(evt)) {
return false; return;
} }
store.dispatch.setSelectedElements({ id: id, source: source, target: target }); store.dispatch.setSelectedElements({ id: id, source: source, target: target });
onClick({ id: id, source: source, target: target, type: type }); onClick({ id: id, source: source, target: target, type: type });
@@ -8802,15 +8925,15 @@ var wrapEdge = (function (EdgeComponent) {
function createEdgeTypes(edgeTypes) { function createEdgeTypes(edgeTypes) {
var standardTypes = { var standardTypes = {
default: wrapEdge((edgeTypes.default || BezierEdge)), default: wrapEdge((edgeTypes.default || BezierEdge)),
straight: wrapEdge((edgeTypes.bezier || StraightEdge)) straight: wrapEdge((edgeTypes.bezier || StraightEdge)),
}; };
var specialTypes = Object var wrappedTypes = {};
.keys(edgeTypes) var specialTypes = Object.keys(edgeTypes)
.filter(function (k) { return !['default', 'bezier'].includes(k); }) .filter(function (k) { return !['default', 'bezier'].includes(k); })
.reduce(function (res, key) { .reduce(function (res, key) {
res[key] = wrapEdge((edgeTypes[key] || BezierEdge)); res[key] = wrapEdge((edgeTypes[key] || BezierEdge));
return res; return res;
}, {}); }, wrappedTypes);
return __assign(__assign({}, standardTypes), specialTypes); return __assign(__assign({}, standardTypes), specialTypes);
} }
@@ -8845,12 +8968,12 @@ var css = ".react-flow {\n width: 100%;\n height: 100%;\n position: relative;
styleInject(css); styleInject(css);
var ReactFlow = function (_a) { var ReactFlow = function (_a) {
var style = _a.style, onElementClick = _a.onElementClick, elements = _a.elements, children = _a.children, nodeTypes = _a.nodeTypes, edgeTypes = _a.edgeTypes, onLoad = _a.onLoad, onMove = _a.onMove, onElementsRemove = _a.onElementsRemove, onConnect = _a.onConnect, onNodeDragStop = _a.onNodeDragStop, connectionLineType = _a.connectionLineType, connectionLineStyle = _a.connectionLineStyle, deleteKeyCode = _a.deleteKeyCode, selectionKeyCode = _a.selectionKeyCode, showBackground = _a.showBackground, backgroundGap = _a.backgroundGap, backgroundType = _a.backgroundType, backgroundColor = _a.backgroundColor; var style = _a.style, onElementClick = _a.onElementClick, elements = _a.elements, children = _a.children, nodeTypes = _a.nodeTypes, edgeTypes = _a.edgeTypes, onLoad = _a.onLoad, onMove = _a.onMove, onElementsRemove = _a.onElementsRemove, onConnect = _a.onConnect, onNodeDragStop = _a.onNodeDragStop, connectionLineType = _a.connectionLineType, connectionLineStyle = _a.connectionLineStyle, deleteKeyCode = _a.deleteKeyCode, selectionKeyCode = _a.selectionKeyCode, showBackground = _a.showBackground, backgroundGap = _a.backgroundGap, backgroundType = _a.backgroundType, backgroundColor = _a.backgroundColor, snapToGrid = _a.snapToGrid, snapGrid = _a.snapGrid;
var nodeTypesParsed = useMemo(function () { return createNodeTypes(nodeTypes); }, []); var nodeTypesParsed = useMemo(function () { return createNodeTypes(nodeTypes); }, []);
var edgeTypesParsed = useMemo(function () { return createEdgeTypes(edgeTypes); }, []); var edgeTypesParsed = useMemo(function () { return createEdgeTypes(edgeTypes); }, []);
return (React.createElement("div", { style: style, className: "react-flow" }, return (React.createElement("div", { style: style, className: "react-flow" },
React.createElement(StoreProvider, { store: store }, React.createElement(StoreProvider, { store: store },
React.createElement(GraphView, { onLoad: onLoad, onMove: onMove, onElementClick: onElementClick, onNodeDragStop: onNodeDragStop, nodeTypes: nodeTypesParsed, edgeTypes: edgeTypesParsed, connectionLineType: connectionLineType, connectionLineStyle: connectionLineStyle, selectionKeyCode: selectionKeyCode, onElementsRemove: onElementsRemove, deleteKeyCode: deleteKeyCode, elements: elements, onConnect: onConnect, backgroundColor: backgroundColor, backgroundGap: backgroundGap, showBackground: showBackground, backgroundType: backgroundType }), React.createElement(GraphView, { onLoad: onLoad, onMove: onMove, onElementClick: onElementClick, onNodeDragStop: onNodeDragStop, nodeTypes: nodeTypesParsed, edgeTypes: edgeTypesParsed, connectionLineType: connectionLineType, connectionLineStyle: connectionLineStyle, selectionKeyCode: selectionKeyCode, onElementsRemove: onElementsRemove, deleteKeyCode: deleteKeyCode, elements: elements, onConnect: onConnect, backgroundColor: backgroundColor, backgroundGap: backgroundGap, showBackground: showBackground, backgroundType: backgroundType, snapToGrid: snapToGrid, snapGrid: snapGrid }),
children))); children)));
}; };
ReactFlow.displayName = 'ReactFlow'; ReactFlow.displayName = 'ReactFlow';
@@ -8864,12 +8987,12 @@ ReactFlow.defaultProps = {
nodeTypes: { nodeTypes: {
input: InputNode, input: InputNode,
default: DefaultNode, default: DefaultNode,
output: OutputNode output: OutputNode,
}, },
edgeTypes: { edgeTypes: {
default: BezierEdge, default: BezierEdge,
straight: StraightEdge, straight: StraightEdge,
step: StepEdge step: StepEdge,
}, },
connectionLineType: 'bezier', connectionLineType: 'bezier',
connectionLineStyle: {}, connectionLineStyle: {},
@@ -8878,7 +9001,9 @@ ReactFlow.defaultProps = {
backgroundColor: '#eee', backgroundColor: '#eee',
backgroundGap: 24, backgroundGap: 24,
showBackground: true, showBackground: true,
backgroundType: GridType.Dots backgroundType: GridType.Dots,
snapToGrid: false,
snapGrid: [16, 16],
}; };
var baseStyle = { var baseStyle = {
@@ -8886,7 +9011,7 @@ var baseStyle = {
zIndex: 5, zIndex: 5,
bottom: 10, bottom: 10,
right: 10, right: 10,
width: 200 width: 200,
}; };
var index = (function (_a) { var index = (function (_a) {
var _b = _a.style, style = _b === void 0 ? {} : _b, className = _a.className, _c = _a.bgColor, bgColor = _c === void 0 ? '#f8f8f8' : _c, _d = _a.nodeColor, nodeColor = _d === void 0 ? '#ddd' : _d; var _b = _a.style, style = _b === void 0 ? {} : _b, className = _a.className, _c = _a.bgColor, bgColor = _c === void 0 ? '#f8f8f8' : _c, _d = _a.nodeColor, nodeColor = _d === void 0 ? '#ddd' : _d;
@@ -8903,23 +9028,29 @@ var index = (function (_a) {
var height = (state.height / (state.width || 1)) * width; var height = (state.height / (state.width || 1)) * width;
var bbox = { x: 0, y: 0, width: state.width, height: state.height }; var bbox = { x: 0, y: 0, width: state.width, height: state.height };
var scaleFactor = width / state.width; var scaleFactor = width / state.width;
var nodeColorFunc = (nodeColor instanceof Function ? nodeColor : function () { return nodeColor; }); var nodeColorFunc = (nodeColor instanceof Function
? nodeColor
: function () { return nodeColor; });
useEffect(function () { useEffect(function () {
if (canvasNode && canvasNode.current) { if (!canvasNode || !canvasNode.current) {
var ctx_1 = canvasNode.current.getContext('2d'); return;
var nodesInside = getNodesInside(state.nodes, bbox, state.transform, true);
ctx_1.fillStyle = bgColor;
ctx_1.fillRect(0, 0, width, height);
nodesInside.forEach(function (n) {
var pos = n.__rg.position;
var transformX = state.transform[0];
var transformY = state.transform[1];
var x = (pos.x * state.transform[2]) + transformX;
var y = (pos.y * state.transform[2]) + transformY;
ctx_1.fillStyle = nodeColorFunc(n);
ctx_1.fillRect((x * scaleFactor), (y * scaleFactor), n.__rg.width * scaleFactor * state.transform[2], n.__rg.height * scaleFactor * state.transform[2]);
});
} }
var ctx = canvasNode.current.getContext('2d');
if (!ctx) {
return;
}
var nodesInside = getNodesInside(state.nodes, bbox, state.transform, true);
ctx.fillStyle = bgColor;
ctx.fillRect(0, 0, width, height);
nodesInside.forEach(function (n) {
var pos = n.__rg.position;
var transformX = state.transform[0];
var transformY = state.transform[1];
var x = pos.x * state.transform[2] + transformX;
var y = pos.y * state.transform[2] + transformY;
ctx.fillStyle = nodeColorFunc(n);
ctx.fillRect(x * scaleFactor, y * scaleFactor, n.__rg.width * scaleFactor * state.transform[2], n.__rg.height * scaleFactor * state.transform[2]);
});
}, [canvasNode.current, nodePositions, state.transform, height]); }, [canvasNode.current, nodePositions, state.transform, height]);
return (React.createElement("canvas", { style: __assign(__assign(__assign({}, baseStyle), style), { height: height }), width: width, height: height, className: mapClasses, ref: canvasNode })); return (React.createElement("canvas", { style: __assign(__assign(__assign({}, baseStyle), style), { height: height }), width: width, height: height, className: mapClasses, ref: canvasNode }));
}); });
+1 -1
View File
File diff suppressed because one or more lines are too long
+306 -175
View File
@@ -5763,6 +5763,8 @@ var storeModel = {
selection: null, selection: null,
connectionSourceId: null, connectionSourceId: null,
connectionPosition: { x: 0, y: 0 }, connectionPosition: { x: 0, y: 0 },
snapGrid: [16, 16],
snapToGrid: true,
onConnect: function () { }, onConnect: function () { },
setOnConnect: action(function (state, onConnect) { setOnConnect: action(function (state, onConnect) {
state.onConnect = onConnect; state.onConnect = onConnect;
@@ -5783,9 +5785,18 @@ var storeModel = {
}), }),
updateNodePos: action(function (state, _a) { updateNodePos: action(function (state, _a) {
var id = _a.id, pos = _a.pos; var id = _a.id, pos = _a.pos;
var position = pos;
if (state.snapToGrid) {
var transformedGridSizeX = state.snapGrid[0] * state.transform[2];
var transformedGridSizeY = state.snapGrid[1] * state.transform[2];
position = {
x: transformedGridSizeX * Math.round(pos.x / transformedGridSizeX),
y: transformedGridSizeY * Math.round(pos.y / transformedGridSizeY),
};
}
state.nodes.forEach(function (n) { state.nodes.forEach(function (n) {
if (n.id === id) { if (n.id === id) {
n.__rg = __assign(__assign({}, n.__rg), { position: pos }); n.__rg = __assign(__assign({}, n.__rg), { position: position });
} }
}); });
}), }),
@@ -5794,7 +5805,7 @@ var storeModel = {
}), }),
setNodesSelection: action(function (state, _a) { setNodesSelection: action(function (state, _a) {
var isActive = _a.isActive, selection = _a.selection; var isActive = _a.isActive, selection = _a.selection;
if (!isActive) { if (!isActive || typeof selection === 'undefined') {
state.nodesSelectionActive = false; state.nodesSelectionActive = false;
state.selectedElements = []; state.selectedElements = [];
return; return;
@@ -5809,7 +5820,9 @@ var storeModel = {
setSelectedElements: action(function (state, elements) { setSelectedElements: action(function (state, elements) {
var selectedElementsArr = Array.isArray(elements) ? elements : [elements]; var selectedElementsArr = Array.isArray(elements) ? elements : [elements];
var selectedElementsUpdated = !fastDeepEqual(selectedElementsArr, state.selectedElements); var selectedElementsUpdated = !fastDeepEqual(selectedElementsArr, state.selectedElements);
var selectedElements = selectedElementsUpdated ? selectedElementsArr : state.selectedElements; var selectedElements = selectedElementsUpdated
? selectedElementsArr
: state.selectedElements;
state.selectedElements = selectedElements; state.selectedElements = selectedElements;
}), }),
updateSelection: action(function (state, selection) { updateSelection: action(function (state, selection) {
@@ -5818,7 +5831,9 @@ var storeModel = {
var nextSelectedElements = __spreadArrays(selectedNodes, selectedEdges); var nextSelectedElements = __spreadArrays(selectedNodes, selectedEdges);
var selectedElementsUpdated = !fastDeepEqual(nextSelectedElements, state.selectedElements); var selectedElementsUpdated = !fastDeepEqual(nextSelectedElements, state.selectedElements);
state.selection = selection; state.selection = selection;
state.selectedElements = selectedElementsUpdated ? nextSelectedElements : state.selectedElements; state.selectedElements = selectedElementsUpdated
? nextSelectedElements
: state.selectedElements;
}), }),
updateTransform: action(function (state, transform) { updateTransform: action(function (state, transform) {
state.transform = [transform.x, transform.y, transform.k]; state.transform = [transform.x, transform.y, transform.k];
@@ -5838,7 +5853,12 @@ var storeModel = {
}), }),
setConnectionSourceId: action(function (state, sourceId) { setConnectionSourceId: action(function (state, sourceId) {
state.connectionSourceId = sourceId; state.connectionSourceId = sourceId;
}) }),
setSnapGrid: action(function (state, _a) {
var snapToGrid = _a.snapToGrid, snapGrid = _a.snapGrid;
state.snapToGrid = snapToGrid;
state.snapGrid = snapGrid;
}),
}; };
var store = createStore$1(storeModel); var store = createStore$1(storeModel);
@@ -5852,7 +5872,9 @@ var getOutgoers = function (node, elements) {
if (!isNode(node)) { if (!isNode(node)) {
return []; return [];
} }
var outgoerIds = elements.filter(function (e) { return e.source === node.id; }).map(function (e) { return e.target; }); var outgoerIds = elements
.filter(function (e) { return e.source === node.id; })
.map(function (e) { return e.target; });
return elements.filter(function (e) { return outgoerIds.includes(e.id); }); return elements.filter(function (e) { return outgoerIds.includes(e.id); });
}; };
var removeElements = function (elementsToRemove, elements) { var removeElements = function (elementsToRemove, elements) {
@@ -5871,7 +5893,9 @@ var addEdge = function (edgeParams, elements) {
if (!edgeParams.source || !edgeParams.target) { if (!edgeParams.source || !edgeParams.target) {
throw new Error('Can not create edge. An edge needs a source and a target'); throw new Error('Can not create edge. An edge needs a source and a target');
} }
return elements.concat(__assign(__assign({}, edgeParams), { id: typeof edgeParams.id !== 'undefined' ? edgeParams.id : getEdgeId(edgeParams) })); return elements.concat(__assign(__assign({}, edgeParams), { id: typeof edgeParams.id !== 'undefined'
? edgeParams.id
: getEdgeId(edgeParams) }));
}; };
var pointToRendererPoint = function (_a, transform) { var pointToRendererPoint = function (_a, transform) {
var x = _a.x, y = _a.y; var x = _a.x, y = _a.y;
@@ -5879,10 +5903,11 @@ var pointToRendererPoint = function (_a, transform) {
var rendererY = (y - transform[1]) * (1 / transform[2]); var rendererY = (y - transform[1]) * (1 / transform[2]);
return { return {
x: rendererX, x: rendererX,
y: rendererY y: rendererY,
}; };
}; };
var parseElement = function (element, transform) { var parseElement = function (element, transform) {
if (transform === void 0) { transform = [0, 0, 1]; }
if (!element.id) { if (!element.id) {
throw new Error('All elements (nodes and edges) need to have an id.'); throw new Error('All elements (nodes and edges) need to have an id.');
} }
@@ -5894,7 +5919,7 @@ var parseElement = function (element, transform) {
position: pointToRendererPoint(nodeElement.position, transform), position: pointToRendererPoint(nodeElement.position, transform),
width: null, width: null,
height: null, height: null,
handleBounds: {} handleBounds: {},
} }); } });
}; };
var getBoundingBox = function (nodes) { var getBoundingBox = function (nodes) {
@@ -5919,23 +5944,22 @@ var getBoundingBox = function (nodes) {
minX: Number.MAX_VALUE, minX: Number.MAX_VALUE,
minY: Number.MAX_VALUE, minY: Number.MAX_VALUE,
maxX: 0, maxX: 0,
maxY: 0 maxY: 0,
}); });
return { return {
x: bbox.minX, x: bbox.minX,
y: bbox.minY, y: bbox.minY,
width: bbox.maxX - bbox.minX, width: bbox.maxX - bbox.minX,
height: bbox.maxY - bbox.minY height: bbox.maxY - bbox.minY,
}; };
}; };
var getNodesInside = function (nodes, bbox, transform, partially) { var getNodesInside = function (nodes, bbox, transform, partially) {
if (transform === void 0) { transform = [0, 0, 1]; } if (transform === void 0) { transform = [0, 0, 1]; }
if (partially === void 0) { partially = false; } if (partially === void 0) { partially = false; }
return nodes return nodes.filter(function (n) {
.filter(function (n) {
var bboxPos = { var bboxPos = {
x: (bbox.x - transform[0]) * (1 / transform[2]), x: (bbox.x - transform[0]) * (1 / transform[2]),
y: (bbox.y - transform[1]) * (1 / transform[2]) y: (bbox.y - transform[1]) * (1 / transform[2]),
}; };
var bboxWidth = bbox.width * (1 / transform[2]); var bboxWidth = bbox.width * (1 / transform[2]);
var bboxHeight = bbox.height * (1 / transform[2]); var bboxHeight = bbox.height * (1 / transform[2]);
@@ -5944,8 +5968,10 @@ var getNodesInside = function (nodes, bbox, transform, partially) {
var nodeHeight = partially ? 0 : height; var nodeHeight = partially ? 0 : height;
var offsetX = partially ? width : 0; var offsetX = partially ? width : 0;
var offsetY = partially ? height : 0; var offsetY = partially ? height : 0;
return ((position.x + offsetX > bboxPos.x && (position.x + nodeWidth) < (bboxPos.x + bboxWidth)) && return (position.x + offsetX > bboxPos.x &&
(position.y + offsetY > bboxPos.y && (position.y + nodeHeight) < (bboxPos.y + bboxHeight))); position.x + nodeWidth < bboxPos.x + bboxWidth &&
(position.y + offsetY > bboxPos.y &&
position.y + nodeHeight < bboxPos.y + bboxHeight));
}); });
}; };
var getConnectedEdges = function (nodes, edges) { var getConnectedEdges = function (nodes, edges) {
@@ -5961,21 +5987,36 @@ var getConnectedEdges = function (nodes, edges) {
var fitView = function (_a) { var fitView = function (_a) {
var padding = (_a === void 0 ? { padding: 0 } : _a).padding; var padding = (_a === void 0 ? { padding: 0 } : _a).padding;
var state = store.getState(); var state = store.getState();
if (!state.d3Selection || !state.d3Zoom) {
return;
}
var bounds = getBoundingBox(state.nodes); var bounds = getBoundingBox(state.nodes);
var maxBoundsSize = Math.max(bounds.width, bounds.height); var maxBoundsSize = Math.max(bounds.width, bounds.height);
var k = Math.min(state.width, state.height) / (maxBoundsSize + (maxBoundsSize * padding)); var k = Math.min(state.width, state.height) /
var boundsCenterX = bounds.x + (bounds.width / 2); (maxBoundsSize + maxBoundsSize * padding);
var boundsCenterY = bounds.y + (bounds.height / 2); var boundsCenterX = bounds.x + bounds.width / 2;
var transform = [(state.width / 2) - (boundsCenterX * k), (state.height / 2) - (boundsCenterY * k)]; var boundsCenterY = bounds.y + bounds.height / 2;
var fittedTransform = identity$1.translate(transform[0], transform[1]).scale(k); var transform = [
state.width / 2 - boundsCenterX * k,
state.height / 2 - boundsCenterY * k,
];
var fittedTransform = identity$1
.translate(transform[0], transform[1])
.scale(k);
state.d3Selection.call(state.d3Zoom.transform, fittedTransform); state.d3Selection.call(state.d3Zoom.transform, fittedTransform);
}; };
var zoomIn = function () { var zoomIn = function () {
var state = store.getState(); var state = store.getState();
if (!state.d3Zoom || !state.d3Selection) {
return;
}
state.d3Zoom.scaleTo(state.d3Selection, state.transform[2] + 0.2); state.d3Zoom.scaleTo(state.d3Selection, state.transform[2] + 0.2);
}; };
var zoomOut = function () { var zoomOut = function () {
var state = store.getState(); var state = store.getState();
if (!state.d3Zoom || !state.d3Selection) {
return;
}
state.d3Zoom.scaleTo(state.d3Selection, state.transform[2] - 0.2); state.d3Zoom.scaleTo(state.d3Selection, state.transform[2] - 0.2);
}; };
@@ -5984,21 +6025,24 @@ function renderNode(node, props, state) {
if (!props.nodeTypes[nodeType]) { if (!props.nodeTypes[nodeType]) {
console.warn("No node type found for type \"" + nodeType + "\". Using fallback type \"default\"."); console.warn("No node type found for type \"" + nodeType + "\". Using fallback type \"default\".");
} }
var NodeComponent = (props.nodeTypes[nodeType] || props.nodeTypes.default); var NodeComponent = (props.nodeTypes[nodeType] ||
props.nodeTypes.default);
var selected = state.selectedElements var selected = state.selectedElements
.filter(isNode) .filter(isNode)
.map(function (e) { return e.id; }) .map(function (e) { return e.id; })
.includes(node.id); .includes(node.id);
return (React__default.createElement(NodeComponent, { key: node.id, id: node.id, type: node.type, data: node.data, xPos: node.__rg.position.x, yPos: node.__rg.position.y, onClick: props.onElementClick, onNodeDragStop: props.onNodeDragStop, transform: state.transform, selected: selected, style: node.style })); return (React__default.createElement(NodeComponent, { key: node.id, id: node.id, type: nodeType, data: node.data, xPos: node.__rg.position.x, yPos: node.__rg.position.y, onClick: props.onElementClick, onNodeDragStop: props.onNodeDragStop, transform: state.transform, selected: selected, style: node.style }));
} }
var NodeRenderer = React.memo(function (props) { var NodeRenderer = React.memo(function (props) {
var state = useStoreState$1(function (s) { return ({ var state = useStoreState$1(function (s) { return ({
nodes: s.nodes, nodes: s.nodes,
transform: s.transform, transform: s.transform,
selectedElements: s.selectedElements selectedElements: s.selectedElements,
}); }); }); });
var transform = state.transform, nodes = state.nodes; var transform = state.transform, nodes = state.nodes;
var transformStyle = { transform: "translate(" + transform[0] + "px," + transform[1] + "px) scale(" + transform[2] + ")" }; var transformStyle = {
transform: "translate(" + transform[0] + "px," + transform[1] + "px) scale(" + transform[2] + ")",
};
return (React__default.createElement("div", { className: "react-flow__nodes", style: transformStyle }, nodes.map(function (node) { return renderNode(node, props, state); }))); return (React__default.createElement("div", { className: "react-flow__nodes", style: transformStyle }, nodes.map(function (node) { return renderNode(node, props, state); })));
}); });
NodeRenderer.displayName = 'NodeRenderer'; NodeRenderer.displayName = 'NodeRenderer';
@@ -6075,11 +6119,15 @@ var ConnectionLine = (function (_a) {
return null; return null;
} }
var edgeClasses = classnames('react-flow__edge', 'connection', className); var edgeClasses = classnames('react-flow__edge', 'connection', className);
var sourceHandle = handleId ? var sourceHandle = handleId
sourceNode.__rg.handleBounds.source.find(function (d) { return d.id === handleId; }) : ? sourceNode.__rg.handleBounds.source.find(function (d) { return d.id === handleId; })
sourceNode.__rg.handleBounds.source[0]; : sourceNode.__rg.handleBounds.source[0];
var sourceHandleX = sourceHandle ? sourceHandle.x + (sourceHandle.width / 2) : sourceNode.__rg.width / 2; var sourceHandleX = sourceHandle
var sourceHandleY = sourceHandle ? sourceHandle.y + (sourceHandle.height / 2) : sourceNode.__rg.height; ? sourceHandle.x + sourceHandle.width / 2
: sourceNode.__rg.width / 2;
var sourceHandleY = sourceHandle
? sourceHandle.y + sourceHandle.height / 2
: sourceNode.__rg.height;
var sourceX = sourceNode.__rg.position.x + sourceHandleX; var sourceX = sourceNode.__rg.position.x + sourceHandleX;
var sourceY = sourceNode.__rg.position.y + sourceHandleY; var sourceY = sourceNode.__rg.position.y + sourceHandleY;
var targetX = (connectionPositionX - transform[0]) * (1 / transform[2]); var targetX = (connectionPositionX - transform[0]) * (1 / transform[2]);
@@ -6101,42 +6149,49 @@ function getHandlePosition(position, node, handle) {
if (handle === void 0) { handle = null; } if (handle === void 0) { handle = null; }
if (!handle) { if (!handle) {
switch (position) { switch (position) {
case 'top': return { case 'top':
x: node.__rg.width / 2, return {
y: 0 x: node.__rg.width / 2,
}; y: 0,
case 'right': return { };
x: node.__rg.width, case 'right':
y: node.__rg.height / 2 return {
}; x: node.__rg.width,
case 'bottom': return { y: node.__rg.height / 2,
x: node.__rg.width / 2, };
y: node.__rg.height case 'bottom':
}; return {
case 'left': return { x: node.__rg.width / 2,
x: 0, y: node.__rg.height,
y: node.__rg.height / 2 };
}; case 'left':
return {
x: 0,
y: node.__rg.height / 2,
};
} }
return null;
} }
switch (position) { switch (position) {
case 'top': return { case 'top':
x: handle.x + (handle.width / 2), return {
y: handle.y x: handle.x + handle.width / 2,
}; y: handle.y,
case 'right': return { };
x: handle.x + handle.width, case 'right':
y: handle.y + (handle.height / 2) return {
}; x: handle.x + handle.width,
case 'bottom': return { y: handle.y + handle.height / 2,
x: handle.x + (handle.width / 2), };
y: handle.y + handle.height case 'bottom':
}; return {
case 'left': return { x: handle.x + handle.width / 2,
x: handle.x, y: handle.y + handle.height,
y: handle.y + (handle.height / 2) };
}; case 'left':
return {
x: handle.x,
y: handle.y + handle.height / 2,
};
} }
} }
function getHandle(bounds, handleId) { function getHandle(bounds, handleId) {
@@ -6162,7 +6217,10 @@ function getEdgePositions(sourceNode, sourceHandle, sourcePosition, targetNode,
var targetX = targetNode.__rg.position.x + targetHandlePos.x; var targetX = targetNode.__rg.position.x + targetHandlePos.x;
var targetY = targetNode.__rg.position.y + targetHandlePos.y; var targetY = targetNode.__rg.position.y + targetHandlePos.y;
return { return {
sourceX: sourceX, sourceY: sourceY, targetX: targetX, targetY: targetY sourceX: sourceX,
sourceY: sourceY,
targetX: targetX,
targetY: targetY,
}; };
} }
function renderEdge(edge, props, state) { function renderEdge(edge, props, state) {
@@ -6200,7 +6258,7 @@ var EdgeRenderer = React.memo(function (_a) {
transform: s.transform, transform: s.transform,
selectedElements: s.selectedElements, selectedElements: s.selectedElements,
connectionSourceId: s.connectionSourceId, connectionSourceId: s.connectionSourceId,
position: s.connectionPosition position: s.connectionPosition,
}); }); }); });
if (!width) { if (!width) {
return null; return null;
@@ -6209,7 +6267,12 @@ var EdgeRenderer = React.memo(function (_a) {
var transformStyle = "translate(" + transform[0] + "," + transform[1] + ") scale(" + transform[2] + ")"; var transformStyle = "translate(" + transform[0] + "," + transform[1] + ") scale(" + transform[2] + ")";
return (React__default.createElement("svg", { width: width, height: height, className: "react-flow__edges" }, return (React__default.createElement("svg", { width: width, height: height, className: "react-flow__edges" },
React__default.createElement("g", { transform: transformStyle }, React__default.createElement("g", { transform: transformStyle },
edges.map(function (e) { return renderEdge(e, __assign({ width: width, height: height, connectionLineStyle: connectionLineStyle, connectionLineType: connectionLineType }, rest), state); }), edges.map(function (e) {
return renderEdge(e, __assign({ width: width,
height: height,
connectionLineStyle: connectionLineStyle,
connectionLineType: connectionLineType }, rest), state);
}),
connectionSourceId && (React__default.createElement(ConnectionLine, { nodes: nodes, connectionSourceId: connectionSourceId, connectionPositionX: position.x, connectionPositionY: position.y, transform: transform, connectionLineStyle: connectionLineStyle, connectionLineType: connectionLineType }))))); connectionSourceId && (React__default.createElement(ConnectionLine, { nodes: nodes, connectionSourceId: connectionSourceId, connectionPositionX: position.x, connectionPositionY: position.y, transform: transform, connectionLineStyle: connectionLineStyle, connectionLineType: connectionLineType })))));
}); });
EdgeRenderer.displayName = 'EdgeRenderer'; EdgeRenderer.displayName = 'EdgeRenderer';
@@ -6221,7 +6284,7 @@ var initialRect = {
y: 0, y: 0,
width: 0, width: 0,
height: 0, height: 0,
draw: false draw: false,
}; };
function getMousePosition(evt) { function getMousePosition(evt) {
var reactFlowNode = document.querySelector('.react-flow'); var reactFlowNode = document.querySelector('.react-flow');
@@ -6244,7 +6307,7 @@ var UserSelection = React.memo(function () {
function onMouseDown(evt) { function onMouseDown(evt) {
var mousePos = getMousePosition(evt); var mousePos = getMousePosition(evt);
if (!mousePos) { if (!mousePos) {
return false; return;
} }
setRect(function (currentRect) { return (__assign(__assign({}, currentRect), { startX: mousePos.x, startY: mousePos.y, x: mousePos.x, y: mousePos.y, draw: true })); }); setRect(function (currentRect) { return (__assign(__assign({}, currentRect), { startX: mousePos.x, startY: mousePos.y, x: mousePos.x, y: mousePos.y, draw: true })); });
setSelection(true); setSelection(true);
@@ -6260,7 +6323,11 @@ var UserSelection = React.memo(function () {
} }
var negativeX = mousePos.x < currentRect.startX; var negativeX = mousePos.x < currentRect.startX;
var negativeY = mousePos.y < currentRect.startY; var negativeY = mousePos.y < currentRect.startY;
var nextRect = __assign(__assign({}, currentRect), { x: negativeX ? mousePos.x : currentRect.x, y: negativeY ? mousePos.y : currentRect.y, width: negativeX ? currentRect.startX - mousePos.x : mousePos.x - currentRect.startX, height: negativeY ? currentRect.startY - mousePos.y : mousePos.y - currentRect.startY }); var nextRect = __assign(__assign({}, currentRect), { x: negativeX ? mousePos.x : currentRect.x, y: negativeY ? mousePos.y : currentRect.y, width: negativeX
? currentRect.startX - mousePos.x
: mousePos.x - currentRect.startX, height: negativeY
? currentRect.startY - mousePos.y
: mousePos.y - currentRect.startY });
updateSelection(nextRect); updateSelection(nextRect);
return nextRect; return nextRect;
}); });
@@ -6272,19 +6339,25 @@ var UserSelection = React.memo(function () {
return __assign(__assign({}, currentRect), { draw: false }); return __assign(__assign({}, currentRect), { draw: false });
}); });
} }
selectionPane.current.addEventListener('mousedown', onMouseDown); if (selectionPane.current) {
selectionPane.current.addEventListener('mousemove', onMouseMove); selectionPane.current.addEventListener('mousedown', onMouseDown);
selectionPane.current.addEventListener('mouseup', onMouseUp); selectionPane.current.addEventListener('mousemove', onMouseMove);
return function () { selectionPane.current.addEventListener('mouseup', onMouseUp);
selectionPane.current.removeEventListener('mousedown', onMouseDown); return function () {
selectionPane.current.removeEventListener('mousemove', onMouseMove); if (!selectionPane.current) {
selectionPane.current.removeEventListener('mouseup', onMouseUp); return;
}; }
}, []); selectionPane.current.removeEventListener('mousedown', onMouseDown);
selectionPane.current.removeEventListener('mousemove', onMouseMove);
selectionPane.current.removeEventListener('mouseup', onMouseUp);
};
}
return;
}, [selectionPane.current]);
return (React__default.createElement("div", { className: "react-flow__selectionpane", ref: selectionPane }, rect.draw && (React__default.createElement("div", { className: "react-flow__selection", style: { return (React__default.createElement("div", { className: "react-flow__selectionpane", ref: selectionPane }, rect.draw && (React__default.createElement("div", { className: "react-flow__selection", style: {
width: rect.width, width: rect.width,
height: rect.height, height: rect.height,
transform: "translate(" + rect.x + "px, " + rect.y + "px)" transform: "translate(" + rect.x + "px, " + rect.y + "px)",
} })))); } }))));
}); });
@@ -7955,16 +8028,15 @@ reactDraggable.default = default_1;
reactDraggable.DraggableCore = DraggableCore; reactDraggable.DraggableCore = DraggableCore;
function getStartPositions(elements) { function getStartPositions(elements) {
return elements var startPositions = {};
.filter(isNode) return elements.filter(isNode).reduce(function (res, node) {
.reduce(function (res, node) {
var startPosition = { var startPosition = {
x: node.__rg.position.x || node.position.x, x: node.__rg.position.x || node.position.x,
y: node.__rg.position.y || node.position.x y: node.__rg.position.y || node.position.x,
}; };
res[node.id] = startPosition; res[node.id] = startPosition;
return res; return res;
}, {}); }, startPositions);
} }
var NodesSelection = React.memo(function () { var NodesSelection = React.memo(function () {
var _a = React.useState({ x: 0, y: 0 }), offset = _a[0], setOffset = _a[1]; var _a = React.useState({ x: 0, y: 0 }), offset = _a[0], setOffset = _a[1];
@@ -7972,7 +8044,7 @@ var NodesSelection = React.memo(function () {
var state = useStoreState$1(function (s) { return ({ var state = useStoreState$1(function (s) { return ({
transform: s.transform, transform: s.transform,
selectedNodesBbox: s.selectedNodesBbox, selectedNodesBbox: s.selectedNodesBbox,
selectedElements: s.selectedElements selectedElements: s.selectedElements,
}); }); }); });
var updateNodePos = useStoreActions$1(function (a) { return a.updateNodePos; }); var updateNodePos = useStoreActions$1(function (a) { return a.updateNodePos; });
var _c = state.transform, x = _c[0], y = _c[1], k = _c[2]; var _c = state.transform, x = _c[0], y = _c[1], k = _c[2];
@@ -7980,37 +8052,46 @@ var NodesSelection = React.memo(function () {
var onStart = function (evt) { var onStart = function (evt) {
var scaledClient = { var scaledClient = {
x: evt.clientX * (1 / k), x: evt.clientX * (1 / k),
y: evt.clientY * (1 / k) y: evt.clientY * (1 / k),
}; };
var offsetX = scaledClient.x - position.x - x; var offsetX = scaledClient.x - position.x - x;
var offsetY = scaledClient.y - position.y - y; var offsetY = scaledClient.y - position.y - y;
var startPositions = getStartPositions(state.selectedElements); var nextStartPositions = getStartPositions(state.selectedElements);
setOffset({ x: offsetX, y: offsetY }); if (nextStartPositions) {
setStartPositions(startPositions); setOffset({ x: offsetX, y: offsetY });
setStartPositions(nextStartPositions);
}
}; };
var onDrag = function (evt) { var onDrag = function (evt) {
var scaledClient = { var scaledClient = {
x: evt.clientX * (1 / k), x: evt.clientX * (1 / k),
y: evt.clientY * (1 / k) y: evt.clientY * (1 / k),
}; };
state.selectedElements state.selectedElements.filter(isNode).forEach(function (node) {
.filter(isNode) var pos = {
.forEach(function (node) { x: startPositions[node.id].x +
updateNodePos({ id: node.id, pos: { scaledClient.x -
x: startPositions[node.id].x + scaledClient.x - position.x - offset.x - x, position.x -
y: startPositions[node.id].y + scaledClient.y - position.y - offset.y - y offset.x -
} }); x,
y: startPositions[node.id].y +
scaledClient.y -
position.y -
offset.y -
y,
};
updateNodePos({ id: node.id, pos: pos });
}); });
}; };
return (React__default.createElement("div", { className: "react-flow__nodesselection", style: { return (React__default.createElement("div", { className: "react-flow__nodesselection", style: {
transform: "translate(" + x + "px," + y + "px) scale(" + k + ")" transform: "translate(" + x + "px," + y + "px) scale(" + k + ")",
} }, } },
React__default.createElement(reactDraggable, { scale: k, onStart: function (evt) { return onStart(evt); }, onDrag: function (evt) { return onDrag(evt); } }, React__default.createElement(reactDraggable, { scale: k, onStart: function (evt) { return onStart(evt); }, onDrag: function (evt) { return onDrag(evt); } },
React__default.createElement("div", { className: "react-flow__nodesselection-rect", style: { React__default.createElement("div", { className: "react-flow__nodesselection-rect", style: {
width: state.selectedNodesBbox.width, width: state.selectedNodesBbox.width,
height: state.selectedNodesBbox.height, height: state.selectedNodesBbox.height,
top: state.selectedNodesBbox.y, top: state.selectedNodesBbox.y,
left: state.selectedNodesBbox.x left: state.selectedNodesBbox.x,
} })))); } }))));
}); });
@@ -8039,13 +8120,14 @@ var createGridDots = function (width, height, xOffset, yOffset, gap, size) {
var x = col * gap + xOffset; var x = col * gap + xOffset;
return Array.from({ length: lineCountY }, function (_, row) { return Array.from({ length: lineCountY }, function (_, row) {
var y = row * gap + yOffset; var y = row * gap + yOffset;
return "M" + x + " " + (y - size) + " l" + size + " " + size + " l" + -size + " " + size + " l" + -size + " " + -size + "z"; return "M" + x + " " + (y -
size) + " l" + size + " " + size + " l" + -size + " " + size + " l" + -size + " " + -size + "z";
}).join(' '); }).join(' ');
}); });
return values.join(' '); return values.join(' ');
}; };
var Grid = React.memo(function (_a) { var Grid = React.memo(function (_a) {
var _b = _a.gap, gap = _b === void 0 ? 24 : _b, _c = _a.color, color = _c === void 0 ? '#aaa' : _c, _d = _a.size, size = _d === void 0 ? 0.5 : _d, _e = _a.style, style = _e === void 0 ? {} : _e, _f = _a.className, className = _f === void 0 ? null : _f, _g = _a.backgroundType, backgroundType = _g === void 0 ? GridType.Dots : _g; var _b = _a.gap, gap = _b === void 0 ? 24 : _b, _c = _a.color, color = _c === void 0 ? '#aaa' : _c, _d = _a.size, size = _d === void 0 ? 0.5 : _d, _e = _a.style, style = _e === void 0 ? {} : _e, _f = _a.className, className = _f === void 0 ? '' : _f, _g = _a.backgroundType, backgroundType = _g === void 0 ? GridType.Dots : _g;
var _h = useStoreState$1(function (s) { return s; }), width = _h.width, height = _h.height, _j = _h.transform, x = _j[0], y = _j[1], scale = _j[2]; var _h = useStoreState$1(function (s) { return s; }), width = _h.width, height = _h.height, _j = _h.transform, x = _j[0], y = _j[1], scale = _j[2];
var gridClasses = classnames('react-flow__grid', className); var gridClasses = classnames('react-flow__grid', className);
var scaledGap = gap * scale; var scaledGap = gap * scale;
@@ -8064,11 +8146,11 @@ Grid.displayName = 'Grid';
var isInputDOMNode = function (e) { var isInputDOMNode = function (e) {
var target = e.target; var target = e.target;
return e && target && ['INPUT', 'SELECT', 'TEXTAREA'].includes(target.nodeName); return (e && target && ['INPUT', 'SELECT', 'TEXTAREA'].includes(target.nodeName));
}; };
var getDimensions = function (node) { return ({ var getDimensions = function (node) { return ({
width: node.offsetWidth, width: node.offsetWidth,
height: node.offsetHeight height: node.offsetHeight,
}); }; }); };
var useKeyPress = (function (keyCode) { var useKeyPress = (function (keyCode) {
@@ -8106,8 +8188,10 @@ var useD3Zoom = (function (zoomPane, onMove, shiftPressed) {
var initD3 = useStoreActions$1(function (actions) { return actions.initD3; }); var initD3 = useStoreActions$1(function (actions) { return actions.initD3; });
var updateTransform = useStoreActions$1(function (actions) { return actions.updateTransform; }); var updateTransform = useStoreActions$1(function (actions) { return actions.updateTransform; });
React.useEffect(function () { React.useEffect(function () {
var selection = select(zoomPane.current).call(d3ZoomInstance); if (zoomPane.current) {
initD3({ zoom: d3ZoomInstance, selection: selection }); var selection = select(zoomPane.current).call(d3ZoomInstance);
initD3({ zoom: d3ZoomInstance, selection: selection });
}
}, []); }, []);
React.useEffect(function () { React.useEffect(function () {
if (shiftPressed) { if (shiftPressed) {
@@ -8115,13 +8199,14 @@ var useD3Zoom = (function (zoomPane, onMove, shiftPressed) {
} }
else { else {
d3ZoomInstance.on('zoom', function () { d3ZoomInstance.on('zoom', function () {
if (event.sourceEvent && event.sourceEvent.target !== zoomPane.current) { if (event.sourceEvent &&
return false; event.sourceEvent.target !== zoomPane.current) {
return;
} }
updateTransform(event.transform); updateTransform(event.transform);
onMove(); onMove();
}); });
if (state.d3Selection) { if (state.d3Selection && state.d3Zoom) {
// we need to restore the graph transform otherwise d3 zoom transform and graph transform are not synced // we need to restore the graph transform otherwise d3 zoom transform and graph transform are not synced
var graphTransform = identity$1 var graphTransform = identity$1
.translate(state.transform[0], state.transform[1]) .translate(state.transform[0], state.transform[1])
@@ -8137,14 +8222,18 @@ var useD3Zoom = (function (zoomPane, onMove, shiftPressed) {
var useGlobalKeyHandler = (function (_a) { var useGlobalKeyHandler = (function (_a) {
var deleteKeyCode = _a.deleteKeyCode, onElementsRemove = _a.onElementsRemove; var deleteKeyCode = _a.deleteKeyCode, onElementsRemove = _a.onElementsRemove;
var state = useStoreState$1(function (s) { return ({ selectedElements: s.selectedElements, edges: s.edges }); }); var state = useStoreState$1(function (s) { return ({
selectedElements: s.selectedElements,
edges: s.edges,
}); });
var setNodesSelection = useStoreActions$1(function (a) { return a.setNodesSelection; }); var setNodesSelection = useStoreActions$1(function (a) { return a.setNodesSelection; });
var deleteKeyPressed = useKeyPress(deleteKeyCode); var deleteKeyPressed = useKeyPress(deleteKeyCode);
React.useEffect(function () { React.useEffect(function () {
if (deleteKeyPressed && state.selectedElements.length) { if (deleteKeyPressed && state.selectedElements.length) {
var elementsToRemove = state.selectedElements; var elementsToRemove = state.selectedElements;
// we also want to remove the edges if only one node is selected // we also want to remove the edges if only one node is selected
if (state.selectedElements.length === 1 && !isEdge(state.selectedElements[0])) { if (state.selectedElements.length === 1 &&
!isEdge(state.selectedElements[0])) {
var node = state.selectedElements[0]; var node = state.selectedElements[0];
var connectedEdges = getConnectedEdges([node], state.edges); var connectedEdges = getConnectedEdges([node], state.edges);
elementsToRemove = __spreadArrays(state.selectedElements, connectedEdges); elementsToRemove = __spreadArrays(state.selectedElements, connectedEdges);
@@ -8153,14 +8242,13 @@ var useGlobalKeyHandler = (function (_a) {
setNodesSelection({ isActive: false }); setNodesSelection({ isActive: false });
} }
}, [deleteKeyPressed]); }, [deleteKeyPressed]);
return null;
}); });
var useElementUpdater = function (elements) { var useElementUpdater = function (elements) {
var state = useStoreState$1(function (s) { return ({ var state = useStoreState$1(function (s) { return ({
nodes: s.nodes, nodes: s.nodes,
edges: s.edges, edges: s.edges,
transform: s.transform transform: s.transform,
}); }); }); });
var setNodes = useStoreActions$1(function (a) { return a.setNodes; }); var setNodes = useStoreActions$1(function (a) { return a.setNodes; });
var setEdges = useStoreActions$1(function (a) { return a.setEdges; }); var setEdges = useStoreActions$1(function (a) { return a.setEdges; });
@@ -8170,7 +8258,8 @@ var useElementUpdater = function (elements) {
var nextNodes = nodes.map(function (propNode) { var nextNodes = nodes.map(function (propNode) {
var existingNode = state.nodes.find(function (n) { return n.id === propNode.id; }); var existingNode = state.nodes.find(function (n) { return n.id === propNode.id; });
if (existingNode) { if (existingNode) {
var data = !fastDeepEqual(existingNode.data, propNode.data) ? __assign(__assign({}, existingNode.data), propNode.data) : existingNode.data; var data = !fastDeepEqual(existingNode.data, propNode.data)
? __assign(__assign({}, existingNode.data), propNode.data) : existingNode.data;
return __assign(__assign({}, existingNode), { data: data }); return __assign(__assign({}, existingNode), { data: data });
} }
return parseElement(propNode, state.transform); return parseElement(propNode, state.transform);
@@ -8184,11 +8273,10 @@ var useElementUpdater = function (elements) {
setEdges(edges); setEdges(edges);
} }
}); });
return null;
}; };
var GraphView = React.memo(function (_a) { var GraphView = React.memo(function (_a) {
var nodeTypes = _a.nodeTypes, edgeTypes = _a.edgeTypes, onMove = _a.onMove, onLoad = _a.onLoad, onElementClick = _a.onElementClick, onNodeDragStop = _a.onNodeDragStop, connectionLineType = _a.connectionLineType, connectionLineStyle = _a.connectionLineStyle, selectionKeyCode = _a.selectionKeyCode, onElementsRemove = _a.onElementsRemove, deleteKeyCode = _a.deleteKeyCode, elements = _a.elements, showBackground = _a.showBackground, backgroundGap = _a.backgroundGap, backgroundColor = _a.backgroundColor, backgroundType = _a.backgroundType, onConnect = _a.onConnect; var nodeTypes = _a.nodeTypes, edgeTypes = _a.edgeTypes, onMove = _a.onMove, onLoad = _a.onLoad, onElementClick = _a.onElementClick, onNodeDragStop = _a.onNodeDragStop, connectionLineType = _a.connectionLineType, connectionLineStyle = _a.connectionLineStyle, selectionKeyCode = _a.selectionKeyCode, onElementsRemove = _a.onElementsRemove, deleteKeyCode = _a.deleteKeyCode, elements = _a.elements, showBackground = _a.showBackground, backgroundGap = _a.backgroundGap, backgroundColor = _a.backgroundColor, backgroundType = _a.backgroundType, onConnect = _a.onConnect, snapToGrid = _a.snapToGrid, snapGrid = _a.snapGrid;
var zoomPane = React.useRef(null); var zoomPane = React.useRef(null);
var rendererNode = React.useRef(null); var rendererNode = React.useRef(null);
var state = useStoreState$1(function (s) { return ({ var state = useStoreState$1(function (s) { return ({
@@ -8197,14 +8285,18 @@ var GraphView = React.memo(function (_a) {
nodes: s.nodes, nodes: s.nodes,
edges: s.edges, edges: s.edges,
d3Initialised: s.d3Initialised, d3Initialised: s.d3Initialised,
nodesSelectionActive: s.nodesSelectionActive nodesSelectionActive: s.nodesSelectionActive,
}); }); }); });
var updateSize = useStoreActions$1(function (actions) { return actions.updateSize; }); var updateSize = useStoreActions$1(function (actions) { return actions.updateSize; });
var setNodesSelection = useStoreActions$1(function (actions) { return actions.setNodesSelection; }); var setNodesSelection = useStoreActions$1(function (actions) { return actions.setNodesSelection; });
var setOnConnect = useStoreActions$1(function (a) { return a.setOnConnect; }); var setOnConnect = useStoreActions$1(function (a) { return a.setOnConnect; });
var setSnapGrid = useStoreActions$1(function (actions) { return actions.setSnapGrid; });
var selectionKeyPressed = useKeyPress(selectionKeyCode); var selectionKeyPressed = useKeyPress(selectionKeyCode);
var onZoomPaneClick = function () { return setNodesSelection({ isActive: false }); }; var onZoomPaneClick = function () { return setNodesSelection({ isActive: false }); };
var updateDimensions = function () { var updateDimensions = function () {
if (!rendererNode.current) {
return;
}
var size = getDimensions(rendererNode.current); var size = getDimensions(rendererNode.current);
updateSize(size); updateSize(size);
}; };
@@ -8222,10 +8314,13 @@ var GraphView = React.memo(function (_a) {
onLoad({ onLoad({
fitView: fitView, fitView: fitView,
zoomIn: zoomIn, zoomIn: zoomIn,
zoomOut: zoomOut zoomOut: zoomOut,
}); });
} }
}, [state.d3Initialised]); }, [state.d3Initialised]);
React.useEffect(function () {
setSnapGrid({ snapToGrid: snapToGrid, snapGrid: snapGrid });
}, [snapToGrid]);
useGlobalKeyHandler({ onElementsRemove: onElementsRemove, deleteKeyCode: deleteKeyCode }); useGlobalKeyHandler({ onElementsRemove: onElementsRemove, deleteKeyCode: deleteKeyCode });
useElementUpdater(elements); useElementUpdater(elements);
return (React__default.createElement("div", { className: "react-flow__renderer", ref: rendererNode }, return (React__default.createElement("div", { className: "react-flow__renderer", ref: rendererNode },
@@ -8241,10 +8336,10 @@ GraphView.displayName = 'GraphView';
function onMouseDown(evt, nodeId, setSourceId, setPosition, onConnect, isTarget, isValidConnection) { function onMouseDown(evt, nodeId, setSourceId, setPosition, onConnect, isTarget, isValidConnection) {
var reactFlowNode = document.querySelector('.react-flow'); var reactFlowNode = document.querySelector('.react-flow');
if (!reactFlowNode) { if (!reactFlowNode) {
return null; return;
} }
var containerBounds = reactFlowNode.getBoundingClientRect(); var containerBounds = reactFlowNode.getBoundingClientRect();
var recentHoveredHandle = null; var recentHoveredHandle;
setPosition({ setPosition({
x: evt.clientX - containerBounds.left, x: evt.clientX - containerBounds.left,
y: evt.clientY - containerBounds.top, y: evt.clientY - containerBounds.top,
@@ -8252,7 +8347,7 @@ function onMouseDown(evt, nodeId, setSourceId, setPosition, onConnect, isTarget,
setSourceId(nodeId); setSourceId(nodeId);
function resetRecentHandle() { function resetRecentHandle() {
if (!recentHoveredHandle) { if (!recentHoveredHandle) {
return false; return;
} }
recentHoveredHandle.classList.remove('valid'); recentHoveredHandle.classList.remove('valid');
recentHoveredHandle.classList.remove('connecting'); recentHoveredHandle.classList.remove('connecting');
@@ -8264,9 +8359,11 @@ function onMouseDown(evt, nodeId, setSourceId, setPosition, onConnect, isTarget,
elementBelow: elementBelow, elementBelow: elementBelow,
isValid: false, isValid: false,
connection: { source: null, target: null }, connection: { source: null, target: null },
isHoveringHandle: false isHoveringHandle: false,
}; };
if (elementBelow && (elementBelow.classList.contains('target') || elementBelow.classList.contains('source'))) { if (elementBelow &&
(elementBelow.classList.contains('target') ||
elementBelow.classList.contains('source'))) {
var connection = { source: null, target: null }; var connection = { source: null, target: null };
if (isTarget) { if (isTarget) {
var sourceId = elementBelow.getAttribute('data-nodeid'); var sourceId = elementBelow.getAttribute('data-nodeid');
@@ -8293,7 +8390,7 @@ function onMouseDown(evt, nodeId, setSourceId, setPosition, onConnect, isTarget,
return resetRecentHandle(); return resetRecentHandle();
} }
var isOwnHandle = connection.source === connection.target; var isOwnHandle = connection.source === connection.target;
if (!isOwnHandle) { if (!isOwnHandle && elementBelow) {
recentHoveredHandle = elementBelow; recentHoveredHandle = elementBelow;
elementBelow.classList.add('connecting'); elementBelow.classList.add('connecting');
elementBelow.classList.toggle('valid', isValid); elementBelow.classList.toggle('valid', isValid);
@@ -8315,9 +8412,14 @@ function onMouseDown(evt, nodeId, setSourceId, setPosition, onConnect, isTarget,
var BaseHandle = React.memo(function (_a) { var BaseHandle = React.memo(function (_a) {
var type = _a.type, nodeId = _a.nodeId, onConnect = _a.onConnect, position = _a.position, setSourceId = _a.setSourceId, setPosition = _a.setPosition, className = _a.className, _b = _a.id, id = _b === void 0 ? false : _b, isValidConnection = _a.isValidConnection, rest = __rest(_a, ["type", "nodeId", "onConnect", "position", "setSourceId", "setPosition", "className", "id", "isValidConnection"]); var type = _a.type, nodeId = _a.nodeId, onConnect = _a.onConnect, position = _a.position, setSourceId = _a.setSourceId, setPosition = _a.setPosition, className = _a.className, _b = _a.id, id = _b === void 0 ? false : _b, isValidConnection = _a.isValidConnection, rest = __rest(_a, ["type", "nodeId", "onConnect", "position", "setSourceId", "setPosition", "className", "id", "isValidConnection"]);
var isTarget = type === 'target'; var isTarget = type === 'target';
var handleClasses = classnames('react-flow__handle', className, position, { source: !isTarget, target: isTarget }); var handleClasses = classnames('react-flow__handle', className, position, {
source: !isTarget,
target: isTarget,
});
var nodeIdWithHandleId = id ? nodeId + "__" + id : nodeId; var nodeIdWithHandleId = id ? nodeId + "__" + id : nodeId;
return (React__default.createElement("div", __assign({ "data-nodeid": nodeIdWithHandleId, "data-handlepos": position, className: handleClasses, onMouseDown: function (evt) { return onMouseDown(evt, nodeIdWithHandleId, setSourceId, setPosition, onConnect, isTarget, isValidConnection); } }, rest))); return (React__default.createElement("div", __assign({ "data-nodeid": nodeIdWithHandleId, "data-handlepos": position, className: handleClasses, onMouseDown: function (evt) {
return onMouseDown(evt, nodeIdWithHandleId, setSourceId, setPosition, onConnect, isTarget, isValidConnection);
} }, rest)));
}); });
BaseHandle.displayName = 'BaseHandle'; BaseHandle.displayName = 'BaseHandle';
@@ -8330,7 +8432,7 @@ var Handle = React.memo(function (_a) {
var nodeId = React.useContext(NodeIdContext); var nodeId = React.useContext(NodeIdContext);
var _f = useStoreActions$1(function (a) { return ({ var _f = useStoreActions$1(function (a) { return ({
setPosition: a.setConnectionPosition, setPosition: a.setConnectionPosition,
setSourceId: a.setConnectionSourceId setSourceId: a.setConnectionSourceId,
}); }), setPosition = _f.setPosition, setSourceId = _f.setSourceId; }); }), setPosition = _f.setPosition, setSourceId = _f.setSourceId;
var onConnectAction = useStoreState$1(function (s) { return s.onConnect; }); var onConnectAction = useStoreState$1(function (s) { return s.onConnect; });
var onConnectExtended = function (params) { var onConnectExtended = function (params) {
@@ -8345,7 +8447,7 @@ var nodeStyles = {
background: '#ff6060', background: '#ff6060',
padding: 10, padding: 10,
borderRadius: 5, borderRadius: 5,
width: 150 width: 150,
}; };
var DefaultNode = (function (_a) { var DefaultNode = (function (_a) {
var data = _a.data, style = _a.style; var data = _a.data, style = _a.style;
@@ -8359,7 +8461,7 @@ var nodeStyles$1 = {
background: '#9999ff', background: '#9999ff',
padding: 10, padding: 10,
borderRadius: 5, borderRadius: 5,
width: 150 width: 150,
}; };
var InputNode = (function (_a) { var InputNode = (function (_a) {
var data = _a.data, style = _a.style; var data = _a.data, style = _a.style;
@@ -8372,7 +8474,7 @@ var nodeStyles$2 = {
background: '#55dd99', background: '#55dd99',
padding: 10, padding: 10,
borderRadius: 5, borderRadius: 5,
width: 150 width: 150,
}; };
var OutputNode = (function (_a) { var OutputNode = (function (_a) {
var data = _a.data, style = _a.style; var data = _a.data, style = _a.style;
@@ -8649,15 +8751,18 @@ var getHandleBounds = function (selector, nodeElement, parentBounds, k) {
if (!handles || !handles.length) { if (!handles || !handles.length) {
return null; return null;
} }
return [].map.call(handles, function (handle) { var handlesArray = Array.from(handles);
return handlesArray.map(function (handle) {
var bounds = handle.getBoundingClientRect(); var bounds = handle.getBoundingClientRect();
var dimensions = getDimensions(handle); var dimensions = getDimensions(handle);
var nodeIdAttr = handle.getAttribute('data-nodeid'); var nodeIdAttr = handle.getAttribute('data-nodeid');
var handlePosition = handle.getAttribute('data-handlepos'); var handlePosition = handle.getAttribute('data-handlepos');
var nodeIdSplitted = nodeIdAttr.split('__'); var nodeIdSplitted = nodeIdAttr ? nodeIdAttr.split('__') : null;
var handleId = null; var handleId = null;
if (nodeIdSplitted) { if (nodeIdSplitted) {
handleId = (nodeIdSplitted.length ? nodeIdSplitted[1] : nodeIdSplitted); handleId = (nodeIdSplitted.length
? nodeIdSplitted[1]
: nodeIdSplitted);
} }
return __assign({ id: handleId, position: handlePosition, x: (bounds.left - parentBounds.left) * (1 / k), y: (bounds.top - parentBounds.top) * (1 / k) }, dimensions); return __assign({ id: handleId, position: handlePosition, x: (bounds.left - parentBounds.left) * (1 / k), y: (bounds.top - parentBounds.top) * (1 / k) }, dimensions);
}); });
@@ -8668,7 +8773,7 @@ var onStart = function (evt, onClick, id, type, data, setOffset, transform, posi
} }
var scaledClient = { var scaledClient = {
x: evt.clientX * (1 / transform[2]), x: evt.clientX * (1 / transform[2]),
y: evt.clientY * (1 / transform[2]) y: evt.clientY * (1 / transform[2]),
}; };
var offsetX = scaledClient.x - position.x - transform[0]; var offsetX = scaledClient.x - position.x - transform[0];
var offsetY = scaledClient.y - position.y - transform[1]; var offsetY = scaledClient.y - position.y - transform[1];
@@ -8680,19 +8785,25 @@ var onStart = function (evt, onClick, id, type, data, setOffset, transform, posi
var onDrag = function (evt, setDragging, id, offset, transform) { var onDrag = function (evt, setDragging, id, offset, transform) {
var scaledClient = { var scaledClient = {
x: evt.clientX * (1 / transform[2]), x: evt.clientX * (1 / transform[2]),
y: evt.clientY * (1 / transform[2]) y: evt.clientY * (1 / transform[2]),
}; };
setDragging(true); setDragging(true);
store.dispatch.updateNodePos({ id: id, pos: { store.dispatch.updateNodePos({
id: id,
pos: {
x: scaledClient.x - transform[0] - offset.x, x: scaledClient.x - transform[0] - offset.x,
y: scaledClient.y - transform[1] - offset.y y: scaledClient.y - transform[1] - offset.y,
} }); },
});
}; };
var onStop = function (onNodeDragStop, isDragging, setDragging, id, type, position, data) { var onStop = function (onNodeDragStop, isDragging, setDragging, id, type, position, data) {
if (isDragging) { if (isDragging) {
setDragging(false); setDragging(false);
onNodeDragStop({ onNodeDragStop({
id: id, type: type, position: position, data: data id: id,
type: type,
position: position,
data: data,
}); });
} }
}; };
@@ -8704,17 +8815,20 @@ var wrapNode = (function (NodeComponent) {
var _c = React.useState(false), isDragging = _c[0], setDragging = _c[1]; var _c = React.useState(false), isDragging = _c[0], setDragging = _c[1];
var position = { x: xPos, y: yPos }; var position = { x: xPos, y: yPos };
var nodeClasses = classnames('react-flow__node', { selected: selected }); var nodeClasses = classnames('react-flow__node', { selected: selected });
var nodeStyle = { zIndex: selected ? 10 : 3, transform: "translate(" + xPos + "px," + yPos + "px)" }; var nodeStyle = {
zIndex: selected ? 10 : 3,
transform: "translate(" + xPos + "px," + yPos + "px)",
};
var updateNode = function () { var updateNode = function () {
if (!nodeElement.current) { if (!nodeElement.current) {
return false; return;
} }
var storeState = store.getState(); var storeState = store.getState();
var bounds = nodeElement.current.getBoundingClientRect(); var bounds = nodeElement.current.getBoundingClientRect();
var dimensions = getDimensions(nodeElement.current); var dimensions = getDimensions(nodeElement.current);
var handleBounds = { var handleBounds = {
source: getHandleBounds('.source', nodeElement.current, bounds, storeState.transform[2]), source: getHandleBounds('.source', nodeElement.current, bounds, storeState.transform[2]),
target: getHandleBounds('.target', nodeElement.current, bounds, storeState.transform[2]) target: getHandleBounds('.target', nodeElement.current, bounds, storeState.transform[2]),
}; };
store.dispatch.updateNodeData(__assign(__assign({ id: id }, dimensions), { handleBounds: handleBounds })); store.dispatch.updateNodeData(__assign(__assign({ id: id }, dimensions), { handleBounds: handleBounds }));
}; };
@@ -8734,8 +8848,15 @@ var wrapNode = (function (NodeComponent) {
} }
}; };
} }
return;
}, [nodeElement.current]); }, [nodeElement.current]);
return (React__default.createElement(DraggableCore, { onStart: function (evt) { return onStart(evt, onClick, id, type, data, setOffset, transform, position); }, onDrag: function (evt) { return onDrag(evt, setDragging, id, offset, transform); }, onStop: function () { return onStop(onNodeDragStop, isDragging, setDragging, id, type, position, data); }, scale: transform[2] }, return (React__default.createElement(DraggableCore, { onStart: function (evt) {
return onStart(evt, onClick, id, type, data, setOffset, transform, position);
}, onDrag: function (evt) {
return onDrag(evt, setDragging, id, offset, transform);
}, onStop: function () {
return onStop(onNodeDragStop, isDragging, setDragging, id, type, position, data);
}, scale: transform[2] },
React__default.createElement("div", { className: nodeClasses, ref: nodeElement, style: nodeStyle }, React__default.createElement("div", { className: nodeClasses, ref: nodeElement, style: nodeStyle },
React__default.createElement(Provider, { value: id }, React__default.createElement(Provider, { value: id },
React__default.createElement(NodeComponent, { id: id, data: data, type: type, style: style, selected: selected }))))); React__default.createElement(NodeComponent, { id: id, data: data, type: type, style: style, selected: selected })))));
@@ -8748,15 +8869,15 @@ function createNodeTypes(nodeTypes) {
var standardTypes = { var standardTypes = {
input: wrapNode((nodeTypes.input || InputNode)), input: wrapNode((nodeTypes.input || InputNode)),
default: wrapNode((nodeTypes.default || DefaultNode)), default: wrapNode((nodeTypes.default || DefaultNode)),
output: wrapNode((nodeTypes.output || OutputNode)) output: wrapNode((nodeTypes.output || OutputNode)),
}; };
var specialTypes = Object var wrappedTypes = {};
.keys(nodeTypes) var specialTypes = Object.keys(nodeTypes)
.filter(function (k) { return !['input', 'default', 'output'].includes(k); }) .filter(function (k) { return !['input', 'default', 'output'].includes(k); })
.reduce(function (res, key) { .reduce(function (res, key) {
res[key] = wrapNode((nodeTypes[key] || DefaultNode)); res[key] = wrapNode((nodeTypes[key] || DefaultNode));
return res; return res;
}, {}); }, wrappedTypes);
return __assign(__assign({}, standardTypes), specialTypes); return __assign(__assign({}, standardTypes), specialTypes);
} }
@@ -8765,15 +8886,17 @@ var BezierEdge = React.memo(function (_a) {
var yOffset = Math.abs(targetY - sourceY) / 2; var yOffset = Math.abs(targetY - sourceY) / 2;
var centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset; var centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset;
var dAttr = "M" + sourceX + "," + sourceY + " C" + sourceX + "," + centerY + " " + targetX + "," + centerY + " " + targetX + "," + targetY; var dAttr = "M" + sourceX + "," + sourceY + " C" + sourceX + "," + centerY + " " + targetX + "," + centerY + " " + targetX + "," + targetY;
if (['left', 'right'].includes(sourcePosition) && ['left', 'right'].includes(targetPosition)) { if (['left', 'right'].includes(sourcePosition) &&
['left', 'right'].includes(targetPosition)) {
var xOffset = Math.abs(targetX - sourceX) / 2; var xOffset = Math.abs(targetX - sourceX) / 2;
var centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset; var centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset;
dAttr = "M" + sourceX + "," + sourceY + " C" + centerX + "," + sourceY + " " + centerX + "," + targetY + " " + targetX + "," + targetY; dAttr = "M" + sourceX + "," + sourceY + " C" + centerX + "," + sourceY + " " + centerX + "," + targetY + " " + targetX + "," + targetY;
} }
else if (['left', 'right'].includes(sourcePosition) || ['left', 'right'].includes(targetPosition)) { else if (['left', 'right'].includes(sourcePosition) ||
['left', 'right'].includes(targetPosition)) {
dAttr = "M" + sourceX + "," + sourceY + " C" + sourceX + "," + targetY + " " + sourceX + "," + targetY + " " + targetX + "," + targetY; dAttr = "M" + sourceX + "," + sourceY + " C" + sourceX + "," + targetY + " " + sourceX + "," + targetY + " " + targetX + "," + targetY;
} }
return (React__default.createElement("path", __assign({}, style, { d: dAttr }))); return React__default.createElement("path", __assign({}, style, { d: dAttr }));
}); });
var StraightEdge = React.memo(function (_a) { var StraightEdge = React.memo(function (_a) {
@@ -8794,7 +8917,7 @@ var wrapEdge = (function (EdgeComponent) {
var edgeClasses = classnames('react-flow__edge', { selected: selected, animated: animated }); var edgeClasses = classnames('react-flow__edge', { selected: selected, animated: animated });
var onEdgeClick = function (evt) { var onEdgeClick = function (evt) {
if (isInputDOMNode(evt)) { if (isInputDOMNode(evt)) {
return false; return;
} }
store.dispatch.setSelectedElements({ id: id, source: source, target: target }); store.dispatch.setSelectedElements({ id: id, source: source, target: target });
onClick({ id: id, source: source, target: target, type: type }); onClick({ id: id, source: source, target: target, type: type });
@@ -8809,15 +8932,15 @@ var wrapEdge = (function (EdgeComponent) {
function createEdgeTypes(edgeTypes) { function createEdgeTypes(edgeTypes) {
var standardTypes = { var standardTypes = {
default: wrapEdge((edgeTypes.default || BezierEdge)), default: wrapEdge((edgeTypes.default || BezierEdge)),
straight: wrapEdge((edgeTypes.bezier || StraightEdge)) straight: wrapEdge((edgeTypes.bezier || StraightEdge)),
}; };
var specialTypes = Object var wrappedTypes = {};
.keys(edgeTypes) var specialTypes = Object.keys(edgeTypes)
.filter(function (k) { return !['default', 'bezier'].includes(k); }) .filter(function (k) { return !['default', 'bezier'].includes(k); })
.reduce(function (res, key) { .reduce(function (res, key) {
res[key] = wrapEdge((edgeTypes[key] || BezierEdge)); res[key] = wrapEdge((edgeTypes[key] || BezierEdge));
return res; return res;
}, {}); }, wrappedTypes);
return __assign(__assign({}, standardTypes), specialTypes); return __assign(__assign({}, standardTypes), specialTypes);
} }
@@ -8852,12 +8975,12 @@ var css = ".react-flow {\n width: 100%;\n height: 100%;\n position: relative;
styleInject(css); styleInject(css);
var ReactFlow = function (_a) { var ReactFlow = function (_a) {
var style = _a.style, onElementClick = _a.onElementClick, elements = _a.elements, children = _a.children, nodeTypes = _a.nodeTypes, edgeTypes = _a.edgeTypes, onLoad = _a.onLoad, onMove = _a.onMove, onElementsRemove = _a.onElementsRemove, onConnect = _a.onConnect, onNodeDragStop = _a.onNodeDragStop, connectionLineType = _a.connectionLineType, connectionLineStyle = _a.connectionLineStyle, deleteKeyCode = _a.deleteKeyCode, selectionKeyCode = _a.selectionKeyCode, showBackground = _a.showBackground, backgroundGap = _a.backgroundGap, backgroundType = _a.backgroundType, backgroundColor = _a.backgroundColor; var style = _a.style, onElementClick = _a.onElementClick, elements = _a.elements, children = _a.children, nodeTypes = _a.nodeTypes, edgeTypes = _a.edgeTypes, onLoad = _a.onLoad, onMove = _a.onMove, onElementsRemove = _a.onElementsRemove, onConnect = _a.onConnect, onNodeDragStop = _a.onNodeDragStop, connectionLineType = _a.connectionLineType, connectionLineStyle = _a.connectionLineStyle, deleteKeyCode = _a.deleteKeyCode, selectionKeyCode = _a.selectionKeyCode, showBackground = _a.showBackground, backgroundGap = _a.backgroundGap, backgroundType = _a.backgroundType, backgroundColor = _a.backgroundColor, snapToGrid = _a.snapToGrid, snapGrid = _a.snapGrid;
var nodeTypesParsed = React.useMemo(function () { return createNodeTypes(nodeTypes); }, []); var nodeTypesParsed = React.useMemo(function () { return createNodeTypes(nodeTypes); }, []);
var edgeTypesParsed = React.useMemo(function () { return createEdgeTypes(edgeTypes); }, []); var edgeTypesParsed = React.useMemo(function () { return createEdgeTypes(edgeTypes); }, []);
return (React__default.createElement("div", { style: style, className: "react-flow" }, return (React__default.createElement("div", { style: style, className: "react-flow" },
React__default.createElement(StoreProvider, { store: store }, React__default.createElement(StoreProvider, { store: store },
React__default.createElement(GraphView, { onLoad: onLoad, onMove: onMove, onElementClick: onElementClick, onNodeDragStop: onNodeDragStop, nodeTypes: nodeTypesParsed, edgeTypes: edgeTypesParsed, connectionLineType: connectionLineType, connectionLineStyle: connectionLineStyle, selectionKeyCode: selectionKeyCode, onElementsRemove: onElementsRemove, deleteKeyCode: deleteKeyCode, elements: elements, onConnect: onConnect, backgroundColor: backgroundColor, backgroundGap: backgroundGap, showBackground: showBackground, backgroundType: backgroundType }), React__default.createElement(GraphView, { onLoad: onLoad, onMove: onMove, onElementClick: onElementClick, onNodeDragStop: onNodeDragStop, nodeTypes: nodeTypesParsed, edgeTypes: edgeTypesParsed, connectionLineType: connectionLineType, connectionLineStyle: connectionLineStyle, selectionKeyCode: selectionKeyCode, onElementsRemove: onElementsRemove, deleteKeyCode: deleteKeyCode, elements: elements, onConnect: onConnect, backgroundColor: backgroundColor, backgroundGap: backgroundGap, showBackground: showBackground, backgroundType: backgroundType, snapToGrid: snapToGrid, snapGrid: snapGrid }),
children))); children)));
}; };
ReactFlow.displayName = 'ReactFlow'; ReactFlow.displayName = 'ReactFlow';
@@ -8871,12 +8994,12 @@ ReactFlow.defaultProps = {
nodeTypes: { nodeTypes: {
input: InputNode, input: InputNode,
default: DefaultNode, default: DefaultNode,
output: OutputNode output: OutputNode,
}, },
edgeTypes: { edgeTypes: {
default: BezierEdge, default: BezierEdge,
straight: StraightEdge, straight: StraightEdge,
step: StepEdge step: StepEdge,
}, },
connectionLineType: 'bezier', connectionLineType: 'bezier',
connectionLineStyle: {}, connectionLineStyle: {},
@@ -8885,7 +9008,9 @@ ReactFlow.defaultProps = {
backgroundColor: '#eee', backgroundColor: '#eee',
backgroundGap: 24, backgroundGap: 24,
showBackground: true, showBackground: true,
backgroundType: GridType.Dots backgroundType: GridType.Dots,
snapToGrid: false,
snapGrid: [16, 16],
}; };
var baseStyle = { var baseStyle = {
@@ -8893,7 +9018,7 @@ var baseStyle = {
zIndex: 5, zIndex: 5,
bottom: 10, bottom: 10,
right: 10, right: 10,
width: 200 width: 200,
}; };
var index = (function (_a) { var index = (function (_a) {
var _b = _a.style, style = _b === void 0 ? {} : _b, className = _a.className, _c = _a.bgColor, bgColor = _c === void 0 ? '#f8f8f8' : _c, _d = _a.nodeColor, nodeColor = _d === void 0 ? '#ddd' : _d; var _b = _a.style, style = _b === void 0 ? {} : _b, className = _a.className, _c = _a.bgColor, bgColor = _c === void 0 ? '#f8f8f8' : _c, _d = _a.nodeColor, nodeColor = _d === void 0 ? '#ddd' : _d;
@@ -8910,23 +9035,29 @@ var index = (function (_a) {
var height = (state.height / (state.width || 1)) * width; var height = (state.height / (state.width || 1)) * width;
var bbox = { x: 0, y: 0, width: state.width, height: state.height }; var bbox = { x: 0, y: 0, width: state.width, height: state.height };
var scaleFactor = width / state.width; var scaleFactor = width / state.width;
var nodeColorFunc = (nodeColor instanceof Function ? nodeColor : function () { return nodeColor; }); var nodeColorFunc = (nodeColor instanceof Function
? nodeColor
: function () { return nodeColor; });
React.useEffect(function () { React.useEffect(function () {
if (canvasNode && canvasNode.current) { if (!canvasNode || !canvasNode.current) {
var ctx_1 = canvasNode.current.getContext('2d'); return;
var nodesInside = getNodesInside(state.nodes, bbox, state.transform, true);
ctx_1.fillStyle = bgColor;
ctx_1.fillRect(0, 0, width, height);
nodesInside.forEach(function (n) {
var pos = n.__rg.position;
var transformX = state.transform[0];
var transformY = state.transform[1];
var x = (pos.x * state.transform[2]) + transformX;
var y = (pos.y * state.transform[2]) + transformY;
ctx_1.fillStyle = nodeColorFunc(n);
ctx_1.fillRect((x * scaleFactor), (y * scaleFactor), n.__rg.width * scaleFactor * state.transform[2], n.__rg.height * scaleFactor * state.transform[2]);
});
} }
var ctx = canvasNode.current.getContext('2d');
if (!ctx) {
return;
}
var nodesInside = getNodesInside(state.nodes, bbox, state.transform, true);
ctx.fillStyle = bgColor;
ctx.fillRect(0, 0, width, height);
nodesInside.forEach(function (n) {
var pos = n.__rg.position;
var transformX = state.transform[0];
var transformY = state.transform[1];
var x = pos.x * state.transform[2] + transformX;
var y = pos.y * state.transform[2] + transformY;
ctx.fillStyle = nodeColorFunc(n);
ctx.fillRect(x * scaleFactor, y * scaleFactor, n.__rg.width * scaleFactor * state.transform[2], n.__rg.height * scaleFactor * state.transform[2]);
});
}, [canvasNode.current, nodePositions, state.transform, height]); }, [canvasNode.current, nodePositions, state.transform, height]);
return (React__default.createElement("canvas", { style: __assign(__assign(__assign({}, baseStyle), style), { height: height }), width: width, height: height, className: mapClasses, ref: canvasNode })); return (React__default.createElement("canvas", { style: __assign(__assign(__assign({}, baseStyle), style), { height: height }), width: width, height: height, className: mapClasses, ref: canvasNode }));
}); });
+1 -1
View File
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
import React, { HTMLAttributes } from 'react';
import { GridType } from '../../types';
interface GridProps extends HTMLAttributes<SVGElement> {
backgroundType?: GridType;
gap?: number;
color?: string;
size?: number;
}
declare const Grid: React.MemoExoticComponent<({ gap, color, size, style, className, backgroundType, }: GridProps) => JSX.Element>;
export default Grid;
+14
View File
@@ -0,0 +1,14 @@
import { SVGAttributes } from 'react';
import { ElementId, Node, Transform } from '../../types';
interface ConnectionLineProps {
connectionSourceId: ElementId;
connectionPositionX: number;
connectionPositionY: number;
connectionLineType?: string | null;
nodes: Node[];
transform: Transform;
connectionLineStyle?: SVGAttributes<{}>;
className?: string;
}
declare const _default: ({ connectionSourceId, connectionLineStyle, connectionPositionX, connectionPositionY, connectionLineType, nodes, className, transform, }: ConnectionLineProps) => JSX.Element | null;
export default _default;
+4
View File
@@ -0,0 +1,4 @@
import React from 'react';
import { EdgeBezierProps } from '../../types';
declare const _default: React.MemoExoticComponent<({ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, style, }: EdgeBezierProps) => JSX.Element>;
export default _default;
+4
View File
@@ -0,0 +1,4 @@
import React from 'react';
import { EdgeProps } from '../../types';
declare const _default: React.MemoExoticComponent<({ sourceX, sourceY, targetX, targetY, style }: EdgeProps) => JSX.Element>;
export default _default;
+4
View File
@@ -0,0 +1,4 @@
import React from 'react';
import { EdgeProps } from '../../types';
declare const _default: React.MemoExoticComponent<({ sourceX, sourceY, targetX, targetY, style }: EdgeProps) => JSX.Element>;
export default _default;
+13
View File
@@ -0,0 +1,13 @@
import React from 'react';
import { ElementId, Edge, EdgeCompProps } from '../../types';
interface EdgeWrapperProps {
id: ElementId;
source: ElementId;
target: ElementId;
type: any;
onClick: (edge: Edge) => void;
animated: boolean;
selected: boolean;
}
declare const _default: (EdgeComponent: React.ComponentType<EdgeCompProps>) => React.MemoExoticComponent<({ id, source, target, type, animated, selected, onClick, ...rest }: EdgeWrapperProps) => JSX.Element>;
export default _default;
+17
View File
@@ -0,0 +1,17 @@
import React from 'react';
import { HandleType, ElementId, Position, XYPosition, OnConnectFunc, Connection } from '../../types';
declare type ValidConnectionFunc = (connection: Connection) => boolean;
declare type SetSourceIdFunc = (nodeId: ElementId | null) => void;
interface BaseHandleProps {
type: HandleType;
nodeId: ElementId;
onConnect: OnConnectFunc;
position: Position;
setSourceId: SetSourceIdFunc;
setPosition: (pos: XYPosition) => void;
isValidConnection: ValidConnectionFunc;
id?: ElementId | boolean;
className?: string;
}
declare const BaseHandle: React.MemoExoticComponent<({ type, nodeId, onConnect, position, setSourceId, setPosition, className, id, isValidConnection, ...rest }: BaseHandleProps) => JSX.Element>;
export default BaseHandle;
+10
View File
@@ -0,0 +1,10 @@
import React from 'react';
import { HandleType, Position, OnConnectFunc } from '../../types';
interface HandleProps {
type: HandleType;
position: Position;
onConnect?: OnConnectFunc;
isValidConnection?: () => boolean;
}
declare const Handle: React.MemoExoticComponent<({ onConnect, type, position, isValidConnection, ...rest }: HandleProps) => JSX.Element>;
export default Handle;
+4
View File
@@ -0,0 +1,4 @@
/// <reference types="react" />
import { NodeProps } from '../../types';
declare const _default: ({ data, style }: NodeProps) => JSX.Element;
export default _default;
+4
View File
@@ -0,0 +1,4 @@
/// <reference types="react" />
import { NodeProps } from '../../types';
declare const _default: ({ data, style }: NodeProps) => JSX.Element;
export default _default;
+4
View File
@@ -0,0 +1,4 @@
/// <reference types="react" />
import { NodeProps } from '../../types';
declare const _default: ({ data, style }: NodeProps) => JSX.Element;
export default _default;
+16
View File
@@ -0,0 +1,16 @@
import React, { CSSProperties } from 'react';
import { Node, Transform, ElementId, NodeComponentProps } from '../../types';
interface WrapNodeProps {
id: ElementId;
type: string;
data: any;
selected: boolean;
transform: Transform;
xPos: number;
yPos: number;
onClick: (node: Node) => void | undefined;
onNodeDragStop: (node: Node) => void;
style?: CSSProperties;
}
declare const _default: (NodeComponent: React.ComponentType<NodeComponentProps>) => React.MemoExoticComponent<({ id, type, data, transform, xPos, yPos, selected, onClick, onNodeDragStop, style, }: WrapNodeProps) => JSX.Element>;
export default _default;
+3
View File
@@ -0,0 +1,3 @@
import React from 'react';
declare const _default: React.MemoExoticComponent<() => JSX.Element>;
export default _default;
+3
View File
@@ -0,0 +1,3 @@
import React from 'react';
declare const _default: React.MemoExoticComponent<() => JSX.Element>;
export default _default;
+11
View File
@@ -0,0 +1,11 @@
import React, { SVGAttributes } from 'react';
interface EdgeRendererProps {
width: number;
height: number;
edgeTypes: any;
connectionLineStyle?: SVGAttributes<{}>;
connectionLineType?: string;
onElementClick?: () => void;
}
declare const EdgeRenderer: React.MemoExoticComponent<({ width, height, connectionLineStyle, connectionLineType, ...rest }: EdgeRendererProps) => JSX.Element | null>;
export default EdgeRenderer;
+2
View File
@@ -0,0 +1,2 @@
import { EdgeTypesType } from '../../types';
export declare function createEdgeTypes(edgeTypes: EdgeTypesType): EdgeTypesType;
+25
View File
@@ -0,0 +1,25 @@
import React, { SVGAttributes } from 'react';
import { Elements, NodeTypesType, EdgeTypesType, GridType, OnLoadFunc } from '../../types';
export interface GraphViewProps {
elements: Elements;
onElementClick: () => void;
onElementsRemove: (elements: Elements) => void;
onNodeDragStop: () => void;
onConnect: () => void;
onLoad: OnLoadFunc;
onMove: () => void;
selectionKeyCode: number;
nodeTypes: NodeTypesType;
edgeTypes: EdgeTypesType;
connectionLineType: string;
connectionLineStyle: SVGAttributes<{}>;
deleteKeyCode: number;
showBackground: boolean;
backgroundGap: number;
backgroundColor: string;
backgroundType: GridType;
snapToGrid: boolean;
snapGrid: [number, number];
}
declare const GraphView: React.MemoExoticComponent<({ nodeTypes, edgeTypes, onMove, onLoad, onElementClick, onNodeDragStop, connectionLineType, connectionLineStyle, selectionKeyCode, onElementsRemove, deleteKeyCode, elements, showBackground, backgroundGap, backgroundColor, backgroundType, onConnect, snapToGrid, snapGrid, }: GraphViewProps) => JSX.Element>;
export default GraphView;
+9
View File
@@ -0,0 +1,9 @@
import React from 'react';
import { NodeTypesType } from '../../types';
interface NodeRendererProps {
nodeTypes: NodeTypesType;
onElementClick: () => void;
onNodeDragStop: () => void;
}
declare const NodeRenderer: React.MemoExoticComponent<(props: NodeRendererProps) => JSX.Element>;
export default NodeRenderer;
+2
View File
@@ -0,0 +1,2 @@
import { NodeTypesType } from '../../types';
export declare function createNodeTypes(nodeTypes: NodeTypesType): NodeTypesType;
+57
View File
@@ -0,0 +1,57 @@
import React, { SVGAttributes, HTMLAttributes } from 'react';
import { Elements, NodeTypesType, EdgeTypesType, GridType, OnLoadFunc } from '../../types';
import '../../style.css';
export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onLoad'> {
elements: Elements;
onElementClick: () => void;
onElementsRemove: (elements: Elements) => void;
onNodeDragStop: () => void;
onConnect: () => void;
onLoad: OnLoadFunc;
onMove: () => void;
nodeTypes: NodeTypesType;
edgeTypes: EdgeTypesType;
connectionLineType: string;
connectionLineStyle: SVGAttributes<{}>;
deleteKeyCode: number;
selectionKeyCode: number;
showBackground: boolean;
backgroundGap: number;
backgroundColor: string;
backgroundType: GridType;
snapToGrid: boolean;
snapGrid: [16, 16];
}
declare const ReactFlow: {
({ style, onElementClick, elements, children, nodeTypes, edgeTypes, onLoad, onMove, onElementsRemove, onConnect, onNodeDragStop, connectionLineType, connectionLineStyle, deleteKeyCode, selectionKeyCode, showBackground, backgroundGap, backgroundType, backgroundColor, snapToGrid, snapGrid, }: ReactFlowProps): JSX.Element;
displayName: string;
defaultProps: {
onElementClick: () => void;
onElementsRemove: () => void;
onNodeDragStop: () => void;
onConnect: () => void;
onLoad: () => void;
onMove: () => void;
nodeTypes: {
input: ({ data, style }: import("../../types").NodeProps) => JSX.Element;
default: ({ data, style }: import("../../types").NodeProps) => JSX.Element;
output: ({ data, style }: import("../../types").NodeProps) => JSX.Element;
};
edgeTypes: {
default: React.MemoExoticComponent<({ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, style, }: import("../../types").EdgeBezierProps) => JSX.Element>;
straight: React.MemoExoticComponent<({ sourceX, sourceY, targetX, targetY, style }: import("../../types").EdgeProps) => JSX.Element>;
step: React.MemoExoticComponent<({ sourceX, sourceY, targetX, targetY, style }: import("../../types").EdgeProps) => JSX.Element>;
};
connectionLineType: string;
connectionLineStyle: {};
deleteKeyCode: number;
selectionKeyCode: number;
backgroundColor: string;
backgroundGap: number;
showBackground: boolean;
backgroundType: GridType;
snapToGrid: boolean;
snapGrid: number[];
};
};
export default ReactFlow;
+7
View File
@@ -0,0 +1,7 @@
/// <reference types="react" />
import { ElementId } from '../types';
declare type ContextProps = ElementId | null;
export declare const NodeIdContext: import("react").Context<ContextProps>;
export declare const Provider: import("react").ProviderExoticComponent<import("react").ProviderProps<ContextProps>>;
export declare const Consumer: import("react").ExoticComponent<import("react").ConsumerProps<ContextProps>>;
export default NodeIdContext;
+3
View File
@@ -0,0 +1,3 @@
import { MutableRefObject } from 'react';
declare const _default: (zoomPane: MutableRefObject<Element | null>, onMove: () => void, shiftPressed: boolean) => void;
export default _default;
+3
View File
@@ -0,0 +1,3 @@
import { Node, Edge } from '../types';
declare const useElementUpdater: (elements: (Node | Edge)[]) => void;
export default useElementUpdater;
+7
View File
@@ -0,0 +1,7 @@
import { Elements } from '../types';
interface HookParams {
deleteKeyCode: number;
onElementsRemove: (elements: Elements) => void;
}
declare const _default: ({ deleteKeyCode, onElementsRemove }: HookParams) => void;
export default _default;
+2
View File
@@ -0,0 +1,2 @@
declare const _default: (keyCode: number) => boolean;
export default _default;
+5
View File
@@ -0,0 +1,5 @@
import ReactFlow from './container/ReactFlow';
export default ReactFlow;
export { default as Handle } from './components/Handle';
export { MiniMap, Controls } from './plugins';
export { isNode, isEdge, removeElements, addEdge, getOutgoers, } from './utils/graph';
+5
View File
@@ -0,0 +1,5 @@
import React from 'react';
interface ControlProps extends React.HTMLAttributes<HTMLDivElement> {
}
declare const _default: ({ style, className }: ControlProps) => JSX.Element;
export default _default;
+9
View File
@@ -0,0 +1,9 @@
import React from 'react';
import { Node } from '../../types';
declare type StringFunc = (node: Node) => string;
interface MiniMapProps extends React.HTMLAttributes<HTMLCanvasElement> {
bgColor?: string;
nodeColor?: string | StringFunc;
}
declare const _default: ({ style, className, bgColor, nodeColor, }: MiniMapProps) => JSX.Element;
export default _default;
+2
View File
@@ -0,0 +1,2 @@
export { default as MiniMap } from './MiniMap';
export { default as Controls } from './Controls';
+69
View File
@@ -0,0 +1,69 @@
import { StoreModel } from './index';
export declare const useStoreActions: <Result>(mapActions: (actions: import("easy-peasy").ActionMapper<{
selectedNodesBbox: import("../types").Rect;
d3Zoom: import("d3-zoom").ZoomBehavior<Element, unknown> | null;
d3Selection: import("d3-selection").Selection<Element, unknown, null, undefined> | null;
selection: import("../types").SelectionRect | null;
connectionPosition: import("../types").XYPosition;
onConnect: import("../types").OnConnectFunc;
setOnConnect: import("easy-peasy").Action<StoreModel, import("../types").OnConnectFunc>;
setNodes: import("easy-peasy").Action<StoreModel, import("../types").Node[]>;
setEdges: import("easy-peasy").Action<StoreModel, import("../types").Edge[]>;
updateNodeData: import("easy-peasy").Action<StoreModel, {
id: string;
width: number;
height: number;
handleBounds: {
source: import("../types").HandleElement[] | null;
target: import("../types").HandleElement[] | null;
};
}>;
updateNodePos: import("easy-peasy").Action<StoreModel, {
id: string;
pos: import("../types").XYPosition;
}>;
setSelection: import("easy-peasy").Action<StoreModel, boolean>;
setNodesSelection: import("easy-peasy").Action<StoreModel, {
isActive: boolean;
selection?: import("../types").SelectionRect | undefined;
}>;
setSelectedElements: import("easy-peasy").Action<StoreModel, import("../types").Node | import("../types").Edge | (import("../types").Node | import("../types").Edge)[]>;
updateSelection: import("easy-peasy").Action<StoreModel, import("../types").SelectionRect>;
updateTransform: import("easy-peasy").Action<StoreModel, {
x: number;
y: number;
k: number;
}>;
updateSize: import("easy-peasy").Action<StoreModel, import("../types").Dimensions>;
initD3: import("easy-peasy").Action<StoreModel, {
zoom: import("d3-zoom").ZoomBehavior<Element, unknown>;
selection: import("d3-selection").Selection<Element, unknown, null, undefined>;
}>;
setSnapGrid: import("easy-peasy").Action<StoreModel, {
snapToGrid: boolean;
snapGrid: [number, number];
}>;
setConnectionPosition: import("easy-peasy").Action<StoreModel, import("../types").XYPosition>;
setConnectionSourceId: import("easy-peasy").Action<StoreModel, string | null>;
}, "1">) => Result) => Result;
export declare const useStoreDispatch: () => import("easy-peasy").Dispatch<StoreModel, import("redux").Action<any>>;
export declare const useStoreState: <Result>(mapState: (state: import("easy-peasy").IntermediateStateMapper<{
width: number;
height: number;
transform: [number, number, number];
nodes: import("../types").Node[];
edges: import("../types").Edge[];
selectedElements: (import("../types").Node | import("../types").Edge)[];
selectedNodesBbox: import("../types").Rect;
d3Zoom: import("d3-zoom").ZoomBehavior<Element, unknown> | null;
d3Selection: import("d3-selection").Selection<Element, unknown, null, undefined> | null;
d3Initialised: boolean;
nodesSelectionActive: boolean;
selectionActive: boolean;
selection: import("../types").SelectionRect | null;
connectionSourceId: string | null;
connectionPosition: import("../types").XYPosition;
snapToGrid: boolean;
snapGrid: [number, number];
onConnect: import("../types").OnConnectFunc;
}, "1">) => Result, dependencies?: any[] | undefined) => Result;
+149
View File
@@ -0,0 +1,149 @@
import { Action } from 'easy-peasy';
import { Selection as D3Selection, ZoomBehavior } from 'd3';
import { ElementId, Elements, Transform, Node, Edge, Rect, Dimensions, XYPosition, OnConnectFunc, SelectionRect, HandleElement } from '../types';
declare type TransformXYK = {
x: number;
y: number;
k: number;
};
declare type NodePosUpdate = {
id: ElementId;
pos: XYPosition;
};
declare type NodeUpdate = {
id: ElementId;
width: number;
height: number;
handleBounds: {
source: HandleElement[] | null;
target: HandleElement[] | null;
};
};
declare type SelectionUpdate = {
isActive: boolean;
selection?: SelectionRect;
};
declare type D3Init = {
zoom: ZoomBehavior<Element, unknown>;
selection: D3Selection<Element, unknown, null, undefined>;
};
declare type SetSnapGrid = {
snapToGrid: boolean;
snapGrid: [number, number];
};
export interface StoreModel {
width: number;
height: number;
transform: Transform;
nodes: Node[];
edges: Edge[];
selectedElements: Elements;
selectedNodesBbox: Rect;
d3Zoom: ZoomBehavior<Element, unknown> | null;
d3Selection: D3Selection<Element, unknown, null, undefined> | null;
d3Initialised: boolean;
nodesSelectionActive: boolean;
selectionActive: boolean;
selection: SelectionRect | null;
connectionSourceId: ElementId | null;
connectionPosition: XYPosition;
snapToGrid: boolean;
snapGrid: [number, number];
onConnect: OnConnectFunc;
setOnConnect: Action<StoreModel, OnConnectFunc>;
setNodes: Action<StoreModel, Node[]>;
setEdges: Action<StoreModel, Edge[]>;
updateNodeData: Action<StoreModel, NodeUpdate>;
updateNodePos: Action<StoreModel, NodePosUpdate>;
setSelection: Action<StoreModel, boolean>;
setNodesSelection: Action<StoreModel, SelectionUpdate>;
setSelectedElements: Action<StoreModel, Elements | Node | Edge>;
updateSelection: Action<StoreModel, SelectionRect>;
updateTransform: Action<StoreModel, TransformXYK>;
updateSize: Action<StoreModel, Dimensions>;
initD3: Action<StoreModel, D3Init>;
setSnapGrid: Action<StoreModel, SetSnapGrid>;
setConnectionPosition: Action<StoreModel, XYPosition>;
setConnectionSourceId: Action<StoreModel, ElementId | null>;
}
declare const store: {
getState: () => import("easy-peasy").IntermediateStateMapper<{
width: number;
height: number;
transform: [number, number, number];
nodes: Node[];
edges: Edge[];
selectedElements: (Node | Edge)[];
selectedNodesBbox: Rect;
d3Zoom: ZoomBehavior<Element, unknown> | null;
d3Selection: D3Selection<Element, unknown, null, undefined> | null;
d3Initialised: boolean;
nodesSelectionActive: boolean;
selectionActive: boolean;
selection: SelectionRect | null;
connectionSourceId: string | null;
connectionPosition: XYPosition;
snapToGrid: boolean;
snapGrid: [number, number];
onConnect: OnConnectFunc;
}, "1">;
subscribe: (listener: () => void) => import("redux").Unsubscribe;
replaceReducer: (nextReducer: import("redux").Reducer<import("easy-peasy").IntermediateStateMapper<{
width: number;
height: number;
transform: [number, number, number];
nodes: Node[];
edges: Edge[];
selectedElements: (Node | Edge)[];
selectedNodesBbox: Rect;
d3Zoom: ZoomBehavior<Element, unknown> | null;
d3Selection: D3Selection<Element, unknown, null, undefined> | null;
d3Initialised: boolean;
nodesSelectionActive: boolean;
selectionActive: boolean;
selection: SelectionRect | null;
connectionSourceId: string | null;
connectionPosition: XYPosition;
snapToGrid: boolean;
snapGrid: [number, number];
onConnect: OnConnectFunc;
}, "1">, import("redux").AnyAction>) => void;
dispatch: import("easy-peasy").Dispatch<StoreModel, import("redux").Action<any>>;
addModel: <ModelSlice extends object>(key: string, modelSlice: ModelSlice) => void;
clearMockedActions: () => void;
getActions: () => import("easy-peasy").ActionMapper<{
selectedNodesBbox: Rect;
d3Zoom: ZoomBehavior<Element, unknown> | null;
d3Selection: D3Selection<Element, unknown, null, undefined> | null;
selection: SelectionRect | null;
connectionPosition: XYPosition;
onConnect: OnConnectFunc;
setOnConnect: Action<StoreModel, OnConnectFunc>;
setNodes: Action<StoreModel, Node[]>;
setEdges: Action<StoreModel, Edge[]>;
updateNodeData: Action<StoreModel, NodeUpdate>;
updateNodePos: Action<StoreModel, NodePosUpdate>;
setSelection: Action<StoreModel, boolean>;
setNodesSelection: Action<StoreModel, SelectionUpdate>;
setSelectedElements: Action<StoreModel, Node | Edge | (Node | Edge)[]>;
updateSelection: Action<StoreModel, SelectionRect>;
updateTransform: Action<StoreModel, TransformXYK>;
updateSize: Action<StoreModel, Dimensions>;
initD3: Action<StoreModel, D3Init>;
setSnapGrid: Action<StoreModel, SetSnapGrid>;
setConnectionPosition: Action<StoreModel, XYPosition>;
setConnectionSourceId: Action<StoreModel, string | null>;
}, "1">;
getListeners: () => import("easy-peasy").ListenerMapper<{
selectedNodesBbox: Rect;
d3Zoom: ZoomBehavior<Element, unknown> | null;
d3Selection: D3Selection<Element, unknown, null, undefined> | null;
selection: SelectionRect | null;
connectionPosition: XYPosition;
onConnect: OnConnectFunc;
}, "1">;
getMockedActions: () => import("easy-peasy").MockedAction[];
reconfigure: <NewStoreModel extends object>(model: NewStoreModel) => void;
removeModel: (key: string) => void;
};
export default store;
+110
View File
@@ -0,0 +1,110 @@
import { CSSProperties, SVGAttributes } from 'react';
export declare type ElementId = string;
export declare type Elements = Array<Node | Edge>;
export declare type Transform = [number, number, number];
export declare type Position = 'left' | 'top' | 'right' | 'bottom';
export declare type XYPosition = {
x: number;
y: number;
};
export declare enum GridType {
Lines = "lines",
Dots = "dots"
}
export declare type HandleType = 'source' | 'target';
export declare type NodeTypesType = {
[key: string]: React.ReactNode;
};
export declare type EdgeTypesType = NodeTypesType;
export interface Dimensions {
width: number;
height: number;
}
export interface Rect extends Dimensions {
x: number;
y: number;
}
export interface SelectionRect extends Rect {
startX: number;
startY: number;
draw: boolean;
}
export interface Node {
id: ElementId;
position: XYPosition;
type?: string;
__rg?: any;
data?: any;
style?: CSSProperties;
}
export interface Edge {
id: ElementId;
type?: string;
source: ElementId;
target: ElementId;
style?: SVGAttributes<{}>;
animated?: boolean;
}
export interface EdgeProps {
sourceX: number;
sourceY: number;
targetX: number;
targetY: number;
style?: SVGAttributes<{}>;
}
export interface EdgeBezierProps extends EdgeProps {
sourcePosition: Position;
targetPosition: Position;
}
export interface NodeProps {
id: ElementId;
type: string;
data: any;
selected: boolean;
style?: CSSProperties;
}
export interface NodeComponentProps {
id: ElementId;
type: string;
data: any;
selected?: boolean;
transform?: Transform;
xPos?: number;
yPos?: number;
onClick?: (node: Node) => void | undefined;
onNodeDragStop?: () => any;
style?: CSSProperties;
}
export declare type FitViewParams = {
padding: number;
};
export declare type FitViewFunc = (fitViewOptions: FitViewParams) => void;
declare type OnLoadParams = {
zoomIn: () => void;
zoomOut: () => void;
fitView: FitViewFunc;
};
export declare type OnLoadFunc = (params: OnLoadParams) => void;
export declare type Connection = {
source: ElementId | null;
target: ElementId | null;
};
export declare type OnConnectFunc = (params: Connection) => void;
export interface HandleElement {
id?: ElementId | null;
position: Position;
x: number;
y: number;
width: number;
height: number;
}
export interface EdgeCompProps {
id: ElementId;
source: ElementId;
target: ElementId;
type: any;
onClick?: (edge: Edge) => void;
animated?: boolean;
selected?: boolean;
}
export {};
+14
View File
@@ -0,0 +1,14 @@
import { Node, Edge, XYPosition, Rect, FitViewParams } from '../types';
export declare const isEdge: (element: Node | Edge) => boolean;
export declare const isNode: (element: Node | Edge) => boolean;
export declare const getOutgoers: (node: Node, elements: (Node | Edge)[]) => (Node | Edge)[];
export declare const removeElements: (elementsToRemove: (Node | Edge)[], elements: (Node | Edge)[]) => (Node | Edge)[];
export declare const addEdge: (edgeParams: Edge, elements: (Node | Edge)[]) => (Node | Edge)[];
export declare const parseElement: (element: Node | Edge, transform?: [number, number, number]) => Node | Edge;
export declare const getBoundingBox: (nodes: Node[]) => Rect;
export declare const graphPosToZoomedPos: (pos: XYPosition, transform: [number, number, number]) => XYPosition;
export declare const getNodesInside: (nodes: Node[], bbox: Rect, transform?: [number, number, number], partially?: boolean) => Node[];
export declare const getConnectedEdges: (nodes: Node[], edges: Edge[]) => Edge[];
export declare const fitView: ({ padding }?: FitViewParams) => void;
export declare const zoomIn: () => void;
export declare const zoomOut: () => void;
+6
View File
@@ -0,0 +1,6 @@
import { MouseEvent as ReactMouseEvent } from 'react';
export declare const isInputDOMNode: (e: MouseEvent | ReactMouseEvent<Element, MouseEvent> | ReactMouseEvent<HTMLElement | SVGElement, MouseEvent> | import("react").TouchEvent<HTMLElement | SVGElement> | TouchEvent | KeyboardEvent) => boolean;
export declare const getDimensions: (node: HTMLDivElement) => {
width: number;
height: number;
};
+5 -3
View File
@@ -14,7 +14,7 @@ class App extends PureComponent {
const onChange = (option, d) => { const onChange = (option, d) => {
this.setState(prevState => ( this.setState(prevState => (
{elements: prevState.elements.map(e => { {elements: prevState.elements.map(e => {
if (isEdge(e) || e.id !== '6') { if (isEdge(e) || e.id !== '6') {
return e; return e;
} }
@@ -32,7 +32,7 @@ class App extends PureComponent {
const onChangeInput = (input, d) => { const onChangeInput = (input, d) => {
this.setState(prevState => ( this.setState(prevState => (
{elements: prevState.elements.map(e => { {elements: prevState.elements.map(e => {
if (isEdge(e) || e.id !== '8') { if (isEdge(e) || e.id !== '8') {
return e; return e;
} }
@@ -41,7 +41,7 @@ class App extends PureComponent {
...e, ...e,
data: { data: {
...e.data, ...e.data,
input: input || 'write something' input: input || 'write something'
} }
}; };
} else { } else {
@@ -145,6 +145,8 @@ class App extends PureComponent {
connectionLineType="bezier" connectionLineType="bezier"
backgroundColor="#888" backgroundColor="#888"
backgroundGap={16} backgroundGap={16}
snapToGrid={true}
snapGrid={[16, 16]}
> >
<MiniMap <MiniMap
style={{ position: 'absolute', right: 10, bottom: 10 }} style={{ position: 'absolute', right: 10, bottom: 10 }}
+17 -30
View File
@@ -3814,8 +3814,7 @@
"ansi-regex": { "ansi-regex": {
"version": "2.1.1", "version": "2.1.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"aproba": { "aproba": {
"version": "1.2.0", "version": "1.2.0",
@@ -3836,14 +3835,12 @@
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@@ -3858,20 +3855,17 @@
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"console-control-strings": { "console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
@@ -3988,8 +3982,7 @@
"inherits": { "inherits": {
"version": "2.0.3", "version": "2.0.3",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.5",
@@ -4001,7 +3994,6 @@
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"number-is-nan": "^1.0.0" "number-is-nan": "^1.0.0"
} }
@@ -4016,7 +4008,6 @@
"version": "3.0.4", "version": "3.0.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
@@ -4024,14 +4015,12 @@
"minimist": { "minimist": {
"version": "0.0.8", "version": "0.0.8",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"minipass": { "minipass": {
"version": "2.3.5", "version": "2.3.5",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"safe-buffer": "^5.1.2", "safe-buffer": "^5.1.2",
"yallist": "^3.0.0" "yallist": "^3.0.0"
@@ -4050,7 +4039,6 @@
"version": "0.5.1", "version": "0.5.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"minimist": "0.0.8" "minimist": "0.0.8"
} }
@@ -4131,8 +4119,7 @@
"number-is-nan": { "number-is-nan": {
"version": "1.0.1", "version": "1.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
@@ -4144,7 +4131,6 @@
"version": "1.4.0", "version": "1.4.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
@@ -4230,8 +4216,7 @@
"safe-buffer": { "safe-buffer": {
"version": "5.1.2", "version": "5.1.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"safer-buffer": { "safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
@@ -4267,7 +4252,6 @@
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"code-point-at": "^1.0.0", "code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0", "is-fullwidth-code-point": "^1.0.0",
@@ -4287,7 +4271,6 @@
"version": "3.0.1", "version": "3.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"ansi-regex": "^2.0.0" "ansi-regex": "^2.0.0"
} }
@@ -4331,14 +4314,12 @@
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"yallist": { "yallist": {
"version": "3.0.3", "version": "3.0.3",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
} }
} }
}, },
@@ -6704,6 +6685,12 @@
"integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
"dev": true "dev": true
}, },
"prettier": {
"version": "1.18.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.18.2.tgz",
"integrity": "sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==",
"dev": true
},
"pretty-bytes": { "pretty-bytes": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-3.0.1.tgz", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-3.0.1.tgz",
+1
View File
@@ -30,6 +30,7 @@
"cypress": "^3.4.1", "cypress": "^3.4.1",
"husky": "^3.0.9", "husky": "^3.0.9",
"postcss-nested": "^4.1.2", "postcss-nested": "^4.1.2",
"prettier": "1.18.2",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"react": "^16.10.2", "react": "^16.10.2",
"rollup": "^1.25.1", "rollup": "^1.25.1",
+4
View File
@@ -0,0 +1,4 @@
module.exports = {
trailingComma: 'es5',
singleQuote: true
};
+37 -14
View File
@@ -1,30 +1,40 @@
import React, {memo} from 'react'; import React, { memo, HTMLAttributes, CSSProperties } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { useStoreState } from '../../store/hooks'; import { useStoreState } from '../../store/hooks';
import { GridType } from '../../types'; import { GridType } from '../../types';
interface GridProps { interface GridProps extends HTMLAttributes<SVGElement> {
backgroundType?: GridType; backgroundType?: GridType;
gap?: number; gap?: number;
color?: string; color?: string;
size?: number; size?: number;
style?: React.CSSProperties; }
className?: string | null;
};
const baseStyles: React.CSSProperties = { const baseStyles: CSSProperties = {
position: 'absolute', position: 'absolute',
top: 0, top: 0,
left: 0, left: 0,
}; };
const createGridLines = (width: number, height: number, xOffset: number, yOffset: number, gap: number): string => { const createGridLines = (
width: number,
height: number,
xOffset: number,
yOffset: number,
gap: number
): string => {
const lineCountX = Math.ceil(width / gap) + 1; const lineCountX = Math.ceil(width / gap) + 1;
const lineCountY = Math.ceil(height / gap) + 1; const lineCountY = Math.ceil(height / gap) + 1;
const xValues = Array.from({length: lineCountX}, (_, i) => `M${i * gap + xOffset} 0 V${height}`); const xValues = Array.from(
const yValues = Array.from({length: lineCountY}, (_, i) => `M0 ${i * gap + yOffset} H${width}`); { length: lineCountX },
(_, i) => `M${i * gap + xOffset} 0 V${height}`
);
const yValues = Array.from(
{ length: lineCountY },
(_, i) => `M0 ${i * gap + yOffset} H${width}`
);
return [...xValues, ...yValues].join(' '); return [...xValues, ...yValues].join(' ');
}; };
@@ -40,11 +50,12 @@ const createGridDots = (
const lineCountX = Math.ceil(width / gap) + 1; const lineCountX = Math.ceil(width / gap) + 1;
const lineCountY = Math.ceil(height / gap) + 1; const lineCountY = Math.ceil(height / gap) + 1;
const values = Array.from({length: lineCountX}, (_, col) => { const values = Array.from({ length: lineCountX }, (_, col) => {
const x = col * gap + xOffset; const x = col * gap + xOffset;
return Array.from({length: lineCountY}, (_, row) => { return Array.from({ length: lineCountY }, (_, row) => {
const y = row * gap + yOffset; const y = row * gap + yOffset;
return `M${x} ${y - size} l${size} ${size} l${-size} ${size} l${-size} ${-size}z`; return `M${x} ${y -
size} l${size} ${size} l${-size} ${size} l${-size} ${-size}z`;
}).join(' '); }).join(' ');
}); });
@@ -52,7 +63,14 @@ const createGridDots = (
}; };
const Grid = memo( const Grid = memo(
({gap = 24, color = '#aaa', size = 0.5, style = {}, className = null, backgroundType = GridType.Dots}: GridProps) => { ({
gap = 24,
color = '#aaa',
size = 0.5,
style = {},
className = '',
backgroundType = GridType.Dots,
}: GridProps) => {
const { const {
width, width,
height, height,
@@ -73,7 +91,12 @@ const Grid = memo(
const stroke = isLines ? color : 'none'; const stroke = isLines ? color : 'none';
return ( return (
<svg width={width} height={height} style={{...baseStyles, ...style}} className={gridClasses}> <svg
width={width}
height={height}
style={{ ...baseStyles, ...style }}
className={gridClasses}
>
<path fill={fill} stroke={stroke} strokeWidth={size} d={path} /> <path fill={fill} stroke={stroke} strokeWidth={size} d={path} />
</svg> </svg>
); );
+23 -14
View File
@@ -7,16 +7,22 @@ interface ConnectionLineProps {
connectionSourceId: ElementId; connectionSourceId: ElementId;
connectionPositionX: number; connectionPositionX: number;
connectionPositionY: number; connectionPositionY: number;
connectionLineType?: string | null; connectionLineType?: string | null;
nodes: Node[]; nodes: Node[];
transform: Transform; transform: Transform;
connectionLineStyle?: SVGAttributes<{}>; connectionLineStyle?: SVGAttributes<{}>;
className?: string; className?: string;
}; }
export default ({ export default ({
connectionSourceId, connectionLineStyle = {}, connectionPositionX, connectionPositionY, connectionSourceId,
connectionLineType, nodes = [], className, transform connectionLineStyle = {},
connectionPositionX,
connectionPositionY,
connectionLineType,
nodes = [],
className,
transform,
}: ConnectionLineProps) => { }: ConnectionLineProps) => {
const [sourceNode, setSourceNode] = useState<Node | null>(null); const [sourceNode, setSourceNode] = useState<Node | null>(null);
const hasHandleId = connectionSourceId.includes('__'); const hasHandleId = connectionSourceId.includes('__');
@@ -25,7 +31,7 @@ export default ({
const handleId = hasHandleId ? sourceIdSplitted[1] : null; const handleId = hasHandleId ? sourceIdSplitted[1] : null;
useEffect(() => { useEffect(() => {
const nextSourceNode = nodes.find(n => n.id === nodeId) || null; const nextSourceNode = nodes.find(n => n.id === nodeId) || null;
setSourceNode(nextSourceNode); setSourceNode(nextSourceNode);
}, []); }, []);
@@ -35,11 +41,17 @@ export default ({
const edgeClasses: string = cx('react-flow__edge', 'connection', className); const edgeClasses: string = cx('react-flow__edge', 'connection', className);
const sourceHandle = handleId ? const sourceHandle = handleId
sourceNode.__rg.handleBounds.source.find((d: HandleElement) => d.id === handleId) : ? sourceNode.__rg.handleBounds.source.find(
sourceNode.__rg.handleBounds.source[0]; (d: HandleElement) => d.id === handleId
const sourceHandleX = sourceHandle ? sourceHandle.x + (sourceHandle.width / 2) : sourceNode.__rg.width / 2; )
const sourceHandleY = sourceHandle ? sourceHandle.y + (sourceHandle.height / 2) : sourceNode.__rg.height; : 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; const sourceX = sourceNode.__rg.position.x + sourceHandleX;
const sourceY = sourceNode.__rg.position.y + sourceHandleY; const sourceY = sourceNode.__rg.position.y + sourceHandleY;
@@ -58,10 +70,7 @@ export default ({
return ( return (
<g className={edgeClasses}> <g className={edgeClasses}>
<path <path d={dAttr} {...connectionLineStyle} />
d={dAttr}
{...connectionLineStyle}
/>
</g> </g>
); );
}; };
+29 -21
View File
@@ -2,28 +2,36 @@ import React, { memo } from 'react';
import { EdgeBezierProps } from '../../types'; import { EdgeBezierProps } from '../../types';
export default memo(({ export default memo(
sourceX, sourceY, targetX, targetY, ({
sourcePosition = 'bottom', targetPosition = 'top', style = {} sourceX,
}: EdgeBezierProps) => { sourceY,
const yOffset = Math.abs(targetY - sourceY) / 2; targetX,
const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset; targetY,
sourcePosition = 'bottom',
targetPosition = 'top',
style = {},
}: EdgeBezierProps) => {
const yOffset = Math.abs(targetY - sourceY) / 2;
const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset;
let dAttr = `M${sourceX},${sourceY} C${sourceX},${centerY} ${targetX},${centerY} ${targetX},${targetY}`; let dAttr = `M${sourceX},${sourceY} C${sourceX},${centerY} ${targetX},${centerY} ${targetX},${targetY}`;
if (['left', 'right'].includes(sourcePosition) && ['left', 'right'].includes(targetPosition)) { if (
const xOffset = Math.abs(targetX - sourceX) / 2; ['left', 'right'].includes(sourcePosition) &&
const centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset; ['left', 'right'].includes(targetPosition)
) {
const xOffset = Math.abs(targetX - sourceX) / 2;
const centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset;
dAttr = `M${sourceX},${sourceY} C${centerX},${sourceY} ${centerX},${targetY} ${targetX},${targetY}`; dAttr = `M${sourceX},${sourceY} C${centerX},${sourceY} ${centerX},${targetY} ${targetX},${targetY}`;
} else if (['left', 'right'].includes(sourcePosition) || ['left', 'right'].includes(targetPosition)) { } else if (
dAttr = `M${sourceX},${sourceY} C${sourceX},${targetY} ${sourceX},${targetY} ${targetX},${targetY}`; ['left', 'right'].includes(sourcePosition) ||
['left', 'right'].includes(targetPosition)
) {
dAttr = `M${sourceX},${sourceY} C${sourceX},${targetY} ${sourceX},${targetY} ${targetX},${targetY}`;
}
return <path {...style} d={dAttr} />;
} }
);
return (
<path
{...style}
d={dAttr}
/>
);
});
+12 -12
View File
@@ -2,16 +2,16 @@ import React, { memo } from 'react';
import { EdgeProps } from '../../types'; import { EdgeProps } from '../../types';
export default memo(({ export default memo(
sourceX, sourceY, targetX, targetY, style = {} ({ sourceX, sourceY, targetX, targetY, style = {} }: EdgeProps) => {
} : EdgeProps) => { const yOffset = Math.abs(targetY - sourceY) / 2;
const yOffset = Math.abs(targetY - sourceY) / 2; const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset;
const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset;
return ( return (
<path <path
{...style} {...style}
d={`M ${sourceX},${sourceY}L ${sourceX},${centerY}L ${targetX},${centerY}L ${targetX},${targetY}`} d={`M ${sourceX},${sourceY}L ${sourceX},${centerY}L ${targetX},${centerY}L ${targetX},${targetY}`}
/> />
); );
}); }
);
+7 -10
View File
@@ -2,13 +2,10 @@ import React, { memo } from 'react';
import { EdgeProps } from '../../types'; import { EdgeProps } from '../../types';
export default memo(({ export default memo(
sourceX, sourceY, targetX, targetY, style = {} ({ sourceX, sourceY, targetX, targetY, style = {} }: EdgeProps) => {
}: EdgeProps) => { return (
return ( <path {...style} d={`M ${sourceX},${sourceY}L ${targetX},${targetY}`} />
<path );
{...style} }
d={`M ${sourceX},${sourceY}L ${targetX},${targetY}`} );
/>
);
});
+47 -33
View File
@@ -3,42 +3,56 @@ import cx from 'classnames';
import { isInputDOMNode } from '../../utils'; import { isInputDOMNode } from '../../utils';
import store from '../../store'; import store from '../../store';
import { EdgeWrapperProps } from '../../types'; import { ElementId, Edge, EdgeCompProps } from '../../types';
export default (EdgeComponent: ComponentType<EdgeWrapperProps>) => { interface EdgeWrapperProps {
const EdgeWrapper = memo(({ id: ElementId;
id, source, target, type, source: ElementId;
animated, selected, onClick, target: ElementId;
...rest type: any;
}: EdgeWrapperProps) => { onClick: (edge: Edge) => void;
const edgeClasses = cx('react-flow__edge', { selected, animated }); animated: boolean;
const onEdgeClick = (evt: MouseEvent) => { selected: boolean;
if (isInputDOMNode(evt)) { }
return false;
}
store.dispatch.setSelectedElements({ id, source, target }); export default (EdgeComponent: ComponentType<EdgeCompProps>) => {
onClick({ id, source, target, type }); const EdgeWrapper = memo(
}; ({
id,
source,
target,
type,
animated,
selected,
onClick,
...rest
}: EdgeWrapperProps) => {
const edgeClasses = cx('react-flow__edge', { selected, animated });
const onEdgeClick = (evt: MouseEvent): void => {
if (isInputDOMNode(evt)) {
return;
}
return ( store.dispatch.setSelectedElements({ id, source, target });
<g onClick({ id, source, target, type });
className={edgeClasses} };
onClick={onEdgeClick}
> return (
<EdgeComponent <g className={edgeClasses} onClick={onEdgeClick}>
id={id} <EdgeComponent
source={source} id={id}
target={target} source={source}
type={type} target={target}
animated={animated} type={type}
selected={selected} animated={animated}
onClick={onClick} selected={selected}
{...rest} onClick={onClick}
/> {...rest}
</g> />
); </g>
}); );
}
);
EdgeWrapper.displayName = 'EdgeWrapper'; EdgeWrapper.displayName = 'EdgeWrapper';
+79 -42
View File
@@ -1,41 +1,54 @@
import React, { memo, MouseEvent as ReactMouseEvent } from 'react'; import React, { memo, MouseEvent as ReactMouseEvent } from 'react';
import cx from 'classnames'; import cx from 'classnames';
import { HandleType, ElementId, Position, XYPosition, OnConnectFunc, Connection } from '../../types'; import {
HandleType,
ElementId,
Position,
XYPosition,
OnConnectFunc,
Connection,
} from '../../types';
type ValidConnectionFunc = (connection: Connection) => boolean; type ValidConnectionFunc = (connection: Connection) => boolean;
type SetSourceIdFunc = (nodeId: ElementId | null) => void;
interface BaseHandleProps { interface BaseHandleProps {
type: HandleType; type: HandleType;
nodeId: ElementId; nodeId: ElementId;
onConnect: OnConnectFunc; onConnect: OnConnectFunc;
position: Position; position: Position;
setSourceId: (nodeId: ElementId) => void; setSourceId: SetSourceIdFunc;
setPosition: (pos: XYPosition) => void; setPosition: (pos: XYPosition) => void;
isValidConnection: ValidConnectionFunc; isValidConnection: ValidConnectionFunc;
id?: ElementId | boolean; id?: ElementId | boolean;
className?: string; className?: string;
}; }
type Result = { type Result = {
elementBelow: Element; elementBelow: Element | null;
isValid: boolean; isValid: boolean;
connection: Connection; connection: Connection;
isHoveringHandle: boolean; isHoveringHandle: boolean;
}; };
function onMouseDown( function onMouseDown(
evt: ReactMouseEvent, nodeId: ElementId, setSourceId: (nodeId: ElementId) => void, setPosition: (pos: XYPosition) => any, evt: ReactMouseEvent,
onConnect: OnConnectFunc, isTarget: boolean, isValidConnection: ValidConnectionFunc nodeId: ElementId,
setSourceId: SetSourceIdFunc,
setPosition: (pos: XYPosition) => any,
onConnect: OnConnectFunc,
isTarget: boolean,
isValidConnection: ValidConnectionFunc
): void { ): void {
const reactFlowNode = document.querySelector('.react-flow'); const reactFlowNode = document.querySelector('.react-flow');
if (!reactFlowNode) { if (!reactFlowNode) {
return null; return;
} }
const containerBounds = reactFlowNode.getBoundingClientRect(); const containerBounds = reactFlowNode.getBoundingClientRect();
let recentHoveredHandle: Element = null; let recentHoveredHandle: Element;
setPosition({ setPosition({
x: evt.clientX - containerBounds.left, x: evt.clientX - containerBounds.left,
@@ -43,9 +56,9 @@ function onMouseDown(
}); });
setSourceId(nodeId); setSourceId(nodeId);
function resetRecentHandle() { function resetRecentHandle(): void {
if (!recentHoveredHandle) { if (!recentHoveredHandle) {
return false; return;
} }
recentHoveredHandle.classList.remove('valid'); recentHoveredHandle.classList.remove('valid');
@@ -55,14 +68,19 @@ function onMouseDown(
// checks if element below mouse is a handle and returns connection in form of an object { source: 123, target: 312 } // checks if element below mouse is a handle and returns connection in form of an object { source: 123, target: 312 }
function checkElementBelowIsValid(evt: MouseEvent) { function checkElementBelowIsValid(evt: MouseEvent) {
const elementBelow = document.elementFromPoint(evt.clientX, evt.clientY); const elementBelow = document.elementFromPoint(evt.clientX, evt.clientY);
const result: Result = { const result: Result = {
elementBelow, elementBelow,
isValid: false, isValid: false,
connection: { source: null, target: null }, connection: { source: null, target: null },
isHoveringHandle: false isHoveringHandle: false,
}; };
if (elementBelow && (elementBelow.classList.contains('target') || elementBelow.classList.contains('source'))) { if (
elementBelow &&
(elementBelow.classList.contains('target') ||
elementBelow.classList.contains('source'))
) {
let connection: Connection = { source: null, target: null }; let connection: Connection = { source: null, target: null };
if (isTarget) { if (isTarget) {
@@ -89,7 +107,12 @@ function onMouseDown(
y: evt.clientY - containerBounds.top, y: evt.clientY - containerBounds.top,
}); });
const { connection, elementBelow, isValid, isHoveringHandle } = checkElementBelowIsValid(evt); const {
connection,
elementBelow,
isValid,
isHoveringHandle,
} = checkElementBelowIsValid(evt);
if (!isHoveringHandle) { if (!isHoveringHandle) {
return resetRecentHandle(); return resetRecentHandle();
@@ -97,7 +120,7 @@ function onMouseDown(
const isOwnHandle = connection.source === connection.target; const isOwnHandle = connection.source === connection.target;
if (!isOwnHandle) { if (!isOwnHandle && elementBelow) {
recentHoveredHandle = elementBelow; recentHoveredHandle = elementBelow;
elementBelow.classList.add('connecting'); elementBelow.classList.add('connecting');
elementBelow.classList.toggle('valid', isValid); elementBelow.classList.toggle('valid', isValid);
@@ -105,7 +128,7 @@ function onMouseDown(
} }
function onMouseUp(evt: MouseEvent) { function onMouseUp(evt: MouseEvent) {
const { connection, isValid } = checkElementBelowIsValid(evt); const { connection, isValid } = checkElementBelowIsValid(evt);
if (isValid) { if (isValid) {
onConnect(connection); onConnect(connection);
@@ -119,37 +142,51 @@ function onMouseDown(
} }
document.addEventListener('mousemove', onMouseMove); document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp) document.addEventListener('mouseup', onMouseUp);
} }
const BaseHandle = memo(({ const BaseHandle = memo(
type, nodeId, onConnect, position, ({
setSourceId, setPosition, className, type,
id = false, isValidConnection, ...rest nodeId,
}: BaseHandleProps) => { onConnect,
const isTarget = type === 'target';
const handleClasses = cx(
'react-flow__handle',
className,
position, position,
{ source: !isTarget, target: isTarget } setSourceId,
); setPosition,
className,
id = false,
isValidConnection,
...rest
}: BaseHandleProps) => {
const isTarget = type === 'target';
const handleClasses = cx('react-flow__handle', className, position, {
source: !isTarget,
target: isTarget,
});
const nodeIdWithHandleId = id ? `${nodeId}__${id}` : nodeId; const nodeIdWithHandleId = id ? `${nodeId}__${id}` : nodeId;
return ( return (
<div <div
data-nodeid={nodeIdWithHandleId} data-nodeid={nodeIdWithHandleId}
data-handlepos={position} data-handlepos={position}
className={handleClasses} className={handleClasses}
onMouseDown={evt => onMouseDown(evt, onMouseDown={evt =>
nodeIdWithHandleId, setSourceId, setPosition, onMouseDown(
onConnect, isTarget, isValidConnection evt,
)} nodeIdWithHandleId,
{...rest} setSourceId,
/> setPosition,
); onConnect,
}); isTarget,
isValidConnection
)
}
{...rest}
/>
);
}
);
BaseHandle.displayName = 'BaseHandle'; BaseHandle.displayName = 'BaseHandle';
+45 -34
View File
@@ -2,45 +2,56 @@ import React, { memo, useContext } from 'react';
import { useStoreActions, useStoreState } from '../../store/hooks'; import { useStoreActions, useStoreState } from '../../store/hooks';
import BaseHandle from './BaseHandle'; import BaseHandle from './BaseHandle';
import NodeIdContext from '../../contexts/NodeIdContext' import NodeIdContext from '../../contexts/NodeIdContext';
import { HandleType, ElementId, Position, OnConnectParams, OnConnectFunc } from '../../types'; import {
HandleType,
ElementId,
Position,
Connection,
OnConnectFunc,
} from '../../types';
interface HandleProps { interface HandleProps {
type: HandleType, type: HandleType;
position: Position, position: Position;
onConnect?: OnConnectFunc, onConnect?: OnConnectFunc;
isValidConnection?: () => boolean isValidConnection?: () => boolean;
}; }
const Handle = memo(({ const Handle = memo(
onConnect = _ => {}, type = 'source', position = 'top', isValidConnection = () => true, ({
...rest onConnect = _ => {},
}: HandleProps) => { type = 'source',
const nodeId = useContext(NodeIdContext) as ElementId; position = 'top',
const { setPosition, setSourceId } = useStoreActions(a => ({ isValidConnection = () => true,
setPosition: a.setConnectionPosition, ...rest
setSourceId: a.setConnectionSourceId }: HandleProps) => {
})); const nodeId = useContext(NodeIdContext) as ElementId;
const onConnectAction = useStoreState(s => s.onConnect); const { setPosition, setSourceId } = useStoreActions(a => ({
const onConnectExtended = (params: OnConnectParams) => { setPosition: a.setConnectionPosition,
onConnectAction(params); setSourceId: a.setConnectionSourceId,
onConnect(params); }));
}; const onConnectAction = useStoreState(s => s.onConnect);
const onConnectExtended = (params: Connection) => {
onConnectAction(params);
onConnect(params);
};
return ( return (
<BaseHandle <BaseHandle
nodeId={nodeId} nodeId={nodeId}
setPosition={setPosition} setPosition={setPosition}
setSourceId={setSourceId} setSourceId={setSourceId}
onConnect={onConnectExtended} onConnect={onConnectExtended}
type={type} type={type}
position={position} position={position}
isValidConnection={isValidConnection} isValidConnection={isValidConnection}
{...rest} {...rest}
/> />
); );
}); }
);
Handle.displayName = 'Handle'; Handle.displayName = 'Handle';
+1 -1
View File
@@ -7,7 +7,7 @@ const nodeStyles: CSSProperties = {
background: '#ff6060', background: '#ff6060',
padding: 10, padding: 10,
borderRadius: 5, borderRadius: 5,
width: 150 width: 150,
}; };
export default ({ data, style }: NodeProps) => ( export default ({ data, style }: NodeProps) => (
+1 -1
View File
@@ -7,7 +7,7 @@ const nodeStyles: CSSProperties = {
background: '#9999ff', background: '#9999ff',
padding: 10, padding: 10,
borderRadius: 5, borderRadius: 5,
width: 150 width: 150,
}; };
export default ({ data, style }: NodeProps) => ( export default ({ data, style }: NodeProps) => (
+1 -1
View File
@@ -7,7 +7,7 @@ const nodeStyles: CSSProperties = {
background: '#55dd99', background: '#55dd99',
padding: 10, padding: 10,
borderRadius: 5, borderRadius: 5,
width: 150 width: 150,
}; };
export default ({ data, style }: NodeProps) => ( export default ({ data, style }: NodeProps) => (
+208 -106
View File
@@ -1,4 +1,11 @@
import React, { useEffect, useRef, useState, memo, ComponentType } from 'react'; import React, {
useEffect,
useRef,
useState,
memo,
ComponentType,
CSSProperties,
} from 'react';
import { DraggableCore, DraggableEvent } from 'react-draggable'; import { DraggableCore, DraggableEvent } from 'react-draggable';
import cx from 'classnames'; import cx from 'classnames';
import { ResizeObserver } from 'resize-observer'; import { ResizeObserver } from 'resize-observer';
@@ -6,7 +13,28 @@ import { ResizeObserver } from 'resize-observer';
import { getDimensions, isInputDOMNode } from '../../utils'; import { getDimensions, isInputDOMNode } from '../../utils';
import { Provider } from '../../contexts/NodeIdContext'; import { Provider } from '../../contexts/NodeIdContext';
import store from '../../store'; import store from '../../store';
import { NodeComponentProps, Node, XYPosition, HandleElement, Position, Transform, ElementId } from '../../types'; import {
Node,
XYPosition,
HandleElement,
Position,
Transform,
ElementId,
NodeComponentProps,
} from '../../types';
interface WrapNodeProps {
id: ElementId;
type: string;
data: any;
selected: boolean;
transform: Transform;
xPos: number;
yPos: number;
onClick: (node: Node) => void | undefined;
onNodeDragStop: (node: Node) => void;
style?: CSSProperties;
}
const isHandle = (evt: MouseEvent | DraggableEvent) => { const isHandle = (evt: MouseEvent | DraggableEvent) => {
const target = evt.target as HTMLElement; const target = evt.target as HTMLElement;
@@ -14,165 +42,239 @@ const isHandle = (evt: MouseEvent | DraggableEvent) => {
return ( return (
target.className && target.className &&
target.className.includes && target.className.includes &&
(target.className.includes('source') || target.className.includes('target')) (target.className.includes('source') || target.className.includes('target'))
); );
}; };
const getHandleBounds = ( const getHandleBounds = (
selector: string, nodeElement: HTMLDivElement, parentBounds: ClientRect | DOMRect, k: number selector: string,
): HandleElement => { nodeElement: HTMLDivElement,
parentBounds: ClientRect | DOMRect,
k: number
): HandleElement[] | null => {
const handles = nodeElement.querySelectorAll(selector); const handles = nodeElement.querySelectorAll(selector);
if (!handles || !handles.length) { if (!handles || !handles.length) {
return null; return null;
} }
return [].map.call(handles, (handle: HTMLDivElement): HandleElement => { const handlesArray = Array.from(handles) as HTMLDivElement[];
const bounds = handle.getBoundingClientRect();
const dimensions = getDimensions(handle);
const nodeIdAttr = handle.getAttribute('data-nodeid');
const handlePosition = handle.getAttribute('data-handlepos') as unknown as Position;
const nodeIdSplitted = nodeIdAttr.split('__');
let handleId = null; return handlesArray.map(
(handle): HandleElement => {
const bounds = handle.getBoundingClientRect();
const dimensions = getDimensions(handle);
const nodeIdAttr = handle.getAttribute('data-nodeid');
const handlePosition = (handle.getAttribute(
'data-handlepos'
) as unknown) as Position;
const nodeIdSplitted = nodeIdAttr ? nodeIdAttr.split('__') : null;
if (nodeIdSplitted) { let handleId = null;
handleId = (nodeIdSplitted.length ? nodeIdSplitted[1] : nodeIdSplitted) as string;
if (nodeIdSplitted) {
handleId = (nodeIdSplitted.length
? nodeIdSplitted[1]
: nodeIdSplitted) as string;
}
return {
id: handleId,
position: handlePosition,
x: (bounds.left - parentBounds.left) * (1 / k),
y: (bounds.top - parentBounds.top) * (1 / k),
...dimensions,
};
} }
);
return {
id: handleId,
position: handlePosition,
x: (bounds.left - parentBounds.left) * (1 / k),
y: (bounds.top - parentBounds.top) * (1 / k),
...dimensions
};
});
}; };
const onStart = ( const onStart = (
evt: MouseEvent, onClick: (node: Node) => void, id: ElementId, type: string, evt: MouseEvent,
data: any, setOffset: (pos: XYPosition) => void, transform: Transform, position: XYPosition onClick: (node: Node) => void,
): false | void => { id: ElementId,
type: string,
data: any,
setOffset: (pos: XYPosition) => void,
transform: Transform,
position: XYPosition
): false | void => {
if (isInputDOMNode(evt) || isHandle(evt)) { if (isInputDOMNode(evt) || isHandle(evt)) {
return false; return false;
} }
const scaledClient: XYPosition = { const scaledClient: XYPosition = {
x: evt.clientX * (1 / transform[2]), x: evt.clientX * (1 / transform[2]),
y: evt.clientY * (1 / transform[2]) y: evt.clientY * (1 / transform[2]),
}; };
const offsetX = scaledClient.x - position.x - transform[0]; const offsetX = scaledClient.x - position.x - transform[0];
const offsetY = scaledClient.y - position.y - transform[1]; const offsetY = scaledClient.y - position.y - transform[1];
const node = { id, type, position, data }; const node = { id, type, position, data };
store.dispatch.setSelectedElements({ id, type }); store.dispatch.setSelectedElements({ id, type } as Node);
setOffset({ x: offsetX, y: offsetY }); setOffset({ x: offsetX, y: offsetY });
onClick(node); onClick(node);
}; };
const onDrag = ( const onDrag = (
evt: MouseEvent, setDragging: (isDragging: boolean) => void, id: ElementId, offset: XYPosition, evt: MouseEvent,
setDragging: (isDragging: boolean) => void,
id: ElementId,
offset: XYPosition,
transform: Transform transform: Transform
): void => { ): void => {
const scaledClient = { const scaledClient = {
x: evt.clientX * (1 / transform[2]), x: evt.clientX * (1 / transform[2]),
y: evt.clientY * (1 / transform[2]) y: evt.clientY * (1 / transform[2]),
}; };
setDragging(true); setDragging(true);
store.dispatch.updateNodePos({ id, pos: { store.dispatch.updateNodePos({
x: scaledClient.x - transform[0] - offset.x, id,
y: scaledClient.y - transform[1] - offset.y pos: {
}}); x: scaledClient.x - transform[0] - offset.x,
y: scaledClient.y - transform[1] - offset.y,
},
});
}; };
const onStop = ( const onStop = (
onNodeDragStop: (params: Node) => void, isDragging: boolean, setDragging: (isDragging: boolean) => void, id: ElementId, onNodeDragStop: (params: Node) => void,
type: string, position: XYPosition, data: any isDragging: boolean,
setDragging: (isDragging: boolean) => void,
id: ElementId,
type: string,
position: XYPosition,
data: any
): void => { ): void => {
if (isDragging) { if (isDragging) {
setDragging(false); setDragging(false);
onNodeDragStop({ onNodeDragStop({
id, type, position, data id,
}); type,
position,
data,
} as Node);
} }
}; };
export default (NodeComponent: ComponentType<NodeComponentProps>) => { export default (NodeComponent: ComponentType<NodeComponentProps>) => {
const NodeWrapper = memo(({ const NodeWrapper = memo(
id, type, data, transform, ({
xPos, yPos, selected, onClick, id,
onNodeDragStop, style type,
}: NodeComponentProps) => { data,
const nodeElement = useRef<HTMLDivElement>(null); transform,
const [offset, setOffset] = useState({ x: 0, y: 0 }); xPos,
const [isDragging, setDragging] = useState(false); yPos,
selected,
onClick,
onNodeDragStop,
style,
}: WrapNodeProps) => {
const nodeElement = useRef<HTMLDivElement>(null);
const [offset, setOffset] = useState({ x: 0, y: 0 });
const [isDragging, setDragging] = useState(false);
const position = { x: xPos, y: yPos }; const position = { x: xPos, y: yPos };
const nodeClasses = cx('react-flow__node', { selected }); const nodeClasses = cx('react-flow__node', { selected });
const nodeStyle = { zIndex: selected ? 10 : 3, transform: `translate(${xPos}px,${yPos}px)` }; const nodeStyle = {
zIndex: selected ? 10 : 3,
const updateNode = () => { transform: `translate(${xPos}px,${yPos}px)`,
if (!nodeElement.current) {
return false;
}
const storeState = store.getState()
const bounds = nodeElement.current.getBoundingClientRect();
const dimensions = getDimensions(nodeElement.current);
const handleBounds = {
source: getHandleBounds('.source', nodeElement.current, bounds, storeState.transform[2]),
target: getHandleBounds('.target', nodeElement.current, bounds, storeState.transform[2])
}; };
store.dispatch.updateNodeData({ id, ...dimensions, handleBounds });
};
useEffect(() => { const updateNode = (): void => {
if (nodeElement.current) { if (!nodeElement.current) {
updateNode(); return;
const resizeObserver = new ResizeObserver(entries => {
for (let _ of entries) {
updateNode();
}
});
resizeObserver.observe(nodeElement.current);
return () => {
if (resizeObserver && nodeElement.current) {
resizeObserver.unobserve(nodeElement.current);
}
} }
}
}, [nodeElement.current]);
return ( const storeState = store.getState();
<DraggableCore const bounds = nodeElement.current.getBoundingClientRect();
onStart={evt => onStart(evt as MouseEvent, onClick, id, type, data, setOffset, transform, position)} const dimensions = getDimensions(nodeElement.current);
onDrag={evt => onDrag(evt as MouseEvent, setDragging, id, offset, transform)} const handleBounds = {
onStop={() => onStop(onNodeDragStop, isDragging, setDragging, id, type, position, data)} source: getHandleBounds(
scale={transform[2]} '.source',
> nodeElement.current,
<div bounds,
className={nodeClasses} storeState.transform[2]
ref={nodeElement} ),
style={nodeStyle} target: getHandleBounds(
'.target',
nodeElement.current,
bounds,
storeState.transform[2]
),
};
store.dispatch.updateNodeData({ id, ...dimensions, handleBounds });
};
useEffect(() => {
if (nodeElement.current) {
updateNode();
const resizeObserver = new ResizeObserver(entries => {
for (let _ of entries) {
updateNode();
}
});
resizeObserver.observe(nodeElement.current);
return () => {
if (resizeObserver && nodeElement.current) {
resizeObserver.unobserve(nodeElement.current);
}
};
}
return;
}, [nodeElement.current]);
return (
<DraggableCore
onStart={evt =>
onStart(
evt as MouseEvent,
onClick,
id,
type,
data,
setOffset,
transform,
position
)
}
onDrag={evt =>
onDrag(evt as MouseEvent, setDragging, id, offset, transform)
}
onStop={() =>
onStop(
onNodeDragStop,
isDragging,
setDragging,
id,
type,
position,
data
)
}
scale={transform[2]}
> >
<Provider value={id}> <div className={nodeClasses} ref={nodeElement} style={nodeStyle}>
<NodeComponent <Provider value={id}>
id={id} <NodeComponent
data={data} id={id}
type={type} data={data}
style={style} type={type}
selected={selected} style={style}
/> selected={selected}
</Provider> />
</div> </Provider>
</DraggableCore> </div>
); </DraggableCore>
}); );
}
);
NodeWrapper.displayName = 'NodeWrapper'; NodeWrapper.displayName = 'NodeWrapper';
+45 -31
View File
@@ -5,28 +5,30 @@ import { useStoreState, useStoreActions } from '../../store/hooks';
import { isNode } from '../../utils/graph'; import { isNode } from '../../utils/graph';
import { Node, Elements, XYPosition } from '../../types'; import { Node, Elements, XYPosition } from '../../types';
function getStartPositions(elements: Elements) { type StartPositions = { [key: string]: XYPosition };
return elements
.filter(isNode)
.reduce((res, node: Node) => {
const startPosition = {
x: node.__rg.position.x || node.position.x,
y: node.__rg.position.y || node.position.x
};
res[node.id] = startPosition; function getStartPositions(elements: Elements): StartPositions {
const startPositions: StartPositions = {};
return res; return (elements.filter(isNode) as Node[]).reduce((res, node) => {
}, {}); const startPosition = {
x: node.__rg.position.x || node.position.x,
y: node.__rg.position.y || node.position.x,
};
res[node.id] = startPosition;
return res;
}, startPositions);
} }
export default memo(() => { export default memo(() => {
const [offset, setOffset] = useState({ x: 0, y: 0 }); const [offset, setOffset] = useState<XYPosition>({ x: 0, y: 0 });
const [startPositions, setStartPositions] = useState({}); const [startPositions, setStartPositions] = useState<StartPositions>({});
const state = useStoreState(s => ({ const state = useStoreState(s => ({
transform: s.transform, transform: s.transform,
selectedNodesBbox: s.selectedNodesBbox, selectedNodesBbox: s.selectedNodesBbox,
selectedElements: s.selectedElements selectedElements: s.selectedElements,
})); }));
const updateNodePos = useStoreActions(a => a.updateNodePos); const updateNodePos = useStoreActions(a => a.updateNodePos);
const [x, y, k] = state.transform; const [x, y, k] = state.transform;
@@ -35,43 +37,55 @@ export default memo(() => {
const onStart = (evt: MouseEvent) => { const onStart = (evt: MouseEvent) => {
const scaledClient: XYPosition = { const scaledClient: XYPosition = {
x: evt.clientX * (1 / k), x: evt.clientX * (1 / k),
y: evt.clientY * (1 / k) y: evt.clientY * (1 / k),
}; };
const offsetX: number = scaledClient.x - position.x - x; const offsetX: number = scaledClient.x - position.x - x;
const offsetY: number = scaledClient.y - position.y - y; const offsetY: number = scaledClient.y - position.y - y;
const startPositions = getStartPositions(state.selectedElements); const nextStartPositions = getStartPositions(state.selectedElements);
setOffset({ x: offsetX, y: offsetY }); if (nextStartPositions) {
setStartPositions(startPositions); setOffset({ x: offsetX, y: offsetY });
setStartPositions(nextStartPositions);
}
}; };
const onDrag = (evt: MouseEvent) => { const onDrag = (evt: MouseEvent) => {
const scaledClient: XYPosition = { const scaledClient: XYPosition = {
x: evt.clientX * (1 / k), x: evt.clientX * (1 / k),
y: evt.clientY * (1 / k) y: evt.clientY * (1 / k),
}; };
state.selectedElements (state.selectedElements.filter(isNode) as Node[]).forEach(node => {
.filter(isNode) const pos: XYPosition = {
.forEach((node: Node) => { x:
updateNodePos({ id: node.id, pos: { startPositions[node.id].x +
x: startPositions[node.id].x + scaledClient.x - position.x - offset.x - x , scaledClient.x -
y: startPositions[node.id].y + scaledClient.y - position.y - offset.y - y position.x -
}}); offset.x -
}); x,
y:
startPositions[node.id].y +
scaledClient.y -
position.y -
offset.y -
y,
};
updateNodePos({ id: node.id, pos });
});
}; };
return ( return (
<div <div
className="react-flow__nodesselection" className="react-flow__nodesselection"
style={{ style={{
transform: `translate(${x}px,${y}px) scale(${k})` transform: `translate(${x}px,${y}px) scale(${k})`,
}} }}
> >
<ReactDraggable <ReactDraggable
scale={k} scale={k}
onStart={(evt: MouseEvent) => onStart(evt)} onStart={evt => onStart(evt as MouseEvent)}
onDrag={(evt: MouseEvent) => onDrag(evt)} onDrag={evt => onDrag(evt as MouseEvent)}
> >
<div <div
className="react-flow__nodesselection-rect" className="react-flow__nodesselection-rect"
@@ -79,7 +93,7 @@ export default memo(() => {
width: state.selectedNodesBbox.width, width: state.selectedNodesBbox.width,
height: state.selectedNodesBbox.height, height: state.selectedNodesBbox.height,
top: state.selectedNodesBbox.y, top: state.selectedNodesBbox.y,
left: state.selectedNodesBbox.x left: state.selectedNodesBbox.x,
}} }}
/> />
</ReactDraggable> </ReactDraggable>
+35 -27
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState, memo, MouseEvent } from 'react'; import React, { useEffect, useRef, useState, memo } from 'react';
import { useStoreActions } from '../../store/hooks'; import { useStoreActions } from '../../store/hooks';
import { SelectionRect } from '../../types'; import { SelectionRect } from '../../types';
@@ -10,7 +10,7 @@ const initialRect: SelectionRect = {
y: 0, y: 0,
width: 0, width: 0,
height: 0, height: 0,
draw: false draw: false,
}; };
function getMousePosition(evt: MouseEvent) { function getMousePosition(evt: MouseEvent) {
@@ -28,33 +28,33 @@ function getMousePosition(evt: MouseEvent) {
} }
export default memo(() => { export default memo(() => {
const selectionPane = useRef(null); const selectionPane = useRef<HTMLDivElement>(null);
const [rect, setRect] = useState(initialRect); const [rect, setRect] = useState(initialRect);
const setSelection = useStoreActions(a => a.setSelection); const setSelection = useStoreActions(a => a.setSelection);
const updateSelection = useStoreActions(a => a.updateSelection); const updateSelection = useStoreActions(a => a.updateSelection);
const setNodesSelection = useStoreActions(a => a.setNodesSelection); const setNodesSelection = useStoreActions(a => a.setNodesSelection);
useEffect(() => { useEffect(() => {
function onMouseDown(evt: MouseEvent) { function onMouseDown(evt: MouseEvent): void {
const mousePos = getMousePosition(evt); const mousePos = getMousePosition(evt);
if (!mousePos) { if (!mousePos) {
return false; return;
} }
setRect((currentRect) => ({ setRect(currentRect => ({
...currentRect, ...currentRect,
startX: mousePos.x, startX: mousePos.x,
startY: mousePos.y, startY: mousePos.y,
x: mousePos.x, x: mousePos.x,
y: mousePos.y, y: mousePos.y,
draw: true draw: true,
})); }));
setSelection(true); setSelection(true);
} }
function onMouseMove(evt: MouseEvent) { function onMouseMove(evt: MouseEvent): void {
setRect((currentRect) => { setRect(currentRect => {
if (!currentRect.draw) { if (!currentRect.draw) {
return currentRect; return currentRect;
} }
@@ -70,8 +70,12 @@ export default memo(() => {
...currentRect, ...currentRect,
x: negativeX ? mousePos.x : currentRect.x, x: negativeX ? mousePos.x : currentRect.x,
y: negativeY ? mousePos.y : currentRect.y, y: negativeY ? mousePos.y : currentRect.y,
width: negativeX ? currentRect.startX - mousePos.x : mousePos.x - currentRect.startX, width: negativeX
height: negativeY ? currentRect.startY - mousePos.y : mousePos.y - currentRect.startY, ? currentRect.startX - mousePos.x
: mousePos.x - currentRect.startX,
height: negativeY
? currentRect.startY - mousePos.y
: mousePos.y - currentRect.startY,
}; };
updateSelection(nextRect); updateSelection(nextRect);
@@ -81,40 +85,44 @@ export default memo(() => {
} }
function onMouseUp() { function onMouseUp() {
setRect((currentRect) => { setRect(currentRect => {
setNodesSelection({ isActive: true, selection: currentRect }); setNodesSelection({ isActive: true, selection: currentRect });
setSelection(false); setSelection(false);
return { return {
...currentRect, ...currentRect,
draw: false draw: false,
}; };
}); });
} }
selectionPane.current.addEventListener('mousedown', onMouseDown); if (selectionPane.current) {
selectionPane.current.addEventListener('mousemove', onMouseMove); selectionPane.current.addEventListener('mousedown', onMouseDown);
selectionPane.current.addEventListener('mouseup', onMouseUp); selectionPane.current.addEventListener('mousemove', onMouseMove);
selectionPane.current.addEventListener('mouseup', onMouseUp);
return () => { return () => {
selectionPane.current.removeEventListener('mousedown', onMouseDown); if (!selectionPane.current) {
selectionPane.current.removeEventListener('mousemove', onMouseMove); return;
selectionPane.current.removeEventListener('mouseup', onMouseUp); }
}; selectionPane.current.removeEventListener('mousedown', onMouseDown);
}, []); selectionPane.current.removeEventListener('mousemove', onMouseMove);
selectionPane.current.removeEventListener('mouseup', onMouseUp);
};
}
return;
}, [selectionPane.current]);
return ( return (
<div <div className="react-flow__selectionpane" ref={selectionPane}>
className="react-flow__selectionpane"
ref={selectionPane}
>
{rect.draw && ( {rect.draw && (
<div <div
className="react-flow__selection" className="react-flow__selection"
style={{ style={{
width: rect.width, width: rect.width,
height: rect.height, height: rect.height,
transform: `translate(${rect.x}px, ${rect.y}px)` transform: `translate(${rect.x}px, ${rect.y}px)`,
}} }}
/> />
)} )}
+156 -92
View File
@@ -3,7 +3,15 @@ import React, { memo, SVGAttributes } from 'react';
import { useStoreState } from '../../store/hooks'; import { useStoreState } from '../../store/hooks';
import ConnectionLine from '../../components/ConnectionLine/index'; import ConnectionLine from '../../components/ConnectionLine/index';
import { isEdge } from '../../utils/graph'; import { isEdge } from '../../utils/graph';
import { XYPosition, Position, Edge, Node, ElementId, Transform, HandleElement } from '../../types'; import {
XYPosition,
Position,
Edge,
Node,
ElementId,
Transform,
HandleElement,
} from '../../types';
interface EdgeRendererProps { interface EdgeRendererProps {
width: number; width: number;
@@ -12,69 +20,82 @@ interface EdgeRendererProps {
connectionLineStyle?: SVGAttributes<{}>; connectionLineStyle?: SVGAttributes<{}>;
connectionLineType?: string; connectionLineType?: string;
onElementClick?: () => void; onElementClick?: () => void;
}; }
interface EdgeRendererState { interface EdgeRendererState {
nodes: Node[]; nodes: Node[];
edges: Edge[]; edges: Edge[];
transform: Transform; transform: Transform;
selectedElements: any; selectedElements: any;
connectionSourceId: ElementId | null; connectionSourceId: ElementId | null;
position: XYPosition; position: XYPosition;
}; }
interface EdgePositions { interface EdgePositions {
sourceX: number; sourceX: number;
sourceY: number; sourceY: number;
targetX: number; targetX: number;
targetY: number; targetY: number;
}; }
function getHandlePosition(position: Position, node: Node, handle: any | null = null): XYPosition { function getHandlePosition(
position: Position,
node: Node,
handle: any | null = null
): XYPosition {
if (!handle) { if (!handle) {
switch (position) { switch (position) {
case 'top': return { case 'top':
x: node.__rg.width / 2, return {
y: 0 x: node.__rg.width / 2,
}; y: 0,
case 'right': return { };
x: node.__rg.width, case 'right':
y: node.__rg.height / 2 return {
}; x: node.__rg.width,
case 'bottom': return { y: node.__rg.height / 2,
x: node.__rg.width / 2, };
y: node.__rg.height case 'bottom':
}; return {
case 'left': return { x: node.__rg.width / 2,
x: 0, y: node.__rg.height,
y: node.__rg.height / 2 };
}; case 'left':
return {
x: 0,
y: node.__rg.height / 2,
};
} }
return null;
} }
switch (position) { switch (position) {
case 'top': return { case 'top':
x: handle.x + (handle.width / 2), return {
y: handle.y x: handle.x + handle.width / 2,
}; y: handle.y,
case 'right': return { };
x: handle.x + handle.width, case 'right':
y: handle.y + (handle.height / 2) return {
}; x: handle.x + handle.width,
case 'bottom': return { y: handle.y + handle.height / 2,
x: handle.x + (handle.width / 2), };
y: handle.y + handle.height case 'bottom':
}; return {
case 'left': return { x: handle.x + handle.width / 2,
x: handle.x, y: handle.y + handle.height,
y: handle.y + (handle.height / 2) };
}; case 'left':
return {
x: handle.x,
y: handle.y + handle.height / 2,
};
} }
} }
function getHandle(bounds: HandleElement[], handleId: ElementId): HandleElement | null { function getHandle(
bounds: HandleElement[],
handleId: ElementId | null
): HandleElement | null | undefined {
let handle = null; let handle = null;
if (!bounds) { if (!bounds) {
@@ -83,7 +104,7 @@ function getHandle(bounds: HandleElement[], handleId: ElementId): HandleElement
// there is no handleId when there are no multiple handles/ handles with ids // there is no handleId when there are no multiple handles/ handles with ids
// so we just pick the first one // so we just pick the first one
if (bounds.length === 1 || !handleId) { if (bounds.length === 1 || !handleId) {
handle = bounds[0]; handle = bounds[0];
} else if (handleId) { } else if (handleId) {
handle = bounds.find(d => d.id === handleId); handle = bounds.find(d => d.id === handleId);
@@ -93,23 +114,42 @@ function getHandle(bounds: HandleElement[], handleId: ElementId): HandleElement
} }
function getEdgePositions( function getEdgePositions(
sourceNode: Node, sourceHandle: HandleElement, sourcePosition: Position, sourceNode: Node,
targetNode: Node, targetHandle: HandleElement, targetPosition: Position sourceHandle: HandleElement | unknown,
sourcePosition: Position,
targetNode: Node,
targetHandle: HandleElement | unknown,
targetPosition: Position
): EdgePositions { ): EdgePositions {
const sourceHandlePos = getHandlePosition(sourcePosition, sourceNode, sourceHandle) const sourceHandlePos = getHandlePosition(
sourcePosition,
sourceNode,
sourceHandle
);
const sourceX = sourceNode.__rg.position.x + sourceHandlePos.x; const sourceX = sourceNode.__rg.position.x + sourceHandlePos.x;
const sourceY = sourceNode.__rg.position.y + sourceHandlePos.y; const sourceY = sourceNode.__rg.position.y + sourceHandlePos.y;
const targetHandlePos = getHandlePosition(targetPosition, targetNode, targetHandle); const targetHandlePos = getHandlePosition(
targetPosition,
targetNode,
targetHandle
);
const targetX = targetNode.__rg.position.x + targetHandlePos.x; const targetX = targetNode.__rg.position.x + targetHandlePos.x;
const targetY = targetNode.__rg.position.y + targetHandlePos.y; const targetY = targetNode.__rg.position.y + targetHandlePos.y;
return { return {
sourceX, sourceY, targetX, targetY sourceX,
sourceY,
targetX,
targetY,
}; };
} }
function renderEdge(edge: Edge, props: EdgeRendererProps, state: EdgeRendererState) { function renderEdge(
edge: Edge,
props: EdgeRendererProps,
state: EdgeRendererState
) {
const edgeType = edge.type || 'default'; const edgeType = edge.type || 'default';
const hasSourceHandleId = edge.source.includes('__'); const hasSourceHandleId = edge.source.includes('__');
@@ -133,14 +173,24 @@ function renderEdge(edge: Edge, props: EdgeRendererProps, state: EdgeRendererSta
} }
const EdgeComponent = props.edgeTypes[edgeType] || props.edgeTypes.default; const EdgeComponent = props.edgeTypes[edgeType] || props.edgeTypes.default;
const sourceHandle = getHandle(sourceNode.__rg.handleBounds.source, sourceHandleId); const sourceHandle = getHandle(
const targetHandle = getHandle(targetNode.__rg.handleBounds.target, targetHandleId); sourceNode.__rg.handleBounds.source,
sourceHandleId
);
const targetHandle = getHandle(
targetNode.__rg.handleBounds.target,
targetHandleId
);
const sourcePosition = sourceHandle ? sourceHandle.position : 'bottom'; const sourcePosition = sourceHandle ? sourceHandle.position : 'bottom';
const targetPosition = targetHandle ? targetHandle.position : 'top'; const targetPosition = targetHandle ? targetHandle.position : 'top';
const { sourceX, sourceY, targetX, targetY } = getEdgePositions( const { sourceX, sourceY, targetX, targetY } = getEdgePositions(
sourceNode, sourceHandle, sourcePosition, sourceNode,
targetNode, targetHandle, targetPosition sourceHandle,
sourcePosition,
targetNode,
targetHandle,
targetPosition
); );
const selected = state.selectedElements const selected = state.selectedElements
.filter(isEdge) .filter(isEdge)
@@ -169,47 +219,61 @@ function renderEdge(edge: Edge, props: EdgeRendererProps, state: EdgeRendererSta
); );
} }
const EdgeRenderer = memo(({ const EdgeRenderer = memo(
width, height, connectionLineStyle, connectionLineType, ...rest ({
}: EdgeRendererProps) => { width,
const state: EdgeRendererState = useStoreState(s => ({ height,
nodes: s.nodes, connectionLineStyle,
edges: s.edges, connectionLineType,
transform: s.transform, ...rest
selectedElements: s.selectedElements, }: EdgeRendererProps) => {
connectionSourceId: s.connectionSourceId, const state: EdgeRendererState = useStoreState(s => ({
position: s.connectionPosition nodes: s.nodes,
})); edges: s.edges,
if (!width) { transform: s.transform,
return null; selectedElements: s.selectedElements,
connectionSourceId: s.connectionSourceId,
position: s.connectionPosition,
}));
if (!width) {
return null;
}
const { transform, edges, nodes, connectionSourceId, position } = state;
const transformStyle = `translate(${transform[0]},${transform[1]}) scale(${transform[2]})`;
return (
<svg width={width} height={height} className="react-flow__edges">
<g transform={transformStyle}>
{edges.map((e: Edge) =>
renderEdge(
e,
{
width,
height,
connectionLineStyle,
connectionLineType,
...rest,
},
state
)
)}
{connectionSourceId && (
<ConnectionLine
nodes={nodes}
connectionSourceId={connectionSourceId}
connectionPositionX={position.x}
connectionPositionY={position.y}
transform={transform}
connectionLineStyle={connectionLineStyle}
connectionLineType={connectionLineType}
/>
)}
</g>
</svg>
);
} }
);
const { transform, edges, nodes, connectionSourceId, position } = state;
const transformStyle = `translate(${transform[0]},${transform[1]}) scale(${transform[2]})`;
return (
<svg
width={width}
height={height}
className="react-flow__edges"
>
<g transform={transformStyle}>
{edges.map((e: Edge) => renderEdge(e, { width, height, connectionLineStyle, connectionLineType, ...rest }, state))}
{connectionSourceId && (
<ConnectionLine
nodes={nodes}
connectionSourceId={connectionSourceId}
connectionPositionX={position.x}
connectionPositionY={position.y}
transform={transform}
connectionLineStyle={connectionLineStyle}
connectionLineType={connectionLineType}
/>
)}
</g>
</svg>
);
});
EdgeRenderer.displayName = 'EdgeRenderer'; EdgeRenderer.displayName = 'EdgeRenderer';
+15 -9
View File
@@ -4,25 +4,31 @@ import StraightEdge from '../../components/Edges/StraightEdge';
import BezierEdge from '../../components/Edges/BezierEdge'; import BezierEdge from '../../components/Edges/BezierEdge';
import wrapEdge from '../../components/Edges/wrapEdge'; import wrapEdge from '../../components/Edges/wrapEdge';
import { EdgeTypesType, EdgeWrapperProps } from '../../types'; import { EdgeTypesType, EdgeCompProps } from '../../types';
export function createEdgeTypes(edgeTypes: EdgeTypesType): EdgeTypesType{ export function createEdgeTypes(edgeTypes: EdgeTypesType): EdgeTypesType {
const standardTypes: EdgeTypesType = { const standardTypes: EdgeTypesType = {
default: wrapEdge((edgeTypes.default || BezierEdge) as ComponentType<EdgeWrapperProps>), default: wrapEdge((edgeTypes.default || BezierEdge) as ComponentType<
straight: wrapEdge((edgeTypes.bezier || StraightEdge) as ComponentType<EdgeWrapperProps>) EdgeCompProps
>),
straight: wrapEdge((edgeTypes.bezier || StraightEdge) as ComponentType<
EdgeCompProps
>),
}; };
const specialTypes: EdgeTypesType = Object const wrappedTypes = {} as EdgeTypesType;
.keys(edgeTypes) const specialTypes: EdgeTypesType = Object.keys(edgeTypes)
.filter(k => !['default', 'bezier'].includes(k)) .filter(k => !['default', 'bezier'].includes(k))
.reduce((res, key) => { .reduce((res, key) => {
res[key] = wrapEdge((edgeTypes[key] || BezierEdge) as ComponentType<EdgeWrapperProps>); res[key] = wrapEdge((edgeTypes[key] || BezierEdge) as ComponentType<
EdgeCompProps
>);
return res; return res;
}, {}); }, wrappedTypes);
return { return {
...standardTypes, ...standardTypes,
...specialTypes ...specialTypes,
}; };
} }
+132 -96
View File
@@ -9,117 +9,153 @@ import BackgroundGrid from '../../components/BackgroundGrid';
import useKeyPress from '../../hooks/useKeyPress'; import useKeyPress from '../../hooks/useKeyPress';
import useD3Zoom from '../../hooks/useD3Zoom'; import useD3Zoom from '../../hooks/useD3Zoom';
import useGlobalKeyHandler from '../../hooks/useGlobalKeyHandler'; import useGlobalKeyHandler from '../../hooks/useGlobalKeyHandler';
import useElementUpdater from '../../hooks/useElementUpdater' import useElementUpdater from '../../hooks/useElementUpdater';
import { getDimensions } from '../../utils'; import { getDimensions } from '../../utils';
import { fitView, zoomIn, zoomOut } from '../../utils/graph'; import { fitView, zoomIn, zoomOut } from '../../utils/graph';
import { Elements, NodeTypesType, EdgeTypesType, GridType, OnLoadFunc } from '../../types' import {
Elements,
NodeTypesType,
EdgeTypesType,
GridType,
OnLoadFunc,
} from '../../types';
export interface GraphViewProps { export interface GraphViewProps {
elements: Elements, elements: Elements;
onElementClick: () => void, onElementClick: () => void;
onElementsRemove: (elements: Elements) => void, onElementsRemove: (elements: Elements) => void;
onNodeDragStop: () => void, onNodeDragStop: () => void;
onConnect: () => void, onConnect: () => void;
onLoad: OnLoadFunc, onLoad: OnLoadFunc;
onMove: () => void, onMove: () => void;
selectionKeyCode: number, selectionKeyCode: number;
nodeTypes: NodeTypesType, nodeTypes: NodeTypesType;
edgeTypes: EdgeTypesType, edgeTypes: EdgeTypesType;
connectionLineType: string, connectionLineType: string;
connectionLineStyle: SVGAttributes<{}>, connectionLineStyle: SVGAttributes<{}>;
deleteKeyCode: number, deleteKeyCode: number;
showBackground: boolean, showBackground: boolean;
backgroundGap: number, backgroundGap: number;
backgroundColor: string, backgroundColor: string;
backgroundType: GridType, backgroundType: GridType;
}; snapToGrid: boolean;
snapGrid: [number, number];
}
const GraphView = memo(({ const GraphView = memo(
nodeTypes, edgeTypes, onMove, onLoad, ({
onElementClick, onNodeDragStop, connectionLineType, connectionLineStyle, nodeTypes,
selectionKeyCode, onElementsRemove, deleteKeyCode, elements, edgeTypes,
showBackground, backgroundGap, backgroundColor, backgroundType, onMove,
onConnect onLoad,
}: GraphViewProps) => { onElementClick,
const zoomPane = useRef<HTMLDivElement>(null); onNodeDragStop,
const rendererNode = useRef<HTMLDivElement>(null); connectionLineType,
const state = useStoreState(s => ({ connectionLineStyle,
width: s.width, selectionKeyCode,
height: s.height, onElementsRemove,
nodes: s.nodes, deleteKeyCode,
edges: s.edges, elements,
d3Initialised: s.d3Initialised, showBackground,
nodesSelectionActive: s.nodesSelectionActive backgroundGap,
})); backgroundColor,
const updateSize = useStoreActions(actions => actions.updateSize); backgroundType,
const setNodesSelection = useStoreActions(actions => actions.setNodesSelection); onConnect,
const setOnConnect = useStoreActions(a => a.setOnConnect); snapToGrid,
const selectionKeyPressed = useKeyPress(selectionKeyCode); snapGrid,
}: GraphViewProps) => {
const zoomPane = useRef<HTMLDivElement>(null);
const rendererNode = useRef<HTMLDivElement>(null);
const state = useStoreState(s => ({
width: s.width,
height: s.height,
nodes: s.nodes,
edges: s.edges,
d3Initialised: s.d3Initialised,
nodesSelectionActive: s.nodesSelectionActive,
}));
const updateSize = useStoreActions(actions => actions.updateSize);
const setNodesSelection = useStoreActions(
actions => actions.setNodesSelection
);
const setOnConnect = useStoreActions(a => a.setOnConnect);
const setSnapGrid = useStoreActions(actions => actions.setSnapGrid);
const onZoomPaneClick = () => setNodesSelection({ isActive: false }); const selectionKeyPressed = useKeyPress(selectionKeyCode);
const updateDimensions = () => { const onZoomPaneClick = () => setNodesSelection({ isActive: false });
const size = getDimensions(rendererNode.current);
updateSize(size);
};
useEffect(() => { const updateDimensions = () => {
updateDimensions(); if (!rendererNode.current) {
setOnConnect(onConnect); return;
window.onresize = updateDimensions; }
return () => { const size = getDimensions(rendererNode.current);
window.onresize = null; updateSize(size);
}; };
}, []);
useD3Zoom(zoomPane, onMove, selectionKeyPressed); useEffect(() => {
updateDimensions();
setOnConnect(onConnect);
window.onresize = updateDimensions;
useEffect(() => { return () => {
if (state.d3Initialised) { window.onresize = null;
onLoad({ };
fitView, }, []);
zoomIn,
zoomOut
});
}
}, [state.d3Initialised]);
useGlobalKeyHandler({ onElementsRemove, deleteKeyCode }); useD3Zoom(zoomPane, onMove, selectionKeyPressed);
useElementUpdater(elements);
return ( useEffect(() => {
<div className="react-flow__renderer" ref={rendererNode}> if (state.d3Initialised) {
{showBackground && ( onLoad({
<BackgroundGrid fitView,
gap={backgroundGap} zoomIn,
color={backgroundColor} zoomOut,
backgroundType={backgroundType} });
}
}, [state.d3Initialised]);
useEffect(() => {
setSnapGrid({ snapToGrid, snapGrid });
}, [snapToGrid]);
useGlobalKeyHandler({ onElementsRemove, deleteKeyCode });
useElementUpdater(elements);
return (
<div className="react-flow__renderer" ref={rendererNode}>
{showBackground && (
<BackgroundGrid
gap={backgroundGap}
color={backgroundColor}
backgroundType={backgroundType}
/>
)}
<NodeRenderer
nodeTypes={nodeTypes}
onElementClick={onElementClick}
onNodeDragStop={onNodeDragStop}
/> />
)} <EdgeRenderer
<NodeRenderer width={state.width}
nodeTypes={nodeTypes} height={state.height}
onElementClick={onElementClick} edgeTypes={edgeTypes}
onNodeDragStop={onNodeDragStop} onElementClick={onElementClick}
/> connectionLineType={connectionLineType}
<EdgeRenderer connectionLineStyle={connectionLineStyle}
width={state.width} />
height={state.height} {selectionKeyPressed && <UserSelection />}
edgeTypes={edgeTypes} {state.nodesSelectionActive && <NodesSelection />}
onElementClick={onElementClick} <div
connectionLineType={connectionLineType} className="react-flow__zoompane"
connectionLineStyle={connectionLineStyle} onClick={onZoomPaneClick}
/> ref={zoomPane}
{selectionKeyPressed && <UserSelection />} />
{state.nodesSelectionActive && <NodesSelection />} </div>
<div );
className="react-flow__zoompane" }
onClick={onZoomPaneClick} );
ref={zoomPane}
/>
</div>
);
});
GraphView.displayName = 'GraphView'; GraphView.displayName = 'GraphView';
+24 -13
View File
@@ -2,28 +2,40 @@ import React, { memo, ComponentType } from 'react';
import { useStoreState } from '../../store/hooks'; import { useStoreState } from '../../store/hooks';
import { isNode } from '../../utils/graph'; import { isNode } from '../../utils/graph';
import { Node, Transform, NodeTypesType, NodeComponentProps, } from '../../types'; import {
Node,
Transform,
NodeTypesType,
NodeComponentProps,
} from '../../types';
interface NodeRendererProps { interface NodeRendererProps {
nodeTypes: NodeTypesType; nodeTypes: NodeTypesType;
onElementClick: () => void; onElementClick: () => void;
onNodeDragStop: () => void; onNodeDragStop: () => void;
}; }
interface NodeRendererState { interface NodeRendererState {
nodes: Node[]; nodes: Node[];
transform: Transform; transform: Transform;
selectedElements: any; selectedElements: any;
}; }
function renderNode(node: Node, props: NodeRendererProps, state: NodeRendererState) { function renderNode(
node: Node,
props: NodeRendererProps,
state: NodeRendererState
) {
const nodeType = node.type || 'default'; const nodeType = node.type || 'default';
if (!props.nodeTypes[nodeType]) { if (!props.nodeTypes[nodeType]) {
console.warn(`No node type found for type "${nodeType}". Using fallback type "default".`); console.warn(
`No node type found for type "${nodeType}". Using fallback type "default".`
);
} }
const NodeComponent = (props.nodeTypes[nodeType] || props.nodeTypes.default) as ComponentType<NodeComponentProps>; const NodeComponent = (props.nodeTypes[nodeType] ||
props.nodeTypes.default) as ComponentType<NodeComponentProps>;
const selected = state.selectedElements const selected = state.selectedElements
.filter(isNode) .filter(isNode)
.map((e: Node) => e.id) .map((e: Node) => e.id)
@@ -33,7 +45,7 @@ function renderNode(node: Node, props: NodeRendererProps, state: NodeRendererSta
<NodeComponent <NodeComponent
key={node.id} key={node.id}
id={node.id} id={node.id}
type={node.type} type={nodeType}
data={node.data} data={node.data}
xPos={node.__rg.position.x} xPos={node.__rg.position.x}
yPos={node.__rg.position.y} yPos={node.__rg.position.y}
@@ -50,17 +62,16 @@ const NodeRenderer = memo((props: NodeRendererProps) => {
const state: NodeRendererState = useStoreState(s => ({ const state: NodeRendererState = useStoreState(s => ({
nodes: s.nodes, nodes: s.nodes,
transform: s.transform, transform: s.transform,
selectedElements: s.selectedElements selectedElements: s.selectedElements,
})); }));
const { transform, nodes } = state; const { transform, nodes } = state;
const transformStyle = { transform : `translate(${transform[0]}px,${transform[1]}px) scale(${transform[2]})` }; const transformStyle = {
transform: `translate(${transform[0]}px,${transform[1]}px) scale(${transform[2]})`,
};
return ( return (
<div <div className="react-flow__nodes" style={transformStyle}>
className="react-flow__nodes"
style={transformStyle}
>
{nodes.map(node => renderNode(node, props, state))} {nodes.map(node => renderNode(node, props, state))}
</div> </div>
); );
+17 -9
View File
@@ -6,24 +6,32 @@ import OutputNode from '../../components/Nodes/OutputNode';
import wrapNode from '../../components/Nodes/wrapNode'; import wrapNode from '../../components/Nodes/wrapNode';
import { NodeTypesType, NodeComponentProps } from '../../types'; import { NodeTypesType, NodeComponentProps } from '../../types';
export function createNodeTypes(nodeTypes: NodeTypesType): NodeTypesType { export function createNodeTypes(nodeTypes: NodeTypesType): NodeTypesType {
const standardTypes: NodeTypesType = { const standardTypes: NodeTypesType = {
input: wrapNode((nodeTypes.input || InputNode) as ComponentType<NodeComponentProps>), input: wrapNode((nodeTypes.input || InputNode) as ComponentType<
default: wrapNode((nodeTypes.default || DefaultNode) as ComponentType<NodeComponentProps>), NodeComponentProps
output: wrapNode((nodeTypes.output || OutputNode) as ComponentType<NodeComponentProps>) >),
default: wrapNode((nodeTypes.default || DefaultNode) as ComponentType<
NodeComponentProps
>),
output: wrapNode((nodeTypes.output || OutputNode) as ComponentType<
NodeComponentProps
>),
}; };
const specialTypes: NodeTypesType = Object const wrappedTypes = {} as NodeTypesType;
.keys(nodeTypes) const specialTypes: NodeTypesType = Object.keys(nodeTypes)
.filter(k => !['input', 'default', 'output'].includes(k)) .filter(k => !['input', 'default', 'output'].includes(k))
.reduce((res, key) => { .reduce((res, key) => {
res[key] = wrapNode((nodeTypes[key] || DefaultNode) as ComponentType<NodeComponentProps>); res[key] = wrapNode((nodeTypes[key] || DefaultNode) as ComponentType<
NodeComponentProps
>);
return res; return res;
}, {}); }, wrappedTypes);
return { return {
...standardTypes, ...standardTypes,
...specialTypes ...specialTypes,
}; };
} }
+60 -34
View File
@@ -1,7 +1,7 @@
import React, { useMemo, CSSProperties, ReactNode, SVGAttributes } from 'react'; import React, { useMemo, SVGAttributes, HTMLAttributes } from 'react';
import { StoreProvider } from 'easy-peasy'; import { StoreProvider } from 'easy-peasy';
const nodeEnv: string = (process.env.NODE_ENV as string); const nodeEnv: string = process.env.NODE_ENV as string;
if (nodeEnv !== 'production') { if (nodeEnv !== 'production') {
const whyDidYouRender = require('@welldone-software/why-did-you-render'); const whyDidYouRender = require('@welldone-software/why-did-you-render');
@@ -18,39 +18,61 @@ import StraightEdge from '../../components/Edges/StraightEdge';
import StepEdge from '../../components/Edges/StepEdge'; import StepEdge from '../../components/Edges/StepEdge';
import { createEdgeTypes } from '../EdgeRenderer/utils'; import { createEdgeTypes } from '../EdgeRenderer/utils';
import store from '../../store'; import store from '../../store';
import { Elements, NodeTypesType, EdgeTypesType, GridType, OnLoadFunc } from '../../types'; import {
Elements,
NodeTypesType,
EdgeTypesType,
GridType,
OnLoadFunc,
} from '../../types';
import '../../style.css'; import '../../style.css';
export interface ReactFlowProps { export interface ReactFlowProps
elements: Elements, extends Omit<HTMLAttributes<HTMLDivElement>, 'onLoad'> {
style?: CSSProperties, elements: Elements;
className?: string, onElementClick: () => void;
children?: ReactNode[], onElementsRemove: (elements: Elements) => void;
onElementClick: () => void, onNodeDragStop: () => void;
onElementsRemove: (elements: Elements) => void, onConnect: () => void;
onNodeDragStop: () => void, onLoad: OnLoadFunc;
onConnect: () => void, onMove: () => void;
onLoad: OnLoadFunc, nodeTypes: NodeTypesType;
onMove: () => void, edgeTypes: EdgeTypesType;
nodeTypes: NodeTypesType, connectionLineType: string;
edgeTypes: EdgeTypesType, connectionLineStyle: SVGAttributes<{}>;
connectionLineType: string, deleteKeyCode: number;
connectionLineStyle: SVGAttributes<{}>, selectionKeyCode: number;
deleteKeyCode: number, showBackground: boolean;
selectionKeyCode: number, backgroundGap: number;
showBackground: boolean, backgroundColor: string;
backgroundGap: number, backgroundType: GridType;
backgroundColor: string, snapToGrid: boolean;
backgroundType: GridType snapGrid: [16, 16];
}; }
const ReactFlow = ({ const ReactFlow = ({
style, onElementClick, elements, children, style,
nodeTypes, edgeTypes, onLoad, onMove, onElementClick,
onElementsRemove, onConnect, onNodeDragStop, connectionLineType, elements,
connectionLineStyle, deleteKeyCode, selectionKeyCode, children,
showBackground, backgroundGap, backgroundType, backgroundColor nodeTypes,
edgeTypes,
onLoad,
onMove,
onElementsRemove,
onConnect,
onNodeDragStop,
connectionLineType,
connectionLineStyle,
deleteKeyCode,
selectionKeyCode,
showBackground,
backgroundGap,
backgroundType,
backgroundColor,
snapToGrid,
snapGrid,
}: ReactFlowProps) => { }: ReactFlowProps) => {
const nodeTypesParsed = useMemo(() => createNodeTypes(nodeTypes), []); const nodeTypesParsed = useMemo(() => createNodeTypes(nodeTypes), []);
const edgeTypesParsed = useMemo(() => createEdgeTypes(edgeTypes), []); const edgeTypesParsed = useMemo(() => createEdgeTypes(edgeTypes), []);
@@ -76,6 +98,8 @@ const ReactFlow = ({
backgroundGap={backgroundGap} backgroundGap={backgroundGap}
showBackground={showBackground} showBackground={showBackground}
backgroundType={backgroundType} backgroundType={backgroundType}
snapToGrid={snapToGrid}
snapGrid={snapGrid}
/> />
{children} {children}
</StoreProvider> </StoreProvider>
@@ -90,17 +114,17 @@ ReactFlow.defaultProps = {
onElementsRemove: () => {}, onElementsRemove: () => {},
onNodeDragStop: () => {}, onNodeDragStop: () => {},
onConnect: () => {}, onConnect: () => {},
onLoad: () => {}, onLoad: () => {},
onMove: () => {}, onMove: () => {},
nodeTypes: { nodeTypes: {
input: InputNode, input: InputNode,
default: DefaultNode, default: DefaultNode,
output: OutputNode output: OutputNode,
}, },
edgeTypes: { edgeTypes: {
default: BezierEdge, default: BezierEdge,
straight: StraightEdge, straight: StraightEdge,
step: StepEdge step: StepEdge,
}, },
connectionLineType: 'bezier', connectionLineType: 'bezier',
connectionLineStyle: {}, connectionLineStyle: {},
@@ -109,7 +133,9 @@ ReactFlow.defaultProps = {
backgroundColor: '#eee', backgroundColor: '#eee',
backgroundGap: 24, backgroundGap: 24,
showBackground: true, showBackground: true,
backgroundType: GridType.Dots backgroundType: GridType.Dots,
snapToGrid: false,
snapGrid: [16, 16],
}; };
export default ReactFlow; export default ReactFlow;
+1 -1
View File
@@ -2,7 +2,7 @@ import { createContext } from 'react';
import { ElementId } from '../types'; import { ElementId } from '../types';
type ContextProps = ElementId | null; type ContextProps = ElementId | null;
export const NodeIdContext = createContext<Partial<ContextProps>>(null); export const NodeIdContext = createContext<Partial<ContextProps>>(null);
export const Provider = NodeIdContext.Provider; export const Provider = NodeIdContext.Provider;
+3 -2
View File
@@ -3,11 +3,12 @@ declare module '*.css' {
export default content; export default content;
} }
interface SvgrComponent extends React.StatelessComponent<React.SVGAttributes<SVGElement>> {} interface SvgrComponent
extends React.StatelessComponent<React.SVGAttributes<SVGElement>> {}
declare module '*.svg' { declare module '*.svg' {
const svgUrl: string; const svgUrl: string;
const svgComponent: SvgrComponent; const svgComponent: SvgrComponent;
export default svgUrl; export default svgUrl;
export { svgComponent as ReactComponent } export { svgComponent as ReactComponent };
} }
+18 -9
View File
@@ -9,7 +9,11 @@ const d3ZoomInstance = d3Zoom
.scaleExtent([0.5, 2]) .scaleExtent([0.5, 2])
.filter(() => !event.button); .filter(() => !event.button);
export default (zoomPane: MutableRefObject<HTMLDivElement>, onMove: () => void, shiftPressed: boolean): void => { export default (
zoomPane: MutableRefObject<Element | null>,
onMove: () => void,
shiftPressed: boolean
): void => {
const state = useStoreState(s => ({ const state = useStoreState(s => ({
transform: s.transform, transform: s.transform,
d3Selection: s.d3Selection, d3Selection: s.d3Selection,
@@ -20,8 +24,10 @@ export default (zoomPane: MutableRefObject<HTMLDivElement>, onMove: () => void,
const updateTransform = useStoreActions(actions => actions.updateTransform); const updateTransform = useStoreActions(actions => actions.updateTransform);
useEffect(() => { useEffect(() => {
const selection = select(zoomPane.current).call(d3ZoomInstance); if (zoomPane.current) {
initD3({ zoom: d3ZoomInstance, selection }); const selection = select(zoomPane.current).call(d3ZoomInstance);
initD3({ zoom: d3ZoomInstance, selection });
}
}, []); }, []);
useEffect(() => { useEffect(() => {
@@ -29,8 +35,11 @@ export default (zoomPane: MutableRefObject<HTMLDivElement>, onMove: () => void,
d3ZoomInstance.on('zoom', null); d3ZoomInstance.on('zoom', null);
} else { } else {
d3ZoomInstance.on('zoom', () => { d3ZoomInstance.on('zoom', () => {
if (event.sourceEvent && event.sourceEvent.target !== zoomPane.current) { if (
return false; event.sourceEvent &&
event.sourceEvent.target !== zoomPane.current
) {
return;
} }
updateTransform(event.transform); updateTransform(event.transform);
@@ -38,11 +47,11 @@ export default (zoomPane: MutableRefObject<HTMLDivElement>, onMove: () => void,
onMove(); onMove();
}); });
if (state.d3Selection) { if (state.d3Selection && state.d3Zoom) {
// we need to restore the graph transform otherwise d3 zoom transform and graph transform are not synced // we need to restore the graph transform otherwise d3 zoom transform and graph transform are not synced
const graphTransform = d3Zoom.zoomIdentity const graphTransform = d3Zoom.zoomIdentity
.translate(state.transform[0], state.transform[1]) .translate(state.transform[0], state.transform[1])
.scale(state.transform[2]); .scale(state.transform[2]);
state.d3Selection.call(state.d3Zoom.transform, graphTransform); state.d3Selection.call(state.d3Zoom.transform, graphTransform);
} }
@@ -52,4 +61,4 @@ export default (zoomPane: MutableRefObject<HTMLDivElement>, onMove: () => void,
d3ZoomInstance.on('zoom', null); d3ZoomInstance.on('zoom', null);
}; };
}, [shiftPressed]); }, [shiftPressed]);
} };
+6 -7
View File
@@ -9,7 +9,7 @@ const useElementUpdater = (elements: Elements): void => {
const state = useStoreState(s => ({ const state = useStoreState(s => ({
nodes: s.nodes, nodes: s.nodes,
edges: s.edges, edges: s.edges,
transform: s.transform transform: s.transform,
})); }));
const setNodes = useStoreActions(a => a.setNodes); const setNodes = useStoreActions(a => a.setNodes);
@@ -19,16 +19,17 @@ const useElementUpdater = (elements: Elements): void => {
const nodes = elements.filter(isNode) as Node[]; const nodes = elements.filter(isNode) as Node[];
const edges = elements.filter(isEdge).map(e => parseElement(e)) as Edge[]; const edges = elements.filter(isEdge).map(e => parseElement(e)) as Edge[];
const nextNodes = nodes.map((propNode) => { const nextNodes = nodes.map(propNode => {
const existingNode = state.nodes.find(n => n.id === propNode.id); const existingNode = state.nodes.find(n => n.id === propNode.id);
if (existingNode) { if (existingNode) {
const data = !isEqual(existingNode.data, propNode.data) ? const data = !isEqual(existingNode.data, propNode.data)
{ ...existingNode.data, ...propNode.data } : existingNode.data; ? { ...existingNode.data, ...propNode.data }
: existingNode.data;
return { return {
...existingNode, ...existingNode,
data data,
}; };
} }
@@ -46,8 +47,6 @@ const useElementUpdater = (elements: Elements): void => {
setEdges(edges); setEdges(edges);
} }
}); });
return null;
}; };
export default useElementUpdater; export default useElementUpdater;
+11 -7
View File
@@ -8,10 +8,13 @@ import { Elements, Node } from '../types';
interface HookParams { interface HookParams {
deleteKeyCode: number; deleteKeyCode: number;
onElementsRemove: (elements: Elements) => void; onElementsRemove: (elements: Elements) => void;
}; }
export default ({ deleteKeyCode, onElementsRemove }: HookParams): void => { export default ({ deleteKeyCode, onElementsRemove }: HookParams): void => {
const state = useStoreState(s => ({ selectedElements: s.selectedElements, edges: s.edges })) const state = useStoreState(s => ({
selectedElements: s.selectedElements,
edges: s.edges,
}));
const setNodesSelection = useStoreActions(a => a.setNodesSelection); const setNodesSelection = useStoreActions(a => a.setNodesSelection);
const deleteKeyPressed = useKeyPress(deleteKeyCode); const deleteKeyPressed = useKeyPress(deleteKeyCode);
@@ -20,8 +23,11 @@ export default ({ deleteKeyCode, onElementsRemove }: HookParams): void => {
let elementsToRemove = state.selectedElements; let elementsToRemove = state.selectedElements;
// we also want to remove the edges if only one node is selected // we also want to remove the edges if only one node is selected
if (state.selectedElements.length === 1 && !isEdge(state.selectedElements[0])) { if (
const node = state.selectedElements[0] as unknown as Node; state.selectedElements.length === 1 &&
!isEdge(state.selectedElements[0])
) {
const node = (state.selectedElements[0] as unknown) as Node;
const connectedEdges = getConnectedEdges([node], state.edges); const connectedEdges = getConnectedEdges([node], state.edges);
elementsToRemove = [...state.selectedElements, ...connectedEdges]; elementsToRemove = [...state.selectedElements, ...connectedEdges];
} }
@@ -29,7 +35,5 @@ export default ({ deleteKeyCode, onElementsRemove }: HookParams): void => {
onElementsRemove(elementsToRemove); onElementsRemove(elementsToRemove);
setNodesSelection({ isActive: false }); setNodesSelection({ isActive: false });
} }
}, [deleteKeyPressed]) }, [deleteKeyPressed]);
return null;
}; };
+2 -2
View File
@@ -2,7 +2,7 @@ import { useState, useEffect } from 'react';
import { isInputDOMNode } from '../utils'; import { isInputDOMNode } from '../utils';
export default (keyCode: number) => { export default (keyCode: number): boolean => {
const [keyPressed, setKeyPressed] = useState(false); const [keyPressed, setKeyPressed] = useState(false);
function downHandler(evt: KeyboardEvent) { function downHandler(evt: KeyboardEvent) {
@@ -28,4 +28,4 @@ export default (keyCode: number) => {
}, []); }, []);
return keyPressed; return keyPressed;
} };
+1 -1
View File
@@ -10,5 +10,5 @@ export {
isEdge, isEdge,
removeElements, removeElements,
addEdge, addEdge,
getOutgoers getOutgoers,
} from './utils/graph'; } from './utils/graph';
+3 -6
View File
@@ -2,7 +2,7 @@ import React, { CSSProperties } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { fitView, zoomIn, zoomOut } from '../../utils/graph'; import { fitView, zoomIn, zoomOut } from '../../utils/graph';
import PlusIcon from '../../../assets/icons/plus.svg'; import PlusIcon from '../../../assets/icons/plus.svg';
import MinusIcon from '../../../assets/icons/minus.svg'; import MinusIcon from '../../../assets/icons/minus.svg';
import FitviewIcon from '../../../assets/icons/fitview.svg'; import FitviewIcon from '../../../assets/icons/fitview.svg';
@@ -13,10 +13,7 @@ const baseStyle: CSSProperties = {
left: 10, left: 10,
}; };
interface ControlProps { interface ControlProps extends React.HTMLAttributes<HTMLDivElement> {}
style?: CSSProperties;
className?: string
};
export default ({ style, className }: ControlProps) => { export default ({ style, className }: ControlProps) => {
const mapClasses: string = classnames('react-flow__controls', className); const mapClasses: string = classnames('react-flow__controls', className);
@@ -26,7 +23,7 @@ export default ({ style, className }: ControlProps) => {
className={mapClasses} className={mapClasses}
style={{ style={{
...baseStyle, ...baseStyle,
...style ...style,
}} }}
> >
<div <div
+54 -39
View File
@@ -7,25 +7,26 @@ import { Node } from '../../types';
type StringFunc = (node: Node) => string; type StringFunc = (node: Node) => string;
interface MiniMapProps { interface MiniMapProps extends React.HTMLAttributes<HTMLCanvasElement> {
style?: CSSProperties;
className?: string | null;
bgColor?: string; bgColor?: string;
nodeColor?: string | StringFunc; nodeColor?: string | StringFunc;
}; }
const baseStyle: CSSProperties = { const baseStyle: CSSProperties = {
position: 'absolute', position: 'absolute',
zIndex: 5, zIndex: 5,
bottom: 10, bottom: 10,
right: 10, right: 10,
width: 200 width: 200,
}; };
export default ( export default ({
{ style = {}, className, bgColor = '#f8f8f8', nodeColor = '#ddd' }: MiniMapProps style = {},
) => { className,
const canvasNode = useRef(null); bgColor = '#f8f8f8',
nodeColor = '#ddd',
}: MiniMapProps) => {
const canvasNode = useRef<HTMLCanvasElement>(null);
const state = useStoreState(s => ({ const state = useStoreState(s => ({
width: s.width, width: s.width,
height: s.height, height: s.height,
@@ -34,45 +35,59 @@ export default (
})); }));
const mapClasses = classnames('react-flow__minimap', className); const mapClasses = classnames('react-flow__minimap', className);
const nodePositions = state.nodes.map(n => n.__rg.position); const nodePositions = state.nodes.map(n => n.__rg.position);
const width: number = +(style.width || baseStyle.width || 0); const width: number = +(style.width || baseStyle.width || 0);
const height = (state.height / (state.width || 1)) * width; const height = (state.height / (state.width || 1)) * width;
const bbox = { x: 0, y: 0, width: state.width, height: state.height }; const bbox = { x: 0, y: 0, width: state.width, height: state.height };
const scaleFactor = width / state.width; const scaleFactor = width / state.width;
const nodeColorFunc = (nodeColor instanceof Function ? nodeColor: () => nodeColor) as StringFunc; const nodeColorFunc = (nodeColor instanceof Function
? nodeColor
: () => nodeColor) as StringFunc;
useEffect(() => { useEffect(() => {
if (canvasNode && canvasNode.current) { if (!canvasNode || !canvasNode.current) {
const ctx = canvasNode.current.getContext('2d'); return;
const nodesInside = getNodesInside(state.nodes, bbox, state.transform, true);
ctx.fillStyle = bgColor;
ctx.fillRect(0, 0, width, height);
nodesInside.forEach((n) => {
const pos = n.__rg.position;
const transformX = state.transform[0];
const transformY = state.transform[1];
const x = (pos.x * state.transform[2]) + transformX;
const y = (pos.y * state.transform[2]) + transformY;
ctx.fillStyle = nodeColorFunc(n);
ctx.fillRect(
(x * scaleFactor),
(y * scaleFactor),
n.__rg.width * scaleFactor * state.transform[2],
n.__rg.height * scaleFactor * state.transform[2]
);
});
} }
}, [canvasNode.current, nodePositions, state.transform, height])
const ctx = canvasNode.current.getContext('2d');
if (!ctx) {
return;
}
const nodesInside = getNodesInside(
state.nodes,
bbox,
state.transform,
true
);
ctx.fillStyle = bgColor;
ctx.fillRect(0, 0, width, height);
nodesInside.forEach(n => {
const pos = n.__rg.position;
const transformX = state.transform[0];
const transformY = state.transform[1];
const x = pos.x * state.transform[2] + transformX;
const y = pos.y * state.transform[2] + transformY;
ctx.fillStyle = nodeColorFunc(n);
ctx.fillRect(
x * scaleFactor,
y * scaleFactor,
n.__rg.width * scaleFactor * state.transform[2],
n.__rg.height * scaleFactor * state.transform[2]
);
});
}, [canvasNode.current, nodePositions, state.transform, height]);
return ( return (
<canvas <canvas
style={{ style={{
...baseStyle, ...baseStyle,
...style, ...style,
height height,
}} }}
width={width} width={width}
height={height} height={height}
@@ -80,4 +95,4 @@ export default (
ref={canvasNode} ref={canvasNode}
/> />
); );
} };
+2 -2
View File
@@ -1,2 +1,2 @@
export { default as MiniMap } from './MiniMap'; export { default as MiniMap } from './MiniMap';
export { default as Controls } from './Controls'; export { default as Controls } from './Controls';
+1 -1
View File
@@ -1,6 +1,6 @@
import { createTypedHooks } from 'easy-peasy'; import { createTypedHooks } from 'easy-peasy';
import { StoreModel } from './index'; import { StoreModel } from './index';
const typedHooks = createTypedHooks<StoreModel>(); const typedHooks = createTypedHooks<StoreModel>();
+96 -36
View File
@@ -2,32 +2,44 @@ import { createStore, Action, action } from 'easy-peasy';
import isEqual from 'fast-deep-equal'; import isEqual from 'fast-deep-equal';
import { Selection as D3Selection, ZoomBehavior } from 'd3'; import { Selection as D3Selection, ZoomBehavior } from 'd3';
import { getBoundingBox, getNodesInside, getConnectedEdges } from '../utils/graph';
import { import {
ElementId, Elements, Transform, Node, getBoundingBox,
Edge, Rect, Dimensions, XYPosition, getNodesInside,
OnConnectFunc, SelectionRect, HandleElement getConnectedEdges,
} from '../utils/graph';
import {
ElementId,
Elements,
Transform,
Node,
Edge,
Rect,
Dimensions,
XYPosition,
OnConnectFunc,
SelectionRect,
HandleElement,
} from '../types'; } from '../types';
type TransformXYK = { type TransformXYK = {
x: number, x: number;
y: number, y: number;
k: number k: number;
}; };
type NodePosUpdate = { type NodePosUpdate = {
id: ElementId, id: ElementId;
pos: XYPosition pos: XYPosition;
}; };
type NodeUpdate = { type NodeUpdate = {
id: ElementId, id: ElementId;
width: number, width: number;
height: number, height: number;
handleBounds: { handleBounds: {
source: HandleElement, source: HandleElement[] | null;
target: HandleElement target: HandleElement[] | null;
} };
}; };
type SelectionUpdate = { type SelectionUpdate = {
@@ -40,6 +52,11 @@ type D3Init = {
selection: D3Selection<Element, unknown, null, undefined>; selection: D3Selection<Element, unknown, null, undefined>;
}; };
type SetSnapGrid = {
snapToGrid: boolean;
snapGrid: [number, number];
};
export interface StoreModel { export interface StoreModel {
width: number; width: number;
height: number; height: number;
@@ -49,17 +66,20 @@ export interface StoreModel {
selectedElements: Elements; selectedElements: Elements;
selectedNodesBbox: Rect; selectedNodesBbox: Rect;
d3Zoom: ZoomBehavior<Element, unknown>; d3Zoom: ZoomBehavior<Element, unknown> | null;
d3Selection: D3Selection<Element, unknown, null, undefined>; d3Selection: D3Selection<Element, unknown, null, undefined> | null;
d3Initialised: boolean; d3Initialised: boolean;
nodesSelectionActive: boolean; nodesSelectionActive: boolean;
selectionActive: boolean; selectionActive: boolean;
selection: SelectionRect | null; selection: SelectionRect | null;
connectionSourceId: ElementId | null; connectionSourceId: ElementId | null;
connectionPosition: XYPosition; connectionPosition: XYPosition;
snapToGrid: boolean;
snapGrid: [number, number];
onConnect: OnConnectFunc; onConnect: OnConnectFunc;
setOnConnect: Action<StoreModel, OnConnectFunc>; setOnConnect: Action<StoreModel, OnConnectFunc>;
@@ -68,7 +88,7 @@ export interface StoreModel {
setEdges: Action<StoreModel, Edge[]>; setEdges: Action<StoreModel, Edge[]>;
updateNodeData: Action<StoreModel, NodeUpdate>; updateNodeData: Action<StoreModel, NodeUpdate>;
updateNodePos: Action<StoreModel, NodePosUpdate>; updateNodePos: Action<StoreModel, NodePosUpdate>;
@@ -76,7 +96,7 @@ export interface StoreModel {
setNodesSelection: Action<StoreModel, SelectionUpdate>; setNodesSelection: Action<StoreModel, SelectionUpdate>;
setSelectedElements: Action<StoreModel, Elements | Node | Edge> setSelectedElements: Action<StoreModel, Elements | Node | Edge>;
updateSelection: Action<StoreModel, SelectionRect>; updateSelection: Action<StoreModel, SelectionRect>;
@@ -86,10 +106,12 @@ export interface StoreModel {
initD3: Action<StoreModel, D3Init>; initD3: Action<StoreModel, D3Init>;
setSnapGrid: Action<StoreModel, SetSnapGrid>;
setConnectionPosition: Action<StoreModel, XYPosition>; setConnectionPosition: Action<StoreModel, XYPosition>;
setConnectionSourceId: Action<StoreModel, ElementId>; setConnectionSourceId: Action<StoreModel, ElementId | null>;
}; }
const storeModel: StoreModel = { const storeModel: StoreModel = {
width: 0, width: 0,
@@ -111,6 +133,9 @@ const storeModel: StoreModel = {
connectionSourceId: null, connectionSourceId: null,
connectionPosition: { x: 0, y: 0 }, connectionPosition: { x: 0, y: 0 },
snapGrid: [16, 16],
snapToGrid: true,
onConnect: () => {}, onConnect: () => {},
setOnConnect: action((state, onConnect) => { setOnConnect: action((state, onConnect) => {
@@ -126,22 +151,34 @@ const storeModel: StoreModel = {
}), }),
updateNodeData: action((state, { id, ...data }) => { updateNodeData: action((state, { id, ...data }) => {
state.nodes.forEach((n) => { state.nodes.forEach(n => {
if (n.id === id) { if (n.id === id) {
n.__rg = { n.__rg = {
...n.__rg, ...n.__rg,
...data ...data,
}; };
} }
}); });
}), }),
updateNodePos: action((state, { id, pos }) => { updateNodePos: action((state, { id, pos }) => {
state.nodes.forEach((n) => { let position: XYPosition = pos;
if (state.snapToGrid) {
const transformedGridSizeX = state.snapGrid[0] * state.transform[2];
const transformedGridSizeY = state.snapGrid[1] * state.transform[2];
position = {
x: transformedGridSizeX * Math.round(pos.x / transformedGridSizeX),
y: transformedGridSizeY * Math.round(pos.y / transformedGridSizeY),
};
}
state.nodes.forEach(n => {
if (n.id === id) { if (n.id === id) {
n.__rg = { n.__rg = {
...n.__rg, ...n.__rg,
position: pos position,
}; };
} }
}); });
@@ -152,13 +189,17 @@ const storeModel: StoreModel = {
}), }),
setNodesSelection: action((state, { isActive, selection }) => { setNodesSelection: action((state, { isActive, selection }) => {
if (!isActive) { if (!isActive || typeof selection === 'undefined') {
state.nodesSelectionActive = false; state.nodesSelectionActive = false;
state.selectedElements = []; state.selectedElements = [];
return; return;
} }
const selectedNodes = getNodesInside(state.nodes, selection, state.transform); const selectedNodes = getNodesInside(
state.nodes,
selection,
state.transform
);
const selectedNodesBbox = getBoundingBox(selectedNodes); const selectedNodesBbox = getBoundingBox(selectedNodes);
state.selection = selection; state.selection = selection;
@@ -169,21 +210,35 @@ const storeModel: StoreModel = {
setSelectedElements: action((state, elements) => { setSelectedElements: action((state, elements) => {
const selectedElementsArr = Array.isArray(elements) ? elements : [elements]; const selectedElementsArr = Array.isArray(elements) ? elements : [elements];
const selectedElementsUpdated = !isEqual(selectedElementsArr, state.selectedElements); const selectedElementsUpdated = !isEqual(
const selectedElements = selectedElementsUpdated ? selectedElementsArr : state.selectedElements; selectedElementsArr,
state.selectedElements
);
const selectedElements = selectedElementsUpdated
? selectedElementsArr
: state.selectedElements;
state.selectedElements = selectedElements; state.selectedElements = selectedElements;
}), }),
updateSelection: action((state, selection) => { updateSelection: action((state, selection) => {
const selectedNodes = getNodesInside(state.nodes, selection, state.transform); const selectedNodes = getNodesInside(
state.nodes,
selection,
state.transform
);
const selectedEdges = getConnectedEdges(selectedNodes, state.edges); const selectedEdges = getConnectedEdges(selectedNodes, state.edges);
const nextSelectedElements = [...selectedNodes, ...selectedEdges]; const nextSelectedElements = [...selectedNodes, ...selectedEdges];
const selectedElementsUpdated = !isEqual(nextSelectedElements, state.selectedElements); const selectedElementsUpdated = !isEqual(
nextSelectedElements,
state.selectedElements
);
state.selection = selection; state.selection = selection;
state.selectedElements = selectedElementsUpdated ? nextSelectedElements: state.selectedElements state.selectedElements = selectedElementsUpdated
? nextSelectedElements
: state.selectedElements;
}), }),
updateTransform: action((state, transform) => { updateTransform: action((state, transform) => {
@@ -207,7 +262,12 @@ const storeModel: StoreModel = {
setConnectionSourceId: action((state, sourceId) => { setConnectionSourceId: action((state, sourceId) => {
state.connectionSourceId = sourceId; state.connectionSourceId = sourceId;
}) }),
setSnapGrid: action((state, { snapToGrid, snapGrid }) => {
state.snapToGrid = snapToGrid;
state.snapGrid = snapGrid;
}),
}; };
const store = createStore(storeModel); const store = createStore(storeModel);
+61 -66
View File
@@ -2,136 +2,131 @@ import { CSSProperties, SVGAttributes } from 'react';
export type ElementId = string; export type ElementId = string;
export type Elements = Array<Node | Edge>; export type Elements = Array<Node | Edge>;
export type Transform = [number, number, number]; export type Transform = [number, number, number];
export type Position = 'left' | 'top' | 'right' | 'bottom'; export type Position = 'left' | 'top' | 'right' | 'bottom';
export type XYPosition = { export type XYPosition = {
x: number, x: number;
y: number y: number;
}; };
export enum GridType { export enum GridType {
Lines = 'lines', Lines = 'lines',
Dots = 'dots', Dots = 'dots',
}; }
export type HandleType = 'source' | 'target'; export type HandleType = 'source' | 'target';
export type NodeTypesType = { [key: string]: React.ReactNode }; export type NodeTypesType = { [key: string]: React.ReactNode };
export type EdgeTypesType = NodeTypesType; export type EdgeTypesType = NodeTypesType;
export interface Dimensions { export interface Dimensions {
width: number, width: number;
height: number height: number;
} }
export interface Rect extends Dimensions { export interface Rect extends Dimensions {
x: number, x: number;
y: number y: number;
}; }
export interface SelectionRect extends Rect { export interface SelectionRect extends Rect {
startX: number; startX: number;
startY: number; startY: number;
draw: boolean draw: boolean;
}; }
export interface Node { export interface Node {
id: ElementId, id: ElementId;
position?: XYPosition, position: XYPosition;
type?: string, type?: string;
__rg?: any, __rg?: any;
data?: any, data?: any;
style?: CSSProperties style?: CSSProperties;
}; }
export interface Edge { export interface Edge {
id: ElementId, id: ElementId;
type?: string, type?: string;
source: ElementId, source: ElementId;
target: ElementId, target: ElementId;
style?: SVGAttributes<{}> style?: SVGAttributes<{}>;
animated?: boolean animated?: boolean;
}; }
export interface EdgeProps { export interface EdgeProps {
sourceX: number, sourceX: number;
sourceY: number, sourceY: number;
targetX: number, targetX: number;
targetY: number, targetY: number;
style?: SVGAttributes<{}> style?: SVGAttributes<{}>;
}; }
export interface EdgeBezierProps extends EdgeProps{ export interface EdgeBezierProps extends EdgeProps {
sourcePosition: Position, sourcePosition: Position;
targetPosition: Position targetPosition: Position;
}; }
export interface NodeProps { export interface NodeProps {
id: ElementId, id: ElementId;
type: string, type: string;
data: any; data: any;
selected: boolean; selected: boolean;
style?: CSSProperties; style?: CSSProperties;
}; }
export interface NodeComponentProps { export interface NodeComponentProps {
id: ElementId, id: ElementId;
type: string; type: string;
data: any; data: any;
selected?: boolean; selected?: boolean;
transform?: Transform; transform?: Transform;
xPos?: number; xPos?: number;
yPos?: number; yPos?: number;
onClick?: () => any; onClick?: (node: Node) => void | undefined;
onNodeDragStop?: () => any; onNodeDragStop?: () => any;
style?: CSSProperties; style?: CSSProperties;
}; }
export type FitViewParams = { export type FitViewParams = {
padding: number padding: number;
}; };
export type FitViewFunc = (fitViewOptions: FitViewParams) => void; export type FitViewFunc = (fitViewOptions: FitViewParams) => void;
type OnLoadParams = { type OnLoadParams = {
zoomIn: () => void; zoomIn: () => void;
zoomOut: () => void; zoomOut: () => void;
fitView: FitViewFunc fitView: FitViewFunc;
}; };
export type OnLoadFunc = (params: OnLoadParams) => void; export type OnLoadFunc = (params: OnLoadParams) => void;
export type OnConnectParams = {
source: ElementId;
target: ElementId;
};
export type OnConnectFunc = (params: OnConnectParams) => void;
export type Connection = { export type Connection = {
source: ElementId; source: ElementId | null;
target: ElementId; target: ElementId | null;
}; };
export type OnConnectFunc = (params: Connection) => void;
export interface HandleElement { export interface HandleElement {
id?: ElementId; id?: ElementId | null;
position: Position; position: Position;
x: number; x: number;
y: number; y: number;
width: number; width: number;
height: number; height: number;
}; }
export interface EdgeWrapperProps { export interface EdgeCompProps {
id: ElementId, id: ElementId;
source: ElementId, source: ElementId;
target: ElementId, target: ElementId;
type: any, type: any;
onClick?: (edge: Edge) => void onClick?: (edge: Edge) => void;
animated?: boolean, animated?: boolean;
selected?: boolean, selected?: boolean;
}; }
+127 -70
View File
@@ -1,9 +1,18 @@
import { zoomIdentity } from 'd3-zoom'; import { zoomIdentity } from 'd3-zoom';
import store from '../store'; import store from '../store';
import { ElementId, Node, Edge, Elements, Transform, XYPosition, Rect, FitViewParams } from '../types'; import {
ElementId,
Node,
Edge,
Elements,
Transform,
XYPosition,
Rect,
FitViewParams,
} from '../types';
export const isEdge = (element: Node | Edge): boolean => export const isEdge = (element: Node | Edge): boolean =>
element.hasOwnProperty('source') && element.hasOwnProperty('target'); element.hasOwnProperty('source') && element.hasOwnProperty('target');
export const isNode = (element: Node | Edge): boolean => export const isNode = (element: Node | Edge): boolean =>
@@ -14,14 +23,19 @@ export const getOutgoers = (node: Node, elements: Elements): Elements => {
return []; return [];
} }
const outgoerIds = elements.filter((e: Edge) => e.source === node.id).map((e: Edge) => e.target); const outgoerIds = (elements as Edge[])
.filter(e => e.source === node.id)
.map(e => e.target);
return elements.filter(e => outgoerIds.includes(e.id)); return elements.filter(e => outgoerIds.includes(e.id));
}; };
export const removeElements = (elementsToRemove: Elements, elements: Elements): Elements => { export const removeElements = (
elementsToRemove: Elements,
elements: Elements
): Elements => {
const nodeIdsToRemove = elementsToRemove.map(n => n.id); const nodeIdsToRemove = elementsToRemove.map(n => n.id);
return elements.filter((element) => { return elements.filter(element => {
const edgeElement = element as Edge; const edgeElement = element as Edge;
return !( return !(
nodeIdsToRemove.includes(element.id) || nodeIdsToRemove.includes(element.id) ||
@@ -36,36 +50,45 @@ function getEdgeId(edgeParams: Edge): ElementId {
} }
export const addEdge = (edgeParams: Edge, elements: Elements): Elements => { export const addEdge = (edgeParams: Edge, elements: Elements): Elements => {
if (!edgeParams.source || !edgeParams.target) { if (!edgeParams.source || !edgeParams.target) {
throw new Error('Can not create edge. An edge needs a source and a target'); throw new Error('Can not create edge. An edge needs a source and a target');
} }
return elements.concat({ return elements.concat({
...edgeParams, ...edgeParams,
id: typeof edgeParams.id !== 'undefined' ? edgeParams.id : getEdgeId(edgeParams) id:
typeof edgeParams.id !== 'undefined'
? edgeParams.id
: getEdgeId(edgeParams),
}); });
} };
const pointToRendererPoint = ({ x, y }: XYPosition, transform: Transform): XYPosition => { const pointToRendererPoint = (
{ x, y }: XYPosition,
transform: Transform
): XYPosition => {
const rendererX = (x - transform[0]) * (1 / transform[2]); const rendererX = (x - transform[0]) * (1 / transform[2]);
const rendererY = (y - transform[1]) * (1 / transform[2]); const rendererY = (y - transform[1]) * (1 / transform[2]);
return { return {
x: rendererX, x: rendererX,
y: rendererY y: rendererY,
}; };
}; };
export const parseElement = (element: Node | Edge, transform?: Transform): Node | Edge => { export const parseElement = (
element: Node | Edge,
transform: Transform = [0, 0, 1]
): Node | Edge => {
if (!element.id) { if (!element.id) {
throw new Error('All elements (nodes and edges) need to have an id.',) throw new Error('All elements (nodes and edges) need to have an id.');
} }
if (isEdge(element)) { if (isEdge(element)) {
return { return {
...element, ...element,
id: element.id.toString(), id: element.id.toString(),
type: element.type || 'default' type: element.type || 'default',
}; };
} }
@@ -79,76 +102,88 @@ export const parseElement = (element: Node | Edge, transform?: Transform): Node
position: pointToRendererPoint(nodeElement.position, transform), position: pointToRendererPoint(nodeElement.position, transform),
width: null, width: null,
height: null, height: null,
handleBounds : {} handleBounds: {},
} },
}; };
}; };
export const getBoundingBox = (nodes: Node[]): Rect => { export const getBoundingBox = (nodes: Node[]): Rect => {
const bbox = nodes.reduce((res, node) => { const bbox = nodes.reduce(
const { position } = node.__rg; (res, node) => {
const x2 = position.x + node.__rg.width; const { position } = node.__rg;
const y2 = position.y + node.__rg.height; const x2 = position.x + node.__rg.width;
const y2 = position.y + node.__rg.height;
if (position.x < res.minX) { if (position.x < res.minX) {
res.minX = position.x; res.minX = position.x;
}
if (x2 > res.maxX) {
res.maxX = x2;
}
if (position.y < res.minY) {
res.minY = position.y;
}
if (y2 > res.maxY) {
res.maxY = y2;
}
return res;
},
{
minX: Number.MAX_VALUE,
minY: Number.MAX_VALUE,
maxX: 0,
maxY: 0,
} }
);
if (x2 > res.maxX) {
res.maxX = x2;
}
if (position.y < res.minY) {
res.minY = position.y;
}
if (y2 > res.maxY) {
res.maxY = y2;
}
return res;
}, {
minX: Number.MAX_VALUE,
minY: Number.MAX_VALUE,
maxX: 0,
maxY: 0
});
return { return {
x: bbox.minX, x: bbox.minX,
y: bbox.minY, y: bbox.minY,
width: bbox.maxX - bbox.minX, width: bbox.maxX - bbox.minX,
height: bbox.maxY - bbox.minY height: bbox.maxY - bbox.minY,
}; };
}; };
export const graphPosToZoomedPos = (pos: XYPosition, transform: Transform): XYPosition => { export const graphPosToZoomedPos = (
pos: XYPosition,
transform: Transform
): XYPosition => {
return { return {
x: (pos.x * transform[2]) + transform[0], x: pos.x * transform[2] + transform[0],
y: (pos.y * transform[2]) + transform[1] y: pos.y * transform[2] + transform[1],
}; };
} };
export const getNodesInside = (nodes: Node[], bbox: Rect, transform: Transform = [0, 0, 1], partially: boolean = false): Node[] => { export const getNodesInside = (
return nodes nodes: Node[],
.filter(n => { bbox: Rect,
const bboxPos = { transform: Transform = [0, 0, 1],
x: (bbox.x - transform[0]) * (1 / transform[2]), partially: boolean = false
y: (bbox.y - transform[1]) * (1 / transform[2]) ): Node[] => {
}; return nodes.filter(n => {
const bboxWidth = bbox.width * (1 / transform[2]); const bboxPos = {
const bboxHeight = bbox.height * (1 / transform[2]); x: (bbox.x - transform[0]) * (1 / transform[2]),
const { position, width, height } = n.__rg; y: (bbox.y - transform[1]) * (1 / transform[2]),
const nodeWidth = partially ? -width : width; };
const nodeHeight = partially ? 0 : height; const bboxWidth = bbox.width * (1 / transform[2]);
const offsetX = partially ? width : 0; const bboxHeight = bbox.height * (1 / transform[2]);
const offsetY = partially ? height : 0; const { position, width, height } = n.__rg;
const nodeWidth = partially ? -width : width;
const nodeHeight = partially ? 0 : height;
const offsetX = partially ? width : 0;
const offsetY = partially ? height : 0;
return ( return (
(position.x + offsetX > bboxPos.x && (position.x + nodeWidth) < (bboxPos.x + bboxWidth)) && position.x + offsetX > bboxPos.x &&
(position.y + offsetY > bboxPos.y && (position.y + nodeHeight) < (bboxPos.y + bboxHeight)) position.x + nodeWidth < bboxPos.x + bboxWidth &&
); (position.y + offsetY > bboxPos.y &&
}); position.y + nodeHeight < bboxPos.y + bboxHeight)
);
});
}; };
export const getConnectedEdges = (nodes: Node[], edges: Edge[]): Edge[] => { export const getConnectedEdges = (nodes: Node[], edges: Edge[]): Edge[] => {
@@ -167,23 +202,45 @@ export const getConnectedEdges = (nodes: Node[], edges: Edge[]): Edge[] => {
export const fitView = ({ padding }: FitViewParams = { padding: 0 }): void => { export const fitView = ({ padding }: FitViewParams = { padding: 0 }): void => {
const state = store.getState(); const state = store.getState();
if (!state.d3Selection || !state.d3Zoom) {
return;
}
const bounds = getBoundingBox(state.nodes); const bounds = getBoundingBox(state.nodes);
const maxBoundsSize = Math.max(bounds.width, bounds.height); const maxBoundsSize = Math.max(bounds.width, bounds.height);
const k = Math.min(state.width, state.height) / (maxBoundsSize + (maxBoundsSize * padding)); const k =
const boundsCenterX = bounds.x + (bounds.width / 2); Math.min(state.width, state.height) /
const boundsCenterY = bounds.y + (bounds.height / 2); (maxBoundsSize + maxBoundsSize * padding);
const transform = [(state.width / 2) - (boundsCenterX * k), (state.height / 2) - (boundsCenterY * k)]; const boundsCenterX = bounds.x + bounds.width / 2;
const fittedTransform = zoomIdentity.translate(transform[0], transform[1]).scale(k); const boundsCenterY = bounds.y + bounds.height / 2;
const transform = [
state.width / 2 - boundsCenterX * k,
state.height / 2 - boundsCenterY * k,
];
const fittedTransform = zoomIdentity
.translate(transform[0], transform[1])
.scale(k);
state.d3Selection.call(state.d3Zoom.transform, fittedTransform); state.d3Selection.call(state.d3Zoom.transform, fittedTransform);
}; };
export const zoomIn = (): void => { export const zoomIn = (): void => {
const state = store.getState(); const state = store.getState();
if (!state.d3Zoom || !state.d3Selection) {
return;
}
state.d3Zoom.scaleTo(state.d3Selection, state.transform[2] + 0.2); state.d3Zoom.scaleTo(state.d3Selection, state.transform[2] + 0.2);
}; };
export const zoomOut = (): void => { export const zoomOut = (): void => {
const state = store.getState(); const state = store.getState();
if (!state.d3Zoom || !state.d3Selection) {
return;
}
state.d3Zoom.scaleTo(state.d3Selection, state.transform[2] - 0.2); state.d3Zoom.scaleTo(state.d3Selection, state.transform[2] - 0.2);
}; };
+7 -3
View File
@@ -1,12 +1,16 @@
import { DraggableEvent } from 'react-draggable'; import { DraggableEvent } from 'react-draggable';
import { MouseEvent as ReactMouseEvent } from 'react'; import { MouseEvent as ReactMouseEvent } from 'react';
export const isInputDOMNode = (e: ReactMouseEvent | DraggableEvent | KeyboardEvent) => { export const isInputDOMNode = (
e: ReactMouseEvent | DraggableEvent | KeyboardEvent
) => {
const target = e.target as HTMLElement; const target = e.target as HTMLElement;
return e && target && ['INPUT', 'SELECT', 'TEXTAREA'].includes(target.nodeName); return (
e && target && ['INPUT', 'SELECT', 'TEXTAREA'].includes(target.nodeName)
);
}; };
export const getDimensions = (node: HTMLDivElement) => ({ export const getDimensions = (node: HTMLDivElement) => ({
width: node.offsetWidth, width: node.offsetWidth,
height: node.offsetHeight height: node.offsetHeight,
}); });
+11 -4
View File
@@ -6,13 +6,20 @@
"lib": ["es6", "dom", "es2016", "es2017"], "lib": ["es6", "dom", "es2016", "es2017"],
"jsx": "react", "jsx": "react",
"moduleResolution": "node", "moduleResolution": "node",
"forceConsistentCasingInFileNames": true, "declaration": true,
"strictNullChecks": false, "strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"noUnusedParameters": true, "noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"noImplicitAny": true, "esModuleInterop": true
"suppressImplicitAnyIndexErrors": true
}, },
"include": ["src"], "include": ["src"],
"exclude": ["node_modules", "build", "dist", "example", "rollup.config.js"] "exclude": ["node_modules", "build", "dist", "example", "rollup.config.js"]