Merge pull request #59 from wbkd/develop

Develop
This commit is contained in:
Moritz
2019-10-23 20:53:24 +02:00
committed by GitHub
46 changed files with 896 additions and 726 deletions

View File

@@ -1,14 +1,30 @@
![react-flow](https://user-images.githubusercontent.com/2857535/67417109-516ef000-f5c8-11e9-938a-c2121d3ebbe4.png)
# :ocean: React Flow
React library for building node-based graphs.
React Flow is a library for building node-based graphs. You can easily implement custom node types and it comes with plugins like a MiniMap and a Controls Panel. Check out the [demo](https://react-flow.netlify.com/) graph.
# Installation
- [Installation](#installation)
- [Usage](#usage)
- [Props](#props)
- [Nodes](#nodes)
- [Props](#props-1)
- [Node Types / Custom Nodes](#node-types--custom-nodes)
- [Edges](#nodes)
- [Props](#props-2)
- [Edge Types / Custom Edges](#edge-types--custom-edges)
- [Plugins](#plugins)
- [Minimap](#minimap)
- [Controls](#controls)
- [Examples](#examples)
## Installation
```
npm install github:wbkd/react-flow
```
# Usage
## Usage
This is a very basic example of how to use react-flow. There are more advanced examples in the [example](/example/src) folder.
@@ -29,7 +45,7 @@ const BasicGraph = () => (
);
```
# Props
## Props
- `elements`: array of [nodes](#nodes) and [edges](#edges) *(required)*
- `onElementClick`: element click handler
@@ -43,8 +59,8 @@ const BasicGraph = () => (
- `style`: css style passed to the wrapper
- `connectionLineType`: connection line type = `straight` or `bezier`
- `connectionLineStyle`: connection style as svg attributes
- `deleteKeyCode`: default: `16`
- `selectionKeyCode`: default: `false`
- `deleteKeyCode`: default: `8` (delete)
- `selectionKeyCode`: default: `16` (shift)
- `showBackground`: default: `true`
- `backgroundGap`: gap size - default: `16`
- `backgroundColor`: color of dots or lines - default: `#eee`
@@ -52,6 +68,7 @@ const BasicGraph = () => (
- `snapToGrid`: default: `false`
- `snapGrid`: [x, y] array - default: `[16, 16]`
- `onlyRenderVisibleNodes`: default: `true`
- `isInteractive`: default: `true`. If the graph is not interactive you can't drag any nodes
## Nodes
@@ -59,30 +76,17 @@ There are three different [node types](#node-types--custom-nodes) (`default`, `i
Node example: `{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } }`
**Node Props**
### Props
- `id`: string *(required)*
- `position`: { x: number, y: number } *(required)*
- `data`: {} *(required if you are using a standard type, otherwise depends on your implementation)*
- `type`: 'input' | 'output' | 'default' or a custom one you implemented
- `style`: css properties
- `targetPosition`: 'left' | 'right' | 'top' | 'bottom' handle position - default: 'top'
- `sourcePosition`: 'left' | 'right' | 'top' | 'bottom' handle position - default: 'bottom'
## Edges
There are three [edge types](#edge-types--custom-edges) (`straight`, `default`, `step`) you can use. The default type is `default`. You can also create [custom edges](#edge-types--custom-edges).
Edge example: `{ id: 'e1-2', type: 'straight', source: '1', target: '2', animated: true }`
**Edge Props**
- `id`: string *(required)*
- `source`: string *(required)*
- `target`: string *(required)*
- `type`: 'input' | 'output' | 'default' or a custom one you implemented
- `animated`: boolean
- `style`: css properties
## Node Types / Custom Nodes
### Node Types / Custom Nodes
The standard node types are `input`, `default` and `output`. The default node types object looks like this:
@@ -105,9 +109,25 @@ nodeTypes={{
You can now use type `special` for a node.
The `default`, `input` and `output` types will be still available except you overwrite one of them.
You can find an example of how to implement a custom node in [custom nodes example](example/src/CustomNodes).
You can find an example of how to implement a custom node in the [custom node example](example/src/CustomNode).
## Edge Types / Custom Edges
## Edges
There are three [edge types](#edge-types--custom-edges) (`straight`, `default`, `step`) you can use. The default type is `default`. You can also create [custom edges](#edge-types--custom-edges).
Edge example: `{ id: 'e1-2', type: 'straight', source: '1', target: '2', animated: true }`
### Props
- `id`: string *(required)*
- `source`: string *(required)*
- `target`: string *(required)*
- `type`: 'input' | 'output' | 'default' or a custom one you implemented
- `animated`: boolean
- `style`: css properties
### Edge Types / Custom Edges
The standard edge types are `straight`, `default` and `step`. The default edge types object looks like this:
@@ -131,9 +151,9 @@ edgeTypes={{
You can now use type `special` for an edge.
The `straight`, `default` and `step` types will be still available except you overwrite one of them.
# Plugins
## Plugins
## MiniMap
### MiniMap
You can use the MiniMap plugin by passing it as a children of you graph:
@@ -149,7 +169,7 @@ const GraphWithMiniMap = () => (
);
```
**Props**
#### Props
- `nodeColor`: string | function - if you pass a color as a string all nodes will get that color. If you pass a function you can return a color depending on the node.
- `nodeBorderRadius`: number
@@ -157,7 +177,7 @@ const GraphWithMiniMap = () => (
- `style`: css properties
- `className`: class name
## Controls
### Controls
The control panel contains a zoom-in, zoom-out and a fit-view button. You can use it by passing it as children to your graph:
@@ -173,7 +193,17 @@ const GraphWithControls = () => (
);
```
**Props**
#### Props
- `style`: css properties
- `className`: class name
## Examples
You can find all examples in the [example](example) folder. They are also deployt:
- [rich](https://react-flow.netlify.com/rich)
- [basic](https://react-flow.netlify.com/basic)
- [empty](https://react-flow.netlify.com/empty)
- [inactive](https://react-flow.netlify.com/inactive)
- [custom node](https://react-flow.netlify.com/custom-node)

View File

@@ -0,0 +1,10 @@
describe('Custom Node Graph Rendering', () => {
it('renders a graph', () => {
cy.visit('/custom-node');
cy.get('.react-flow__renderer');
cy.get('.react-flow__node').should('have.length', 4);
cy.get('.react-flow__edge').should('have.length', 3);
});
});

View File

@@ -1,55 +0,0 @@
describe('Custom Nodes Graph Rendering', () => {
it('renders a flow with sone nodes', () => {
cy.visit('/');
cy.get('.react-flow__renderer');
cy.get('.react-flow__node').should('have.length', 8);
cy.get('.react-flow__edge').should('have.length', 8);
});
it('renders a grid', () => {
cy.get('.react-flow__grid');
const gridStroke = Cypress.$('.react-flow__grid path').attr('fill');
expect(gridStroke).to.equal('#888');
});
it('connects nodes', () => {
cy.get('.react-flow__node')
.contains('1 Tests')
.find('.react-flow__handle.source')
.trigger('mousedown', { which: 1 });
cy.get('.react-flow__node')
.contains('7 output')
.find('.react-flow__handle.target')
.trigger('mousemove')
.should('have.class', 'connecting')
.should('have.class', 'valid')
.trigger('mouseup', { force: true })
.should('not.have.class', 'valid')
.should('not.have.class', 'connecting');
cy.get('.react-flow__edge').should('have.length', 9);
});
it('tries to make invalid connection', () => {
cy.get('.react-flow__node')
.contains('write something')
.parents('.react-flow__node')
.find('.react-flow__handle.source')
.trigger('mousedown', { which: 1 });
cy.get('.react-flow__node')
.contains('5 Another node')
.find('.react-flow__handle.target')
.trigger('mousemove')
.should('have.class', 'connecting')
.should('not.have.class', 'valid')
.trigger('mouseup', { force: true })
.should('not.have.class', 'connecting');
cy.get('.react-flow__edge').should('have.length', 9);
});
});

View File

@@ -0,0 +1,71 @@
describe('Inactive Graph Rendering', () => {
it('renders a graph', () => {
cy.visit('/inactive');
cy.get('.react-flow__renderer');
cy.get('.react-flow__node').should('have.length', 4);
cy.get('.react-flow__edge').should('have.length', 2);
cy.get('.react-flow__node')
.children('div')
.children('.react-flow__handle');
});
it('tries to select a node by click', () => {
cy.get('.react-flow__node:first')
.click()
.should('have.not.class', 'selected');
});
it('tries to select an edge by click', () => {
cy.get('.react-flow__edge:first')
.click()
.should('have.not.class', 'selected');
});
it('tries to do a selection', () => {
cy.get('body')
.type('{shift}', { release: false })
.get('.react-flow__selectionpane')
.should('not.exist');
});
it('tries to drag a node', () => {
const styleBeforeDrag = Cypress.$('.react-flow__node:first').css('transform');
cy.drag('.react-flow__node:first', { x: 500, y: 25 }).then($el => {
const styleAfterDrag = $el.css('transform');
expect(styleBeforeDrag).to.equal(styleAfterDrag);
});
});
it('tries to connect nodes', () => {
cy.get('.react-flow__node')
.contains('Node 3')
.find('.react-flow__handle.source')
.trigger('mousedown', { which: 1 });
cy.get('.react-flow__node')
.contains('Node 4')
.find('.react-flow__handle.target')
.trigger('mousemove')
.trigger('mouseup', { force: true });
cy.get('.react-flow__edge').should('have.length', 2);
});
it('toggles interactive mode', () => {
cy.get('.react-flow__interactive').click();
});
it('selects a node by click', () => {
cy.get('.react-flow__node:first')
.click()
.should('have.class', 'selected');
});
it('selects an edge by click', () => {
cy.get('.react-flow__edge:first')
.click()
.should('have.class', 'selected');
});
});

View File

@@ -0,0 +1,17 @@
describe('Rich Graph Rendering', () => {
it('renders a graph', () => {
cy.visit('/');
cy.get('.react-flow__renderer');
cy.get('.react-flow__node').should('have.length', 6);
cy.get('.react-flow__edge').should('have.length', 5);
});
it('renders a grid', () => {
cy.get('.react-flow__grid');
const gridStroke = Cypress.$('.react-flow__grid path').attr('fill');
expect(gridStroke).to.equal('#888');
});
});

248
dist/ReactFlow.esm.js vendored
View File

@@ -2522,6 +2522,63 @@ var StoreProvider = function StoreProvider(_ref) {
setAutoFreeze(false);
function unwrapExports (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
function createCommonjsModule(fn, module) {
return module = { exports: {} }, fn(module, module.exports), module.exports;
}
var classnames = createCommonjsModule(function (module) {
/*!
Copyright (c) 2017 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/
/* global define */
(function () {
var hasOwn = {}.hasOwnProperty;
function classNames () {
var classes = [];
for (var i = 0; i < arguments.length; i++) {
var arg = arguments[i];
if (!arg) continue;
var argType = typeof arg;
if (argType === 'string' || argType === 'number') {
classes.push(arg);
} else if (Array.isArray(arg) && arg.length) {
var inner = classNames.apply(null, arg);
if (inner) {
classes.push(inner);
}
} else if (argType === 'object') {
for (var key in arg) {
if (hasOwn.call(arg, key) && arg[key]) {
classes.push(key);
}
}
}
}
return classes.join(' ');
}
if ( module.exports) {
classNames.default = classNames;
module.exports = classNames;
} else {
window.classNames = classNames;
}
}());
});
var typedHooks = createTypedHooks();
var useStoreActions$1 = typedHooks.useStoreActions;
var useStoreState$1 = typedHooks.useStoreState;
@@ -5758,6 +5815,7 @@ var storeModel = {
connectionPosition: { x: 0, y: 0 },
snapGrid: [16, 16],
snapToGrid: true,
isInteractive: true,
onConnect: function () { },
setOnConnect: action(function (state, onConnect) {
state.onConnect = onConnect;
@@ -5818,9 +5876,7 @@ var storeModel = {
setSelectedElements: action(function (state, elements) {
var selectedElementsArr = Array.isArray(elements) ? elements : [elements];
var selectedElementsUpdated = !fastDeepEqual(selectedElementsArr, state.selectedElements);
var selectedElements = selectedElementsUpdated
? selectedElementsArr
: state.selectedElements;
var selectedElements = selectedElementsUpdated ? selectedElementsArr : state.selectedElements;
state.selectedElements = selectedElements;
}),
updateSelection: action(function (state, selection) {
@@ -5829,9 +5885,7 @@ var storeModel = {
var nextSelectedElements = __spreadArrays(selectedNodes, selectedEdges);
var selectedElementsUpdated = !fastDeepEqual(nextSelectedElements, state.selectedElements);
state.selection = selection;
state.selectedElements = selectedElementsUpdated
? nextSelectedElements
: state.selectedElements;
state.selectedElements = selectedElementsUpdated ? nextSelectedElements : state.selectedElements;
}),
updateTransform: action(function (state, transform) {
state.transform = [transform.x, transform.y, transform.k];
@@ -5857,6 +5911,9 @@ var storeModel = {
state.snapToGrid = snapToGrid;
state.snapGrid = snapGrid;
}),
setInteractive: action(function (state, isInteractive) {
state.isInteractive = isInteractive;
}),
};
var store = createStore$1(storeModel);
@@ -6013,7 +6070,7 @@ var zoom$1 = function (amount) {
var zoomIn = function () { return zoom$1(0.2); };
var zoomOut = function () { return zoom$1(-0.2); };
function renderNode(node, props, transform, selectedElements) {
function renderNode(node, props, transform, selectedElements, isInteractive) {
var nodeType = node.type || 'default';
var NodeComponent = (props.nodeTypes[nodeType] || props.nodeTypes.default);
if (!props.nodeTypes[nodeType]) {
@@ -6023,79 +6080,31 @@ function renderNode(node, props, transform, selectedElements) {
var id = _a.id;
return id === node.id;
});
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: transform, selected: isSelected, 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: transform, selected: isSelected, style: node.style, isInteractive: isInteractive, sourcePosition: node.sourcePosition, targetPosition: node.targetPosition }));
}
var NodeRenderer = memo(function (_a) {
var _b = _a.onlyRenderVisibleNodes, onlyRenderVisibleNodes = _b === void 0 ? true : _b, props = __rest(_a, ["onlyRenderVisibleNodes"]);
var _c = useStoreState$1(function (s) { return s; }), nodes = _c.nodes, transform = _c.transform, selectedElements = _c.selectedElements, width = _c.width, height = _c.height;
var _c = useStoreState$1(function (s) { return ({
nodes: s.nodes,
transform: s.transform,
selectedElements: s.selectedElements,
width: s.width,
height: s.height,
isInteractive: s.isInteractive,
}); }), nodes = _c.nodes, transform = _c.transform, selectedElements = _c.selectedElements, width = _c.width, height = _c.height, isInteractive = _c.isInteractive;
var tx = transform[0], ty = transform[1], tScale = transform[2];
var transformStyle = {
transform: "translate(" + tx + "px," + ty + "px) scale(" + tScale + ")",
};
var renderNodes = onlyRenderVisibleNodes ? getNodesInside(nodes, { x: 0, y: 0, width: width, height: height }, transform, true) : nodes;
return (React.createElement("div", { className: "react-flow__nodes", style: transformStyle }, renderNodes.map(function (node) { return renderNode(node, props, transform, selectedElements); })));
var renderNodes = onlyRenderVisibleNodes
? getNodesInside(nodes, { x: 0, y: 0, width: width, height: height }, transform, true)
: nodes;
return (React.createElement("div", { className: "react-flow__nodes", style: transformStyle }, renderNodes.map(function (node) { return renderNode(node, props, transform, selectedElements, isInteractive); })));
});
NodeRenderer.displayName = 'NodeRenderer';
function unwrapExports (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
function createCommonjsModule(fn, module) {
return module = { exports: {} }, fn(module, module.exports), module.exports;
}
var classnames = createCommonjsModule(function (module) {
/*!
Copyright (c) 2017 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/
/* global define */
(function () {
var hasOwn = {}.hasOwnProperty;
function classNames () {
var classes = [];
for (var i = 0; i < arguments.length; i++) {
var arg = arguments[i];
if (!arg) continue;
var argType = typeof arg;
if (argType === 'string' || argType === 'number') {
classes.push(arg);
} else if (Array.isArray(arg) && arg.length) {
var inner = classNames.apply(null, arg);
if (inner) {
classes.push(inner);
}
} else if (argType === 'object') {
for (var key in arg) {
if (hasOwn.call(arg, key) && arg[key]) {
classes.push(key);
}
}
}
}
return classes.join(' ');
}
if ( module.exports) {
classNames.default = classNames;
module.exports = classNames;
} else {
window.classNames = classNames;
}
}());
});
var ConnectionLine = (function (_a) {
var connectionSourceId = _a.connectionSourceId, _b = _a.connectionLineStyle, connectionLineStyle = _b === void 0 ? {} : _b, connectionPositionX = _a.connectionPositionX, connectionPositionY = _a.connectionPositionY, connectionLineType = _a.connectionLineType, _c = _a.nodes, nodes = _c === void 0 ? [] : _c, className = _a.className, transform = _a.transform;
var connectionSourceId = _a.connectionSourceId, _b = _a.connectionLineStyle, connectionLineStyle = _b === void 0 ? {} : _b, connectionPositionX = _a.connectionPositionX, connectionPositionY = _a.connectionPositionY, connectionLineType = _a.connectionLineType, _c = _a.nodes, nodes = _c === void 0 ? [] : _c, className = _a.className, transform = _a.transform, isInteractive = _a.isInteractive;
var _d = useState(null), sourceNode = _d[0], setSourceNode = _d[1];
var hasHandleId = connectionSourceId.includes('__');
var sourceIdSplitted = connectionSourceId.split('__');
@@ -6105,19 +6114,15 @@ var ConnectionLine = (function (_a) {
var nextSourceNode = nodes.find(function (n) { return n.id === nodeId; }) || null;
setSourceNode(nextSourceNode);
}, []);
if (!sourceNode) {
if (!sourceNode || !isInteractive) {
return null;
}
var edgeClasses = classnames('react-flow__edge', 'connection', className);
var sourceHandle = handleId
? sourceNode.__rg.handleBounds.source.find(function (d) { return d.id === handleId; })
: sourceNode.__rg.handleBounds.source[0];
var sourceHandleX = sourceHandle
? sourceHandle.x + sourceHandle.width / 2
: sourceNode.__rg.width / 2;
var sourceHandleY = sourceHandle
? sourceHandle.y + sourceHandle.height / 2
: sourceNode.__rg.height;
var sourceHandleX = sourceHandle ? 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 sourceY = sourceNode.__rg.position.y + sourceHandleY;
var targetX = (connectionPositionX - transform[0]) * (1 / transform[2]);
@@ -6226,7 +6231,7 @@ function getEdgePositions(sourceNode, sourceHandle, sourcePosition, targetNode,
targetY: targetY,
};
}
function renderEdge(edge, props, nodes, selectedElements) {
function renderEdge(edge, props, nodes, selectedElements, isInteractive) {
var _a = edge.source.split('__'), sourceId = _a[0], sourceHandleId = _a[1];
var _b = edge.target.split('__'), targetId = _b[0], targetHandleId = _b[1];
var sourceNode = nodes.find(function (n) { return n.id === sourceId; });
@@ -6248,10 +6253,18 @@ function renderEdge(edge, props, nodes, selectedElements) {
var targetPosition = targetHandle ? targetHandle.position : Position.Top;
var _c = getEdgePositions(sourceNode, sourceHandle, sourcePosition, targetNode, targetHandle, targetPosition), sourceX = _c.sourceX, sourceY = _c.sourceY, targetX = _c.targetX, targetY = _c.targetY;
var isSelected = selectedElements.some(function (elm) { return isEdge(elm) && elm.source === sourceId && elm.target === targetId; });
return (React.createElement(EdgeComponent, { key: edge.id, id: edge.id, type: edge.type, onClick: props.onElementClick, selected: isSelected, animated: edge.animated, style: edge.style, source: sourceId, target: targetId, sourceHandleId: sourceHandleId, targetHandleId: targetHandleId, sourceX: sourceX, sourceY: sourceY, targetX: targetX, targetY: targetY, sourcePosition: sourcePosition, targetPosition: targetPosition }));
return (React.createElement(EdgeComponent, { key: edge.id, id: edge.id, type: edge.type, onClick: props.onElementClick, selected: isSelected, animated: edge.animated, style: edge.style, source: sourceId, target: targetId, sourceHandleId: sourceHandleId, targetHandleId: targetHandleId, sourceX: sourceX, sourceY: sourceY, targetX: targetX, targetY: targetY, sourcePosition: sourcePosition, targetPosition: targetPosition, isInteractive: isInteractive }));
}
var EdgeRenderer = memo(function (props) {
var _a = useStoreState$1(function (s) { return s; }), transform = _a.transform, edges = _a.edges, nodes = _a.nodes, connectionSourceId = _a.connectionSourceId, _b = _a.connectionPosition, x = _b.x, y = _b.y, selectedElements = _a.selectedElements;
var _a = useStoreState$1(function (s) { return ({
transform: s.transform,
edges: s.edges,
nodes: s.nodes,
connectionSourceId: s.connectionSourceId,
connectionPosition: s.connectionPosition,
selectedElements: s.selectedElements,
isInteractive: s.isInteractive,
}); }), transform = _a.transform, edges = _a.edges, nodes = _a.nodes, connectionSourceId = _a.connectionSourceId, _b = _a.connectionPosition, x = _b.x, y = _b.y, selectedElements = _a.selectedElements, isInteractive = _a.isInteractive;
var width = props.width, height = props.height, connectionLineStyle = props.connectionLineStyle, connectionLineType = props.connectionLineType;
if (!width) {
return null;
@@ -6260,8 +6273,8 @@ var EdgeRenderer = memo(function (props) {
var transformStyle = "translate(" + tx + "," + ty + ") scale(" + tScale + ")";
return (React.createElement("svg", { width: width, height: height, className: "react-flow__edges" },
React.createElement("g", { transform: transformStyle },
edges.map(function (e) { return renderEdge(e, props, nodes, selectedElements); }),
connectionSourceId && (React.createElement(ConnectionLine, { nodes: nodes, connectionSourceId: connectionSourceId, connectionPositionX: x, connectionPositionY: y, transform: transform, connectionLineStyle: connectionLineStyle, connectionLineType: connectionLineType })))));
edges.map(function (e) { return renderEdge(e, props, nodes, selectedElements, isInteractive); }),
connectionSourceId && (React.createElement(ConnectionLine, { nodes: nodes, connectionSourceId: connectionSourceId, connectionPositionX: x, connectionPositionY: y, transform: transform, connectionLineStyle: connectionLineStyle, connectionLineType: connectionLineType, isInteractive: isInteractive })))));
});
EdgeRenderer.displayName = 'EdgeRenderer';
@@ -6285,12 +6298,16 @@ function getMousePosition(evt) {
y: evt.clientY - containerBounds.top,
};
}
var UserSelection = memo(function () {
var UserSelection = memo(function (_a) {
var isInteractive = _a.isInteractive;
var selectionPane = useRef(null);
var _a = useState(initialRect), rect = _a[0], setRect = _a[1];
var _b = useState(initialRect), rect = _b[0], setRect = _b[1];
var setSelection = useStoreActions$1(function (a) { return a.setSelection; });
var updateSelection = useStoreActions$1(function (a) { return a.updateSelection; });
var setNodesSelection = useStoreActions$1(function (a) { return a.setNodesSelection; });
if (!isInteractive) {
return null;
}
useEffect(function () {
function onMouseDown(evt) {
var mousePos = getMousePosition(evt);
@@ -6311,11 +6328,7 @@ var UserSelection = memo(function () {
}
var negativeX = mousePos.x < currentRect.startX;
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);
return nextRect;
});
@@ -8131,7 +8144,7 @@ Grid.displayName = 'Grid';
var isInputDOMNode = function (e) {
var target = e.target;
return (e && target && ['INPUT', 'SELECT', 'TEXTAREA'].includes(target.nodeName));
return e && target && ['INPUT', 'SELECT', 'TEXTAREA', 'BUTTON'].includes(target.nodeName);
};
var getDimensions = function (node) { return ({
width: node.offsetWidth,
@@ -8184,8 +8197,7 @@ var useD3Zoom = (function (zoomPane, onMove, shiftPressed) {
}
else {
d3ZoomInstance.on('zoom', function () {
if (event.sourceEvent &&
event.sourceEvent.target !== zoomPane.current) {
if (event.sourceEvent && event.sourceEvent.target !== zoomPane.current) {
return;
}
updateTransform(event.transform);
@@ -8267,7 +8279,7 @@ var useElementUpdater = function (elements) {
};
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, snapToGrid = _a.snapToGrid, snapGrid = _a.snapGrid, onlyRenderVisibleNodes = _a.onlyRenderVisibleNodes;
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, onlyRenderVisibleNodes = _a.onlyRenderVisibleNodes, isInteractive = _a.isInteractive;
var zoomPane = useRef(null);
var rendererNode = useRef(null);
var state = useStoreState$1(function (s) { return ({
@@ -8282,7 +8294,9 @@ var GraphView = memo(function (_a) {
var setNodesSelection = useStoreActions$1(function (actions) { return actions.setNodesSelection; });
var setOnConnect = useStoreActions$1(function (a) { return a.setOnConnect; });
var setSnapGrid = useStoreActions$1(function (actions) { return actions.setSnapGrid; });
var setInteractive = useStoreActions$1(function (actions) { return actions.setInteractive; });
var selectionKeyPressed = useKeyPress(selectionKeyCode);
var rendererClasses = classnames('react-flow__renderer', { 'is-interactive': isInteractive });
var onZoomPaneClick = function () { return setNodesSelection({ isActive: false }); };
var updateDimensions = function () {
if (!rendererNode.current) {
@@ -8312,13 +8326,16 @@ var GraphView = memo(function (_a) {
useEffect(function () {
setSnapGrid({ snapToGrid: snapToGrid, snapGrid: snapGrid });
}, [snapToGrid]);
useEffect(function () {
setInteractive(isInteractive);
}, [isInteractive]);
useGlobalKeyHandler({ onElementsRemove: onElementsRemove, deleteKeyCode: deleteKeyCode });
useElementUpdater(elements);
return (React.createElement("div", { className: "react-flow__renderer", ref: rendererNode },
return (React.createElement("div", { className: rendererClasses, ref: rendererNode },
showBackground && (React.createElement(Grid, { gap: backgroundGap, color: backgroundColor, backgroundType: backgroundType })),
React.createElement(NodeRenderer, { nodeTypes: nodeTypes, onElementClick: onElementClick, onNodeDragStop: onNodeDragStop, onlyRenderVisibleNodes: onlyRenderVisibleNodes }),
React.createElement(EdgeRenderer, { width: state.width, height: state.height, edgeTypes: edgeTypes, onElementClick: onElementClick, connectionLineType: connectionLineType, connectionLineStyle: connectionLineStyle }),
selectionKeyPressed && React.createElement(UserSelection, null),
selectionKeyPressed && React.createElement(UserSelection, { isInteractive: isInteractive }),
state.nodesSelectionActive && React.createElement(NodesSelection, null),
React.createElement("div", { className: "react-flow__zoompane", onClick: onZoomPaneClick, ref: zoomPane })));
});
@@ -8441,11 +8458,11 @@ var nodeStyles = {
width: 150,
};
var DefaultNode = (function (_a) {
var data = _a.data, style = _a.style;
var data = _a.data, _b = _a.targetPosition, targetPosition = _b === void 0 ? Position.Top : _b, _c = _a.sourcePosition, sourcePosition = _c === void 0 ? Position.Bottom : _c, style = _a.style;
return (React.createElement("div", { style: __assign(__assign({}, nodeStyles), style) },
React.createElement(Handle, { type: "target", position: Position.Top }),
React.createElement(Handle, { type: "target", position: targetPosition }),
data.label,
React.createElement(Handle, { type: "source", position: Position.Bottom })));
React.createElement(Handle, { type: "source", position: sourcePosition })));
});
var nodeStyles$1 = {
@@ -8455,10 +8472,10 @@ var nodeStyles$1 = {
width: 150,
};
var InputNode = (function (_a) {
var data = _a.data, style = _a.style;
var data = _a.data, style = _a.style, _b = _a.sourcePosition, sourcePosition = _b === void 0 ? Position.Bottom : _b;
return (React.createElement("div", { style: __assign(__assign({}, nodeStyles$1), style) },
data.label,
React.createElement(Handle, { type: "source", position: Position.Bottom })));
React.createElement(Handle, { type: "source", position: sourcePosition })));
});
var nodeStyles$2 = {
@@ -8468,9 +8485,9 @@ var nodeStyles$2 = {
width: 150,
};
var OutputNode = (function (_a) {
var data = _a.data, style = _a.style;
var data = _a.data, style = _a.style, _b = _a.targetPosition, targetPosition = _b === void 0 ? Position.Top : _b;
return (React.createElement("div", { style: __assign(__assign({}, nodeStyles$2), style) },
React.createElement(Handle, { type: "target", position: Position.Top }),
React.createElement(Handle, { type: "target", position: targetPosition }),
data.label));
});
@@ -8751,9 +8768,7 @@ var getHandleBounds = function (selector, nodeElement, parentBounds, k) {
var nodeIdSplitted = nodeIdAttr ? nodeIdAttr.split('__') : null;
var handleId = null;
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);
});
@@ -8800,7 +8815,7 @@ var onStop = function (onNodeDragStop, isDragging, setDragging, id, type, positi
};
var wrapNode = (function (NodeComponent) {
var NodeWrapper = memo(function (_a) {
var id = _a.id, type = _a.type, data = _a.data, transform = _a.transform, xPos = _a.xPos, yPos = _a.yPos, selected = _a.selected, onClick = _a.onClick, onNodeDragStop = _a.onNodeDragStop, style = _a.style;
var id = _a.id, type = _a.type, data = _a.data, transform = _a.transform, xPos = _a.xPos, yPos = _a.yPos, selected = _a.selected, onClick = _a.onClick, onNodeDragStop = _a.onNodeDragStop, style = _a.style, isInteractive = _a.isInteractive, sourcePosition = _a.sourcePosition, targetPosition = _a.targetPosition;
var nodeElement = useRef(null);
var _b = useState({ x: 0, y: 0 }), offset = _b[0], setOffset = _b[1];
var _c = useState(false), isDragging = _c[0], setDragging = _c[1];
@@ -8841,16 +8856,10 @@ var wrapNode = (function (NodeComponent) {
}
return;
}, [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], disabled: !isInteractive },
React.createElement("div", { className: nodeClasses, ref: nodeElement, style: nodeStyle },
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, sourcePosition: sourcePosition, targetPosition: targetPosition })))));
});
NodeWrapper.displayName = 'NodeWrapper';
return NodeWrapper;
@@ -8903,10 +8912,10 @@ var StepEdge = memo(function (_a) {
var wrapEdge = (function (EdgeComponent) {
var EdgeWrapper = memo(function (_a) {
var id = _a.id, source = _a.source, target = _a.target, type = _a.type, animated = _a.animated, selected = _a.selected, onClick = _a.onClick, rest = __rest(_a, ["id", "source", "target", "type", "animated", "selected", "onClick"]);
var id = _a.id, source = _a.source, target = _a.target, type = _a.type, animated = _a.animated, selected = _a.selected, onClick = _a.onClick, isInteractive = _a.isInteractive, rest = __rest(_a, ["id", "source", "target", "type", "animated", "selected", "onClick", "isInteractive"]);
var edgeClasses = classnames('react-flow__edge', { selected: selected, animated: animated });
var onEdgeClick = function (evt) {
if (isInputDOMNode(evt)) {
if (isInputDOMNode(evt) || !isInteractive) {
return;
}
store.dispatch.setSelectedElements({ id: id, source: source, target: target });
@@ -8961,16 +8970,16 @@ function styleInject(css, ref) {
}
}
var css = ".react-flow {\n width: 100%;\n height: 100%;\n position: relative;\n overflow: hidden;\n}\n\n.react-flow__renderer {\n width: 100%;\n height: 100%;\n position: absolute;\n}\n\n.react-flow__zoompane {\n width: 100%;\n height: 100%;\n position: absolute;\n top: 0;\n left: 0;\n z-index: 1;\n}\n\n.react-flow__selectionpane {\n width: 100%;\n height: 100%;\n position: absolute;\n top: 0;\n left: 0;\n z-index: 2;\n}\n\n.react-flow__selection {\n position: absolute;\n top: 0;\n left: 0;\n background: rgba(0, 89, 220, 0.08);\n border: 1px dotted rgba(0, 89, 220, 0.8);\n}\n\n.react-flow__edges {\n position: absolute;\n top: 0;\n left: 0;\n pointer-events: none;\n z-index: 2;\n}\n\n.react-flow__edge {\n fill: none;\n stroke: #bbb;\n stroke-width: 2;\n pointer-events: all;\n}\n\n.react-flow__edge.selected {\n stroke: #555;\n }\n\n.react-flow__edge.animated {\n stroke-dasharray: 5;\n -webkit-animation: dashdraw 0.5s linear infinite;\n animation: dashdraw 0.5s linear infinite;\n }\n\n.react-flow__edge.connection {\n stroke: '#ddd';\n pointer-events: none;\n }\n\n@-webkit-keyframes dashdraw {\n from {stroke-dashoffset: 10}\n}\n\n@keyframes dashdraw {\n from {stroke-dashoffset: 10}\n}\n\n.react-flow__nodes {\n width: 100%;\n height: 100%;\n position: absolute;\n z-index: 3;\n pointer-events: none;\n transform-origin: 0 0;\n}\n\n.react-flow__node {\n position: absolute;\n color: #222;\n font-family: sans-serif;\n font-size: 12px;\n text-align: center;\n cursor: -webkit-grab;\n cursor: grab;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n pointer-events: all;\n transform-origin: 0 0;\n}\n\n.react-flow__node:hover > * {\n box-shadow: 0 1px 5px 2px rgba(0, 0, 0, 0.08);\n }\n\n.react-flow__node.selected > * {\n box-shadow: 0 0 0 2px #555;\n }\n\n.react-flow__handle {\n position: absolute;\n width: 10px;\n height: 8px;\n background: rgba(255, 255, 255, 0.4);\n cursor: crosshair;\n}\n\n.react-flow__handle.bottom {\n top: auto;\n left: 50%;\n bottom: 0;\n transform: translate(-50%, 0);\n }\n\n.react-flow__handle.top {\n left: 50%;\n top: 0;\n transform: translate(-50%, 0);\n }\n\n.react-flow__handle.left {\n top: 50%;\n left: 0;\n transform: translate(0, -50%);\n\n }\n\n.react-flow__handle.right {\n right: 0;\n top: 50%;\n transform: translate(0, -50%);\n }\n\n.react-flow__nodesselection {\n z-index: 3;\n position: absolute;\n width: 100%;\n height: 100%;\n top: 0;\n left: 0;\n transform-origin: left top;\n pointer-events: none;\n}\n\n.react-flow__nodesselection-rect {\n position: absolute;\n background: rgba(0, 89, 220, 0.08);\n border: 1px dotted rgba(0, 89, 220, 0.8);\n pointer-events: all;\n }\n\n.react-flow__controls {\n box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.08);\n}\n\n.react-flow__controls-button {\n background: #fefefe;\n border-bottom: 1px solid #eee;\n display: flex;\n justify-content: center;\n align-items: center;\n width: 16px;\n height: 16px;\n cursor: pointer;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n padding: 5px;\n }\n\n.react-flow__controls-button svg {\n width: 100%;\n }\n\n.react-flow__controls-button:hover {\n background: #f4f4f4;\n }\n";
var css = ".react-flow {\n width: 100%;\n height: 100%;\n position: relative;\n overflow: hidden;\n}\n\n.react-flow__renderer {\n width: 100%;\n height: 100%;\n position: absolute;\n}\n\n.react-flow__zoompane {\n width: 100%;\n height: 100%;\n position: absolute;\n top: 0;\n left: 0;\n z-index: 1;\n}\n\n.react-flow__selectionpane {\n width: 100%;\n height: 100%;\n position: absolute;\n top: 0;\n left: 0;\n z-index: 2;\n}\n\n.react-flow__selection {\n position: absolute;\n top: 0;\n left: 0;\n background: rgba(0, 89, 220, 0.08);\n border: 1px dotted rgba(0, 89, 220, 0.8);\n}\n\n.react-flow__edges {\n position: absolute;\n top: 0;\n left: 0;\n pointer-events: none;\n z-index: 2;\n}\n\n.react-flow__edge {\n fill: none;\n stroke: #bbb;\n stroke-width: 2;\n pointer-events: all;\n}\n\n.react-flow__edge.selected {\n stroke: #555;\n }\n\n.react-flow__edge.animated {\n stroke-dasharray: 5;\n -webkit-animation: dashdraw 0.5s linear infinite;\n animation: dashdraw 0.5s linear infinite;\n }\n\n.react-flow__edge.connection {\n stroke: '#ddd';\n pointer-events: none;\n }\n\n@-webkit-keyframes dashdraw {\n from {\n stroke-dashoffset: 10;\n }\n}\n\n@keyframes dashdraw {\n from {\n stroke-dashoffset: 10;\n }\n}\n\n.react-flow__nodes {\n width: 100%;\n height: 100%;\n position: absolute;\n z-index: 3;\n pointer-events: none;\n transform-origin: 0 0;\n}\n\n.is-interactive .react-flow__node {\n cursor: -webkit-grab;\n cursor: grab;\n }\n\n.is-interactive .react-flow__node:hover > * {\n box-shadow: 0 1px 5px 2px rgba(0, 0, 0, 0.08);\n }\n\n.is-interactive .react-flow__handle {\n cursor: crosshair;\n }\n\n.react-flow__node {\n position: absolute;\n color: #222;\n font-family: sans-serif;\n font-size: 12px;\n text-align: center;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n pointer-events: all;\n transform-origin: 0 0;\n}\n\n.react-flow__node.selected > * {\n box-shadow: 0 0 0 2px #555;\n }\n\n.react-flow__handle {\n position: absolute;\n width: 10px;\n height: 8px;\n background: rgba(255, 255, 255, 0.4);\n}\n\n.react-flow__handle.bottom {\n top: auto;\n left: 50%;\n bottom: 0;\n transform: translate(-50%, 0);\n }\n\n.react-flow__handle.top {\n left: 50%;\n top: 0;\n transform: translate(-50%, 0);\n }\n\n.react-flow__handle.left {\n top: 50%;\n left: 0;\n transform: translate(0, -50%);\n }\n\n.react-flow__handle.right {\n right: 0;\n top: 50%;\n transform: translate(0, -50%);\n }\n\n.react-flow__nodesselection {\n z-index: 3;\n position: absolute;\n width: 100%;\n height: 100%;\n top: 0;\n left: 0;\n transform-origin: left top;\n pointer-events: none;\n}\n\n.react-flow__nodesselection-rect {\n position: absolute;\n background: rgba(0, 89, 220, 0.08);\n border: 1px dotted rgba(0, 89, 220, 0.8);\n pointer-events: all;\n }\n\n.react-flow__controls {\n box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.08);\n}\n\n.react-flow__controls-button {\n background: #fefefe;\n border-bottom: 1px solid #eee;\n display: flex;\n justify-content: center;\n align-items: center;\n width: 16px;\n height: 16px;\n cursor: pointer;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n padding: 5px;\n }\n\n.react-flow__controls-button svg {\n width: 100%;\n }\n\n.react-flow__controls-button:hover {\n background: #f4f4f4;\n }\n";
styleInject(css);
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, snapToGrid = _a.snapToGrid, snapGrid = _a.snapGrid, onlyRenderVisibleNodes = _a.onlyRenderVisibleNodes;
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, onlyRenderVisibleNodes = _a.onlyRenderVisibleNodes, isInteractive = _a.isInteractive;
var nodeTypesParsed = useMemo(function () { return createNodeTypes(nodeTypes); }, []);
var edgeTypesParsed = useMemo(function () { return createEdgeTypes(edgeTypes); }, []);
return (React.createElement("div", { style: style, className: "react-flow" },
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, snapToGrid: snapToGrid, snapGrid: snapGrid, onlyRenderVisibleNodes: onlyRenderVisibleNodes }),
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, onlyRenderVisibleNodes: onlyRenderVisibleNodes, isInteractive: isInteractive }),
children)));
};
ReactFlow.displayName = 'ReactFlow';
@@ -9001,7 +9010,8 @@ ReactFlow.defaultProps = {
backgroundType: GridType.Dots,
snapToGrid: false,
snapGrid: [16, 16],
onlyRenderVisibleNodes: true
onlyRenderVisibleNodes: true,
isInteractive: true,
};
var baseStyle = {

File diff suppressed because one or more lines are too long

248
dist/ReactFlow.js vendored
View File

@@ -2529,6 +2529,63 @@ var StoreProvider = function StoreProvider(_ref) {
setAutoFreeze(false);
function unwrapExports (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
function createCommonjsModule(fn, module) {
return module = { exports: {} }, fn(module, module.exports), module.exports;
}
var classnames = createCommonjsModule(function (module) {
/*!
Copyright (c) 2017 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/
/* global define */
(function () {
var hasOwn = {}.hasOwnProperty;
function classNames () {
var classes = [];
for (var i = 0; i < arguments.length; i++) {
var arg = arguments[i];
if (!arg) continue;
var argType = typeof arg;
if (argType === 'string' || argType === 'number') {
classes.push(arg);
} else if (Array.isArray(arg) && arg.length) {
var inner = classNames.apply(null, arg);
if (inner) {
classes.push(inner);
}
} else if (argType === 'object') {
for (var key in arg) {
if (hasOwn.call(arg, key) && arg[key]) {
classes.push(key);
}
}
}
}
return classes.join(' ');
}
if ( module.exports) {
classNames.default = classNames;
module.exports = classNames;
} else {
window.classNames = classNames;
}
}());
});
var typedHooks = createTypedHooks();
var useStoreActions$1 = typedHooks.useStoreActions;
var useStoreState$1 = typedHooks.useStoreState;
@@ -5765,6 +5822,7 @@ var storeModel = {
connectionPosition: { x: 0, y: 0 },
snapGrid: [16, 16],
snapToGrid: true,
isInteractive: true,
onConnect: function () { },
setOnConnect: action(function (state, onConnect) {
state.onConnect = onConnect;
@@ -5825,9 +5883,7 @@ var storeModel = {
setSelectedElements: action(function (state, elements) {
var selectedElementsArr = Array.isArray(elements) ? elements : [elements];
var selectedElementsUpdated = !fastDeepEqual(selectedElementsArr, state.selectedElements);
var selectedElements = selectedElementsUpdated
? selectedElementsArr
: state.selectedElements;
var selectedElements = selectedElementsUpdated ? selectedElementsArr : state.selectedElements;
state.selectedElements = selectedElements;
}),
updateSelection: action(function (state, selection) {
@@ -5836,9 +5892,7 @@ var storeModel = {
var nextSelectedElements = __spreadArrays(selectedNodes, selectedEdges);
var selectedElementsUpdated = !fastDeepEqual(nextSelectedElements, state.selectedElements);
state.selection = selection;
state.selectedElements = selectedElementsUpdated
? nextSelectedElements
: state.selectedElements;
state.selectedElements = selectedElementsUpdated ? nextSelectedElements : state.selectedElements;
}),
updateTransform: action(function (state, transform) {
state.transform = [transform.x, transform.y, transform.k];
@@ -5864,6 +5918,9 @@ var storeModel = {
state.snapToGrid = snapToGrid;
state.snapGrid = snapGrid;
}),
setInteractive: action(function (state, isInteractive) {
state.isInteractive = isInteractive;
}),
};
var store = createStore$1(storeModel);
@@ -6020,7 +6077,7 @@ var zoom$1 = function (amount) {
var zoomIn = function () { return zoom$1(0.2); };
var zoomOut = function () { return zoom$1(-0.2); };
function renderNode(node, props, transform, selectedElements) {
function renderNode(node, props, transform, selectedElements, isInteractive) {
var nodeType = node.type || 'default';
var NodeComponent = (props.nodeTypes[nodeType] || props.nodeTypes.default);
if (!props.nodeTypes[nodeType]) {
@@ -6030,79 +6087,31 @@ function renderNode(node, props, transform, selectedElements) {
var id = _a.id;
return id === node.id;
});
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: transform, selected: isSelected, 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: transform, selected: isSelected, style: node.style, isInteractive: isInteractive, sourcePosition: node.sourcePosition, targetPosition: node.targetPosition }));
}
var NodeRenderer = React.memo(function (_a) {
var _b = _a.onlyRenderVisibleNodes, onlyRenderVisibleNodes = _b === void 0 ? true : _b, props = __rest(_a, ["onlyRenderVisibleNodes"]);
var _c = useStoreState$1(function (s) { return s; }), nodes = _c.nodes, transform = _c.transform, selectedElements = _c.selectedElements, width = _c.width, height = _c.height;
var _c = useStoreState$1(function (s) { return ({
nodes: s.nodes,
transform: s.transform,
selectedElements: s.selectedElements,
width: s.width,
height: s.height,
isInteractive: s.isInteractive,
}); }), nodes = _c.nodes, transform = _c.transform, selectedElements = _c.selectedElements, width = _c.width, height = _c.height, isInteractive = _c.isInteractive;
var tx = transform[0], ty = transform[1], tScale = transform[2];
var transformStyle = {
transform: "translate(" + tx + "px," + ty + "px) scale(" + tScale + ")",
};
var renderNodes = onlyRenderVisibleNodes ? getNodesInside(nodes, { x: 0, y: 0, width: width, height: height }, transform, true) : nodes;
return (React__default.createElement("div", { className: "react-flow__nodes", style: transformStyle }, renderNodes.map(function (node) { return renderNode(node, props, transform, selectedElements); })));
var renderNodes = onlyRenderVisibleNodes
? getNodesInside(nodes, { x: 0, y: 0, width: width, height: height }, transform, true)
: nodes;
return (React__default.createElement("div", { className: "react-flow__nodes", style: transformStyle }, renderNodes.map(function (node) { return renderNode(node, props, transform, selectedElements, isInteractive); })));
});
NodeRenderer.displayName = 'NodeRenderer';
function unwrapExports (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
function createCommonjsModule(fn, module) {
return module = { exports: {} }, fn(module, module.exports), module.exports;
}
var classnames = createCommonjsModule(function (module) {
/*!
Copyright (c) 2017 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/
/* global define */
(function () {
var hasOwn = {}.hasOwnProperty;
function classNames () {
var classes = [];
for (var i = 0; i < arguments.length; i++) {
var arg = arguments[i];
if (!arg) continue;
var argType = typeof arg;
if (argType === 'string' || argType === 'number') {
classes.push(arg);
} else if (Array.isArray(arg) && arg.length) {
var inner = classNames.apply(null, arg);
if (inner) {
classes.push(inner);
}
} else if (argType === 'object') {
for (var key in arg) {
if (hasOwn.call(arg, key) && arg[key]) {
classes.push(key);
}
}
}
}
return classes.join(' ');
}
if ( module.exports) {
classNames.default = classNames;
module.exports = classNames;
} else {
window.classNames = classNames;
}
}());
});
var ConnectionLine = (function (_a) {
var connectionSourceId = _a.connectionSourceId, _b = _a.connectionLineStyle, connectionLineStyle = _b === void 0 ? {} : _b, connectionPositionX = _a.connectionPositionX, connectionPositionY = _a.connectionPositionY, connectionLineType = _a.connectionLineType, _c = _a.nodes, nodes = _c === void 0 ? [] : _c, className = _a.className, transform = _a.transform;
var connectionSourceId = _a.connectionSourceId, _b = _a.connectionLineStyle, connectionLineStyle = _b === void 0 ? {} : _b, connectionPositionX = _a.connectionPositionX, connectionPositionY = _a.connectionPositionY, connectionLineType = _a.connectionLineType, _c = _a.nodes, nodes = _c === void 0 ? [] : _c, className = _a.className, transform = _a.transform, isInteractive = _a.isInteractive;
var _d = React.useState(null), sourceNode = _d[0], setSourceNode = _d[1];
var hasHandleId = connectionSourceId.includes('__');
var sourceIdSplitted = connectionSourceId.split('__');
@@ -6112,19 +6121,15 @@ var ConnectionLine = (function (_a) {
var nextSourceNode = nodes.find(function (n) { return n.id === nodeId; }) || null;
setSourceNode(nextSourceNode);
}, []);
if (!sourceNode) {
if (!sourceNode || !isInteractive) {
return null;
}
var edgeClasses = classnames('react-flow__edge', 'connection', className);
var sourceHandle = handleId
? sourceNode.__rg.handleBounds.source.find(function (d) { return d.id === handleId; })
: sourceNode.__rg.handleBounds.source[0];
var sourceHandleX = sourceHandle
? sourceHandle.x + sourceHandle.width / 2
: sourceNode.__rg.width / 2;
var sourceHandleY = sourceHandle
? sourceHandle.y + sourceHandle.height / 2
: sourceNode.__rg.height;
var sourceHandleX = sourceHandle ? 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 sourceY = sourceNode.__rg.position.y + sourceHandleY;
var targetX = (connectionPositionX - transform[0]) * (1 / transform[2]);
@@ -6233,7 +6238,7 @@ function getEdgePositions(sourceNode, sourceHandle, sourcePosition, targetNode,
targetY: targetY,
};
}
function renderEdge(edge, props, nodes, selectedElements) {
function renderEdge(edge, props, nodes, selectedElements, isInteractive) {
var _a = edge.source.split('__'), sourceId = _a[0], sourceHandleId = _a[1];
var _b = edge.target.split('__'), targetId = _b[0], targetHandleId = _b[1];
var sourceNode = nodes.find(function (n) { return n.id === sourceId; });
@@ -6255,10 +6260,18 @@ function renderEdge(edge, props, nodes, selectedElements) {
var targetPosition = targetHandle ? targetHandle.position : Position.Top;
var _c = getEdgePositions(sourceNode, sourceHandle, sourcePosition, targetNode, targetHandle, targetPosition), sourceX = _c.sourceX, sourceY = _c.sourceY, targetX = _c.targetX, targetY = _c.targetY;
var isSelected = selectedElements.some(function (elm) { return isEdge(elm) && elm.source === sourceId && elm.target === targetId; });
return (React__default.createElement(EdgeComponent, { key: edge.id, id: edge.id, type: edge.type, onClick: props.onElementClick, selected: isSelected, animated: edge.animated, style: edge.style, source: sourceId, target: targetId, sourceHandleId: sourceHandleId, targetHandleId: targetHandleId, sourceX: sourceX, sourceY: sourceY, targetX: targetX, targetY: targetY, sourcePosition: sourcePosition, targetPosition: targetPosition }));
return (React__default.createElement(EdgeComponent, { key: edge.id, id: edge.id, type: edge.type, onClick: props.onElementClick, selected: isSelected, animated: edge.animated, style: edge.style, source: sourceId, target: targetId, sourceHandleId: sourceHandleId, targetHandleId: targetHandleId, sourceX: sourceX, sourceY: sourceY, targetX: targetX, targetY: targetY, sourcePosition: sourcePosition, targetPosition: targetPosition, isInteractive: isInteractive }));
}
var EdgeRenderer = React.memo(function (props) {
var _a = useStoreState$1(function (s) { return s; }), transform = _a.transform, edges = _a.edges, nodes = _a.nodes, connectionSourceId = _a.connectionSourceId, _b = _a.connectionPosition, x = _b.x, y = _b.y, selectedElements = _a.selectedElements;
var _a = useStoreState$1(function (s) { return ({
transform: s.transform,
edges: s.edges,
nodes: s.nodes,
connectionSourceId: s.connectionSourceId,
connectionPosition: s.connectionPosition,
selectedElements: s.selectedElements,
isInteractive: s.isInteractive,
}); }), transform = _a.transform, edges = _a.edges, nodes = _a.nodes, connectionSourceId = _a.connectionSourceId, _b = _a.connectionPosition, x = _b.x, y = _b.y, selectedElements = _a.selectedElements, isInteractive = _a.isInteractive;
var width = props.width, height = props.height, connectionLineStyle = props.connectionLineStyle, connectionLineType = props.connectionLineType;
if (!width) {
return null;
@@ -6267,8 +6280,8 @@ var EdgeRenderer = React.memo(function (props) {
var transformStyle = "translate(" + tx + "," + ty + ") scale(" + tScale + ")";
return (React__default.createElement("svg", { width: width, height: height, className: "react-flow__edges" },
React__default.createElement("g", { transform: transformStyle },
edges.map(function (e) { return renderEdge(e, props, nodes, selectedElements); }),
connectionSourceId && (React__default.createElement(ConnectionLine, { nodes: nodes, connectionSourceId: connectionSourceId, connectionPositionX: x, connectionPositionY: y, transform: transform, connectionLineStyle: connectionLineStyle, connectionLineType: connectionLineType })))));
edges.map(function (e) { return renderEdge(e, props, nodes, selectedElements, isInteractive); }),
connectionSourceId && (React__default.createElement(ConnectionLine, { nodes: nodes, connectionSourceId: connectionSourceId, connectionPositionX: x, connectionPositionY: y, transform: transform, connectionLineStyle: connectionLineStyle, connectionLineType: connectionLineType, isInteractive: isInteractive })))));
});
EdgeRenderer.displayName = 'EdgeRenderer';
@@ -6292,12 +6305,16 @@ function getMousePosition(evt) {
y: evt.clientY - containerBounds.top,
};
}
var UserSelection = React.memo(function () {
var UserSelection = React.memo(function (_a) {
var isInteractive = _a.isInteractive;
var selectionPane = React.useRef(null);
var _a = React.useState(initialRect), rect = _a[0], setRect = _a[1];
var _b = React.useState(initialRect), rect = _b[0], setRect = _b[1];
var setSelection = useStoreActions$1(function (a) { return a.setSelection; });
var updateSelection = useStoreActions$1(function (a) { return a.updateSelection; });
var setNodesSelection = useStoreActions$1(function (a) { return a.setNodesSelection; });
if (!isInteractive) {
return null;
}
React.useEffect(function () {
function onMouseDown(evt) {
var mousePos = getMousePosition(evt);
@@ -6318,11 +6335,7 @@ var UserSelection = React.memo(function () {
}
var negativeX = mousePos.x < currentRect.startX;
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);
return nextRect;
});
@@ -8138,7 +8151,7 @@ Grid.displayName = 'Grid';
var isInputDOMNode = function (e) {
var target = e.target;
return (e && target && ['INPUT', 'SELECT', 'TEXTAREA'].includes(target.nodeName));
return e && target && ['INPUT', 'SELECT', 'TEXTAREA', 'BUTTON'].includes(target.nodeName);
};
var getDimensions = function (node) { return ({
width: node.offsetWidth,
@@ -8191,8 +8204,7 @@ var useD3Zoom = (function (zoomPane, onMove, shiftPressed) {
}
else {
d3ZoomInstance.on('zoom', function () {
if (event.sourceEvent &&
event.sourceEvent.target !== zoomPane.current) {
if (event.sourceEvent && event.sourceEvent.target !== zoomPane.current) {
return;
}
updateTransform(event.transform);
@@ -8274,7 +8286,7 @@ var useElementUpdater = function (elements) {
};
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, snapToGrid = _a.snapToGrid, snapGrid = _a.snapGrid, onlyRenderVisibleNodes = _a.onlyRenderVisibleNodes;
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, onlyRenderVisibleNodes = _a.onlyRenderVisibleNodes, isInteractive = _a.isInteractive;
var zoomPane = React.useRef(null);
var rendererNode = React.useRef(null);
var state = useStoreState$1(function (s) { return ({
@@ -8289,7 +8301,9 @@ var GraphView = React.memo(function (_a) {
var setNodesSelection = useStoreActions$1(function (actions) { return actions.setNodesSelection; });
var setOnConnect = useStoreActions$1(function (a) { return a.setOnConnect; });
var setSnapGrid = useStoreActions$1(function (actions) { return actions.setSnapGrid; });
var setInteractive = useStoreActions$1(function (actions) { return actions.setInteractive; });
var selectionKeyPressed = useKeyPress(selectionKeyCode);
var rendererClasses = classnames('react-flow__renderer', { 'is-interactive': isInteractive });
var onZoomPaneClick = function () { return setNodesSelection({ isActive: false }); };
var updateDimensions = function () {
if (!rendererNode.current) {
@@ -8319,13 +8333,16 @@ var GraphView = React.memo(function (_a) {
React.useEffect(function () {
setSnapGrid({ snapToGrid: snapToGrid, snapGrid: snapGrid });
}, [snapToGrid]);
React.useEffect(function () {
setInteractive(isInteractive);
}, [isInteractive]);
useGlobalKeyHandler({ onElementsRemove: onElementsRemove, deleteKeyCode: deleteKeyCode });
useElementUpdater(elements);
return (React__default.createElement("div", { className: "react-flow__renderer", ref: rendererNode },
return (React__default.createElement("div", { className: rendererClasses, ref: rendererNode },
showBackground && (React__default.createElement(Grid, { gap: backgroundGap, color: backgroundColor, backgroundType: backgroundType })),
React__default.createElement(NodeRenderer, { nodeTypes: nodeTypes, onElementClick: onElementClick, onNodeDragStop: onNodeDragStop, onlyRenderVisibleNodes: onlyRenderVisibleNodes }),
React__default.createElement(EdgeRenderer, { width: state.width, height: state.height, edgeTypes: edgeTypes, onElementClick: onElementClick, connectionLineType: connectionLineType, connectionLineStyle: connectionLineStyle }),
selectionKeyPressed && React__default.createElement(UserSelection, null),
selectionKeyPressed && React__default.createElement(UserSelection, { isInteractive: isInteractive }),
state.nodesSelectionActive && React__default.createElement(NodesSelection, null),
React__default.createElement("div", { className: "react-flow__zoompane", onClick: onZoomPaneClick, ref: zoomPane })));
});
@@ -8448,11 +8465,11 @@ var nodeStyles = {
width: 150,
};
var DefaultNode = (function (_a) {
var data = _a.data, style = _a.style;
var data = _a.data, _b = _a.targetPosition, targetPosition = _b === void 0 ? Position.Top : _b, _c = _a.sourcePosition, sourcePosition = _c === void 0 ? Position.Bottom : _c, style = _a.style;
return (React__default.createElement("div", { style: __assign(__assign({}, nodeStyles), style) },
React__default.createElement(Handle, { type: "target", position: Position.Top }),
React__default.createElement(Handle, { type: "target", position: targetPosition }),
data.label,
React__default.createElement(Handle, { type: "source", position: Position.Bottom })));
React__default.createElement(Handle, { type: "source", position: sourcePosition })));
});
var nodeStyles$1 = {
@@ -8462,10 +8479,10 @@ var nodeStyles$1 = {
width: 150,
};
var InputNode = (function (_a) {
var data = _a.data, style = _a.style;
var data = _a.data, style = _a.style, _b = _a.sourcePosition, sourcePosition = _b === void 0 ? Position.Bottom : _b;
return (React__default.createElement("div", { style: __assign(__assign({}, nodeStyles$1), style) },
data.label,
React__default.createElement(Handle, { type: "source", position: Position.Bottom })));
React__default.createElement(Handle, { type: "source", position: sourcePosition })));
});
var nodeStyles$2 = {
@@ -8475,9 +8492,9 @@ var nodeStyles$2 = {
width: 150,
};
var OutputNode = (function (_a) {
var data = _a.data, style = _a.style;
var data = _a.data, style = _a.style, _b = _a.targetPosition, targetPosition = _b === void 0 ? Position.Top : _b;
return (React__default.createElement("div", { style: __assign(__assign({}, nodeStyles$2), style) },
React__default.createElement(Handle, { type: "target", position: Position.Top }),
React__default.createElement(Handle, { type: "target", position: targetPosition }),
data.label));
});
@@ -8758,9 +8775,7 @@ var getHandleBounds = function (selector, nodeElement, parentBounds, k) {
var nodeIdSplitted = nodeIdAttr ? nodeIdAttr.split('__') : null;
var handleId = null;
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);
});
@@ -8807,7 +8822,7 @@ var onStop = function (onNodeDragStop, isDragging, setDragging, id, type, positi
};
var wrapNode = (function (NodeComponent) {
var NodeWrapper = React.memo(function (_a) {
var id = _a.id, type = _a.type, data = _a.data, transform = _a.transform, xPos = _a.xPos, yPos = _a.yPos, selected = _a.selected, onClick = _a.onClick, onNodeDragStop = _a.onNodeDragStop, style = _a.style;
var id = _a.id, type = _a.type, data = _a.data, transform = _a.transform, xPos = _a.xPos, yPos = _a.yPos, selected = _a.selected, onClick = _a.onClick, onNodeDragStop = _a.onNodeDragStop, style = _a.style, isInteractive = _a.isInteractive, sourcePosition = _a.sourcePosition, targetPosition = _a.targetPosition;
var nodeElement = React.useRef(null);
var _b = React.useState({ x: 0, y: 0 }), offset = _b[0], setOffset = _b[1];
var _c = React.useState(false), isDragging = _c[0], setDragging = _c[1];
@@ -8848,16 +8863,10 @@ var wrapNode = (function (NodeComponent) {
}
return;
}, [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], disabled: !isInteractive },
React__default.createElement("div", { className: nodeClasses, ref: nodeElement, style: nodeStyle },
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, sourcePosition: sourcePosition, targetPosition: targetPosition })))));
});
NodeWrapper.displayName = 'NodeWrapper';
return NodeWrapper;
@@ -8910,10 +8919,10 @@ var StepEdge = React.memo(function (_a) {
var wrapEdge = (function (EdgeComponent) {
var EdgeWrapper = React.memo(function (_a) {
var id = _a.id, source = _a.source, target = _a.target, type = _a.type, animated = _a.animated, selected = _a.selected, onClick = _a.onClick, rest = __rest(_a, ["id", "source", "target", "type", "animated", "selected", "onClick"]);
var id = _a.id, source = _a.source, target = _a.target, type = _a.type, animated = _a.animated, selected = _a.selected, onClick = _a.onClick, isInteractive = _a.isInteractive, rest = __rest(_a, ["id", "source", "target", "type", "animated", "selected", "onClick", "isInteractive"]);
var edgeClasses = classnames('react-flow__edge', { selected: selected, animated: animated });
var onEdgeClick = function (evt) {
if (isInputDOMNode(evt)) {
if (isInputDOMNode(evt) || !isInteractive) {
return;
}
store.dispatch.setSelectedElements({ id: id, source: source, target: target });
@@ -8968,16 +8977,16 @@ function styleInject(css, ref) {
}
}
var css = ".react-flow {\n width: 100%;\n height: 100%;\n position: relative;\n overflow: hidden;\n}\n\n.react-flow__renderer {\n width: 100%;\n height: 100%;\n position: absolute;\n}\n\n.react-flow__zoompane {\n width: 100%;\n height: 100%;\n position: absolute;\n top: 0;\n left: 0;\n z-index: 1;\n}\n\n.react-flow__selectionpane {\n width: 100%;\n height: 100%;\n position: absolute;\n top: 0;\n left: 0;\n z-index: 2;\n}\n\n.react-flow__selection {\n position: absolute;\n top: 0;\n left: 0;\n background: rgba(0, 89, 220, 0.08);\n border: 1px dotted rgba(0, 89, 220, 0.8);\n}\n\n.react-flow__edges {\n position: absolute;\n top: 0;\n left: 0;\n pointer-events: none;\n z-index: 2;\n}\n\n.react-flow__edge {\n fill: none;\n stroke: #bbb;\n stroke-width: 2;\n pointer-events: all;\n}\n\n.react-flow__edge.selected {\n stroke: #555;\n }\n\n.react-flow__edge.animated {\n stroke-dasharray: 5;\n -webkit-animation: dashdraw 0.5s linear infinite;\n animation: dashdraw 0.5s linear infinite;\n }\n\n.react-flow__edge.connection {\n stroke: '#ddd';\n pointer-events: none;\n }\n\n@-webkit-keyframes dashdraw {\n from {stroke-dashoffset: 10}\n}\n\n@keyframes dashdraw {\n from {stroke-dashoffset: 10}\n}\n\n.react-flow__nodes {\n width: 100%;\n height: 100%;\n position: absolute;\n z-index: 3;\n pointer-events: none;\n transform-origin: 0 0;\n}\n\n.react-flow__node {\n position: absolute;\n color: #222;\n font-family: sans-serif;\n font-size: 12px;\n text-align: center;\n cursor: -webkit-grab;\n cursor: grab;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n pointer-events: all;\n transform-origin: 0 0;\n}\n\n.react-flow__node:hover > * {\n box-shadow: 0 1px 5px 2px rgba(0, 0, 0, 0.08);\n }\n\n.react-flow__node.selected > * {\n box-shadow: 0 0 0 2px #555;\n }\n\n.react-flow__handle {\n position: absolute;\n width: 10px;\n height: 8px;\n background: rgba(255, 255, 255, 0.4);\n cursor: crosshair;\n}\n\n.react-flow__handle.bottom {\n top: auto;\n left: 50%;\n bottom: 0;\n transform: translate(-50%, 0);\n }\n\n.react-flow__handle.top {\n left: 50%;\n top: 0;\n transform: translate(-50%, 0);\n }\n\n.react-flow__handle.left {\n top: 50%;\n left: 0;\n transform: translate(0, -50%);\n\n }\n\n.react-flow__handle.right {\n right: 0;\n top: 50%;\n transform: translate(0, -50%);\n }\n\n.react-flow__nodesselection {\n z-index: 3;\n position: absolute;\n width: 100%;\n height: 100%;\n top: 0;\n left: 0;\n transform-origin: left top;\n pointer-events: none;\n}\n\n.react-flow__nodesselection-rect {\n position: absolute;\n background: rgba(0, 89, 220, 0.08);\n border: 1px dotted rgba(0, 89, 220, 0.8);\n pointer-events: all;\n }\n\n.react-flow__controls {\n box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.08);\n}\n\n.react-flow__controls-button {\n background: #fefefe;\n border-bottom: 1px solid #eee;\n display: flex;\n justify-content: center;\n align-items: center;\n width: 16px;\n height: 16px;\n cursor: pointer;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n padding: 5px;\n }\n\n.react-flow__controls-button svg {\n width: 100%;\n }\n\n.react-flow__controls-button:hover {\n background: #f4f4f4;\n }\n";
var css = ".react-flow {\n width: 100%;\n height: 100%;\n position: relative;\n overflow: hidden;\n}\n\n.react-flow__renderer {\n width: 100%;\n height: 100%;\n position: absolute;\n}\n\n.react-flow__zoompane {\n width: 100%;\n height: 100%;\n position: absolute;\n top: 0;\n left: 0;\n z-index: 1;\n}\n\n.react-flow__selectionpane {\n width: 100%;\n height: 100%;\n position: absolute;\n top: 0;\n left: 0;\n z-index: 2;\n}\n\n.react-flow__selection {\n position: absolute;\n top: 0;\n left: 0;\n background: rgba(0, 89, 220, 0.08);\n border: 1px dotted rgba(0, 89, 220, 0.8);\n}\n\n.react-flow__edges {\n position: absolute;\n top: 0;\n left: 0;\n pointer-events: none;\n z-index: 2;\n}\n\n.react-flow__edge {\n fill: none;\n stroke: #bbb;\n stroke-width: 2;\n pointer-events: all;\n}\n\n.react-flow__edge.selected {\n stroke: #555;\n }\n\n.react-flow__edge.animated {\n stroke-dasharray: 5;\n -webkit-animation: dashdraw 0.5s linear infinite;\n animation: dashdraw 0.5s linear infinite;\n }\n\n.react-flow__edge.connection {\n stroke: '#ddd';\n pointer-events: none;\n }\n\n@-webkit-keyframes dashdraw {\n from {\n stroke-dashoffset: 10;\n }\n}\n\n@keyframes dashdraw {\n from {\n stroke-dashoffset: 10;\n }\n}\n\n.react-flow__nodes {\n width: 100%;\n height: 100%;\n position: absolute;\n z-index: 3;\n pointer-events: none;\n transform-origin: 0 0;\n}\n\n.is-interactive .react-flow__node {\n cursor: -webkit-grab;\n cursor: grab;\n }\n\n.is-interactive .react-flow__node:hover > * {\n box-shadow: 0 1px 5px 2px rgba(0, 0, 0, 0.08);\n }\n\n.is-interactive .react-flow__handle {\n cursor: crosshair;\n }\n\n.react-flow__node {\n position: absolute;\n color: #222;\n font-family: sans-serif;\n font-size: 12px;\n text-align: center;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n pointer-events: all;\n transform-origin: 0 0;\n}\n\n.react-flow__node.selected > * {\n box-shadow: 0 0 0 2px #555;\n }\n\n.react-flow__handle {\n position: absolute;\n width: 10px;\n height: 8px;\n background: rgba(255, 255, 255, 0.4);\n}\n\n.react-flow__handle.bottom {\n top: auto;\n left: 50%;\n bottom: 0;\n transform: translate(-50%, 0);\n }\n\n.react-flow__handle.top {\n left: 50%;\n top: 0;\n transform: translate(-50%, 0);\n }\n\n.react-flow__handle.left {\n top: 50%;\n left: 0;\n transform: translate(0, -50%);\n }\n\n.react-flow__handle.right {\n right: 0;\n top: 50%;\n transform: translate(0, -50%);\n }\n\n.react-flow__nodesselection {\n z-index: 3;\n position: absolute;\n width: 100%;\n height: 100%;\n top: 0;\n left: 0;\n transform-origin: left top;\n pointer-events: none;\n}\n\n.react-flow__nodesselection-rect {\n position: absolute;\n background: rgba(0, 89, 220, 0.08);\n border: 1px dotted rgba(0, 89, 220, 0.8);\n pointer-events: all;\n }\n\n.react-flow__controls {\n box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.08);\n}\n\n.react-flow__controls-button {\n background: #fefefe;\n border-bottom: 1px solid #eee;\n display: flex;\n justify-content: center;\n align-items: center;\n width: 16px;\n height: 16px;\n cursor: pointer;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n padding: 5px;\n }\n\n.react-flow__controls-button svg {\n width: 100%;\n }\n\n.react-flow__controls-button:hover {\n background: #f4f4f4;\n }\n";
styleInject(css);
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, snapToGrid = _a.snapToGrid, snapGrid = _a.snapGrid, onlyRenderVisibleNodes = _a.onlyRenderVisibleNodes;
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, onlyRenderVisibleNodes = _a.onlyRenderVisibleNodes, isInteractive = _a.isInteractive;
var nodeTypesParsed = React.useMemo(function () { return createNodeTypes(nodeTypes); }, []);
var edgeTypesParsed = React.useMemo(function () { return createEdgeTypes(edgeTypes); }, []);
return (React__default.createElement("div", { style: style, className: "react-flow" },
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, snapToGrid: snapToGrid, snapGrid: snapGrid, onlyRenderVisibleNodes: onlyRenderVisibleNodes }),
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, onlyRenderVisibleNodes: onlyRenderVisibleNodes, isInteractive: isInteractive }),
children)));
};
ReactFlow.displayName = 'ReactFlow';
@@ -9008,7 +9017,8 @@ ReactFlow.defaultProps = {
backgroundType: GridType.Dots,
snapToGrid: false,
snapGrid: [16, 16],
onlyRenderVisibleNodes: true
onlyRenderVisibleNodes: true,
isInteractive: true,
};
var baseStyle = {

File diff suppressed because one or more lines are too long

View File

@@ -7,8 +7,9 @@ interface ConnectionLineProps {
connectionLineType?: string | null;
nodes: Node[];
transform: Transform;
isInteractive: boolean;
connectionLineStyle?: SVGAttributes<{}>;
className?: string;
}
declare const _default: ({ connectionSourceId, connectionLineStyle, connectionPositionX, connectionPositionY, connectionLineType, nodes, className, transform, }: ConnectionLineProps) => JSX.Element | null;
declare const _default: ({ connectionSourceId, connectionLineStyle, connectionPositionX, connectionPositionY, connectionLineType, nodes, className, transform, isInteractive, }: ConnectionLineProps) => JSX.Element | null;
export default _default;

View File

@@ -8,6 +8,7 @@ interface EdgeWrapperProps {
onClick: (edge: Edge) => void;
animated: boolean;
selected: boolean;
isInteractive: boolean;
}
declare const _default: (EdgeComponent: React.ComponentType<EdgeCompProps>) => React.MemoExoticComponent<({ id, source, target, type, animated, selected, onClick, ...rest }: EdgeWrapperProps) => JSX.Element>;
declare const _default: (EdgeComponent: React.ComponentType<EdgeCompProps>) => React.MemoExoticComponent<({ id, source, target, type, animated, selected, onClick, isInteractive, ...rest }: EdgeWrapperProps) => JSX.Element>;
export default _default;

View File

@@ -1,4 +1,4 @@
/// <reference types="react" />
import { NodeProps } from '../../types';
declare const _default: ({ data, style }: NodeProps) => JSX.Element;
declare const _default: ({ data, targetPosition, sourcePosition, style }: NodeProps) => JSX.Element;
export default _default;

View File

@@ -1,4 +1,4 @@
/// <reference types="react" />
import { NodeProps } from '../../types';
declare const _default: ({ data, style }: NodeProps) => JSX.Element;
declare const _default: ({ data, style, sourcePosition }: NodeProps) => JSX.Element;
export default _default;

View File

@@ -1,4 +1,4 @@
/// <reference types="react" />
import { NodeProps } from '../../types';
declare const _default: ({ data, style }: NodeProps) => JSX.Element;
declare const _default: ({ data, style, targetPosition }: NodeProps) => JSX.Element;
export default _default;

View File

@@ -1,16 +1,4 @@
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>;
import React from 'react';
import { NodeComponentProps, WrapNodeProps } from '../../types';
declare const _default: (NodeComponent: React.ComponentType<NodeComponentProps>) => React.MemoExoticComponent<({ id, type, data, transform, xPos, yPos, selected, onClick, onNodeDragStop, style, isInteractive, sourcePosition, targetPosition, }: WrapNodeProps) => JSX.Element>;
export default _default;

View File

@@ -1,3 +1,6 @@
import React from 'react';
declare const _default: React.MemoExoticComponent<() => JSX.Element>;
declare type UserSelectionProps = {
isInteractive: boolean;
};
declare const _default: React.MemoExoticComponent<({ isInteractive }: UserSelectionProps) => JSX.Element | null>;
export default _default;

View File

@@ -21,6 +21,7 @@ export interface GraphViewProps {
snapToGrid: boolean;
snapGrid: [number, number];
onlyRenderVisibleNodes: boolean;
isInteractive: boolean;
}
declare const GraphView: React.MemoExoticComponent<({ nodeTypes, edgeTypes, onMove, onLoad, onElementClick, onNodeDragStop, connectionLineType, connectionLineStyle, selectionKeyCode, onElementsRemove, deleteKeyCode, elements, showBackground, backgroundGap, backgroundColor, backgroundType, onConnect, snapToGrid, snapGrid, onlyRenderVisibleNodes }: GraphViewProps) => JSX.Element>;
declare const GraphView: React.MemoExoticComponent<({ nodeTypes, edgeTypes, onMove, onLoad, onElementClick, onNodeDragStop, connectionLineType, connectionLineStyle, selectionKeyCode, onElementsRemove, deleteKeyCode, elements, showBackground, backgroundGap, backgroundColor, backgroundType, onConnect, snapToGrid, snapGrid, onlyRenderVisibleNodes, isInteractive, }: GraphViewProps) => JSX.Element>;
export default GraphView;

View File

@@ -22,9 +22,10 @@ export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'on
snapToGrid: boolean;
snapGrid: [number, number];
onlyRenderVisibleNodes: boolean;
isInteractive: boolean;
}
declare const ReactFlow: {
({ style, onElementClick, elements, children, nodeTypes, edgeTypes, onLoad, onMove, onElementsRemove, onConnect, onNodeDragStop, connectionLineType, connectionLineStyle, deleteKeyCode, selectionKeyCode, showBackground, backgroundGap, backgroundType, backgroundColor, snapToGrid, snapGrid, onlyRenderVisibleNodes }: ReactFlowProps): JSX.Element;
({ style, onElementClick, elements, children, nodeTypes, edgeTypes, onLoad, onMove, onElementsRemove, onConnect, onNodeDragStop, connectionLineType, connectionLineStyle, deleteKeyCode, selectionKeyCode, showBackground, backgroundGap, backgroundType, backgroundColor, snapToGrid, snapGrid, onlyRenderVisibleNodes, isInteractive, }: ReactFlowProps): JSX.Element;
displayName: string;
defaultProps: {
onElementClick: () => void;
@@ -34,9 +35,9 @@ declare const ReactFlow: {
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;
input: ({ data, style, sourcePosition }: import("../../types").NodeProps) => JSX.Element;
default: ({ data, targetPosition, sourcePosition, style }: import("../../types").NodeProps) => JSX.Element;
output: ({ data, style, targetPosition }: import("../../types").NodeProps) => JSX.Element;
};
edgeTypes: {
default: React.MemoExoticComponent<({ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, style, }: import("../../types").EdgeBezierProps) => JSX.Element>;
@@ -54,6 +55,7 @@ declare const ReactFlow: {
snapToGrid: boolean;
snapGrid: number[];
onlyRenderVisibleNodes: boolean;
isInteractive: boolean;
};
};
export default ReactFlow;

View File

@@ -45,6 +45,7 @@ export declare const useStoreActions: <Result>(mapActions: (actions: import("eas
}>;
setConnectionPosition: import("easy-peasy").Action<StoreModel, import("../types").XYPosition>;
setConnectionSourceId: import("easy-peasy").Action<StoreModel, string | null>;
setInteractive: import("easy-peasy").Action<StoreModel, boolean>;
}, "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<{
@@ -65,5 +66,6 @@ export declare const useStoreState: <Result>(mapState: (state: import("easy-peas
connectionPosition: import("../types").XYPosition;
snapToGrid: boolean;
snapGrid: [number, number];
isInteractive: boolean;
onConnect: import("../types").OnConnectFunc;
}, "1">) => Result, dependencies?: any[] | undefined) => Result;

View File

@@ -49,6 +49,7 @@ export interface StoreModel {
connectionPosition: XYPosition;
snapToGrid: boolean;
snapGrid: [number, number];
isInteractive: boolean;
onConnect: OnConnectFunc;
setOnConnect: Action<StoreModel, OnConnectFunc>;
setNodes: Action<StoreModel, Node[]>;
@@ -65,6 +66,7 @@ export interface StoreModel {
setSnapGrid: Action<StoreModel, SetSnapGrid>;
setConnectionPosition: Action<StoreModel, XYPosition>;
setConnectionSourceId: Action<StoreModel, ElementId | null>;
setInteractive: Action<StoreModel, boolean>;
}
declare const store: {
getState: () => import("easy-peasy").IntermediateStateMapper<{
@@ -85,6 +87,7 @@ declare const store: {
connectionPosition: XYPosition;
snapToGrid: boolean;
snapGrid: [number, number];
isInteractive: boolean;
onConnect: OnConnectFunc;
}, "1">;
subscribe: (listener: () => void) => import("redux").Unsubscribe;
@@ -106,6 +109,7 @@ declare const store: {
connectionPosition: XYPosition;
snapToGrid: boolean;
snapGrid: [number, number];
isInteractive: boolean;
onConnect: OnConnectFunc;
}, "1">, import("redux").AnyAction>) => void;
dispatch: import("easy-peasy").Dispatch<StoreModel, import("redux").Action<any>>;
@@ -133,6 +137,7 @@ declare const store: {
setSnapGrid: Action<StoreModel, SetSnapGrid>;
setConnectionPosition: Action<StoreModel, XYPosition>;
setConnectionSourceId: Action<StoreModel, string | null>;
setInteractive: Action<StoreModel, boolean>;
}, "1">;
getListeners: () => import("easy-peasy").ListenerMapper<{
selectedNodesBbox: Rect;

21
dist/types/index.d.ts vendored
View File

@@ -43,6 +43,8 @@ export interface Node {
__rg?: any;
data?: any;
style?: CSSProperties;
targetPosition?: Position;
sourcePosition?: Position;
}
export interface Edge {
id: ElementId;
@@ -68,6 +70,8 @@ export interface NodeProps {
type: string;
data: any;
selected: boolean;
targetPosition?: Position;
sourcePosition?: Position;
style?: CSSProperties;
}
export interface NodeComponentProps {
@@ -78,10 +82,27 @@ export interface NodeComponentProps {
transform?: Transform;
xPos?: number;
yPos?: number;
targetPosition?: Position;
sourcePosition?: Position;
onClick?: (node: Node) => void | undefined;
onNodeDragStop?: () => any;
style?: CSSProperties;
}
export interface WrapNodeProps {
id: ElementId;
type: string;
data: any;
selected: boolean;
transform: Transform;
xPos: number;
yPos: number;
isInteractive: boolean;
onClick: (node: Node) => void | undefined;
onNodeDragStop: (node: Node) => void;
style?: CSSProperties;
sourcePosition?: Position;
targetPosition?: Position;
}
export declare type FitViewParams = {
padding: number;
};

View File

@@ -0,0 +1,32 @@
import React from 'react';
import { Handle } from 'react-flow';
export default ({ data, styles }) => {
return (
<div
style={{ background: '#eee', border: '1px solid #ddd', padding: 10, borderRadius: 5, ...styles }}
>
<Handle
type="target"
position="left"
style={{ background: '#fff' }}
onConnect={params => console.log('handle onConnect', params)}
/>
<div>Custom Color Picker Node: <strong>{data.color}</strong></div>
<input type="color" onChange={data.onChange} defaultValue={data.color}/>
<Handle
type="source"
position="right"
id="a"
style={{ top: 10, background: '#fff' }}
/>
<Handle
type="source"
position="right"
id="b"
style={{ bottom: 10, top: 'auto', background: '#fff' }}
/>
</div>
);
};

View File

@@ -0,0 +1,86 @@
import React, { useState, useEffect } from 'react';
import Graph, { isEdge, removeElements, addEdge, MiniMap, Controls } from 'react-flow';
import ColorSelectorNode from './ColorSelectorNode';
const onNodeDragStop = node => console.log('drag stop', node);
const onElementClick = element => console.log('click', element);
const onLoad = (graph) => {
console.log('graph loaded:', graph);
};
const initBgColor = '#8888e8';
const CustomNodeGraph = () => {
const [elements, setElements] = useState([]);
const [bgColor, setBgColor] = useState(initBgColor);
useEffect(() => {
const onChange = (evt) => {
setElements(els => els.map(e => {
if (isEdge(e) || e.id !== '2') {
return e;
}
const color = evt.target.value;
setBgColor(color);
return {
...e,
data: {
...e.data,
color
}
};
}));
};
setElements([
{ id: '1', type: 'input', data: { label: 'An input node' }, position: { x: 0, y: 50 }, sourcePosition: 'right' },
{ id: '2', type: 'selectorNode', data: { onChange: onChange, color: initBgColor }, position: { x: 250, y: 50 } },
{ id: '3', type: 'output', data: { label: 'Output A' }, position: { x: 550, y: 25 }, targetPosition: 'left' },
{ id: '4', type: 'output', data: { label: 'Output B' }, position: { x: 550, y: 100 }, targetPosition: 'left' },
{ id: 'e1-2', source: '1', target: '2', animated: true, style: { stroke: '#fff' } },
{ id: 'e2a-3', source: '2__a', target: '3', animated: true, style: { stroke: '#fff' } },
{ id: 'e2b-4', source: '2__b', target: '4', animated: true, style: { stroke: '#fff' } },
])
}, []);
const onElementsRemove = (elementsToRemove) => setElements(els => removeElements(elementsToRemove, els));
const onConnect = (params) => setElements(els => addEdge(params, els));
return (
<Graph
elements={elements}
onElementClick={onElementClick}
onElementsRemove={onElementsRemove}
onConnect={onConnect}
onNodeDragStop={onNodeDragStop}
style={{ width: '100%', height: '100%', background: bgColor }}
onLoad={onLoad}
nodeTypes={{
selectorNode: ColorSelectorNode,
}}
connectionLineStyle={{ stroke: '#ddd', strokeWidth: 2 }}
connectionLineType="bezier"
backgroundColor="#888"
backgroundGap={16}
snapToGrid={true}
snapGrid={[16, 16]}
>
<MiniMap
nodeColor={n => {
if (n.type === 'input') return 'blue';
if (n.type === 'selectorNode') return bgColor;
if (n.type === 'output') return 'green';
}}
/>
<Controls />
</Graph>
);
}
export default CustomNodeGraph;

View File

@@ -1,19 +0,0 @@
import React from 'react';
import { Handle } from 'react-flow';
export default({ data, styles }) => (
<div
style={{ background: '#FFCC00', padding: 10, borderRadius: 2, ...styles }}
>
<Handle type="target" position="left" style={{ background: '#999' }} />
<div>{data.input}</div>
<input onChange={(e) => data.onChange(e.target.value, data)} />
<Handle
type="source"
position="right"
style={{ background: '#999' }}
isValidConnection={connection => +connection.target % 2 === 0}
/>
</div>
);

View File

@@ -1,36 +0,0 @@
import React from 'react';
import { Handle } from 'react-flow';
export default ({ data, styles }) => {
return (
<div
style={{ background: '#FFCC00', padding: 10, borderRadius: 2, ...styles }}
>
<Handle
type="target"
position="top"
id="a"
style={{ left: 10, background: '#999' }}
onConnect={params => console.log('handle onConnect', params)}
/>
<Handle
type="target"
position="top"
id="b"
style={{ left: 30, background: '#999' }}
/>
<div>I am <strong>special</strong>!<br />{data.label}</div>
<select onChange={(e) => data.onChange(e.target.value, data)}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<Handle
type="source"
position="bottom"
style={{ left: 10, background: '#999' }}
/>
</div>
);
};

View File

@@ -1,119 +0,0 @@
import React, { useState, useEffect } from 'react';
import Graph, { isEdge, removeElements, addEdge, MiniMap, Controls } from 'react-flow';
import SpecialNode from './SpecialNode';
import InputNode from './InputNode';
const onNodeDragStop = node => console.log('drag stop', node);
const onElementClick = element => console.log('click', element);
const onLoad = (graph) => console.log('graph loaded:', graph);
const CustomNodesGraph = () => {
const [elements, setElements] = useState([]);
useEffect(() => {
const onChange = (option, d) => {
setElements(els => els.map(e => {
if (isEdge(e) || e.id !== '6') {
return e;
}
return {
...e,
data: {
...e.data,
label: `Option ${option} selected.`
}
};
}));
};
const onChangeInput = (input, d) => {
setElements(els => els.map(e => {
if (isEdge(e) || e.id !== '8') {
return e;
}
return {
...e,
data: {
...e.data,
input: input || 'write something'
}
};
}));
};
setElements([
{ id: '1', type: 'input', data: { label: '1 Tests' }, position: { x: 250, y: 5 } },
{ id: '2', data: { label: '2 This is a node This is a node This is a node This is a node' }, position: { x: 100, y: 100 } },
{ id: '3', data: { label: '3 I bring my own style' }, position: { x: 100, y: 200 }, style: { background: '#eee', color: '#222', border: '1px solid #bbb' } },
{ id: '4', type: 'output', data: { label: '4 nody nodes' }, position: { x: 50, y: 300 } },
{ id: '5', type: 'default', data: { label: '5 Another node'}, position: { x: 400, y: 300 } },
{ id: '6', type: 'special', data: { onChange: onChange, label: '6 no option selected' }, position: { x: 425, y: 375 } },
{ id: '7', type: 'output', data: { label: '7 output' }, position: { x: 250, y: 500 } },
{ id: '8', type: 'text', data: { onChange: onChangeInput, input: 'write something' }, position: { x: 350, y: 100 } },
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e1-8', source: '1', target: '8', animated: true },
{ id: 'e2-3', source: '2', target: '3', animated: true },
{ id: 'e3-4', source: '3', target: '4', animated: true },
{ id: 'e3-5', source: '3', target: '5', animated: true },
{ id: 'e5-6b', source: '5', target: '6__b', animated: true },
{ id: 'e5-6a', source: '5', target: '6__a', animated: true },
{ id: 'e6-7', source: '6', target: '7', animated: true },
])
}, []);
const addRandomNode = () => {
setElements(els => els.concat({
id: (els.length + 1).toString(),
data: { label: 'Added node' },
position: { x: Math.random() * window.innerWidth, y: Math.random() * window.innerHeight }
}));
};
const onElementsRemove = (elementsToRemove) => setElements(els => removeElements(elementsToRemove, els));
const onConnect = (params) => setElements(els => addEdge(params, els));
return (
<Graph
elements={elements}
onElementClick={onElementClick}
onElementsRemove={onElementsRemove}
onConnect={onConnect}
onNodeDragStop={onNodeDragStop}
style={{ width: '100%', height: '100%' }}
onLoad={onLoad}
nodeTypes={{
special: SpecialNode,
text: InputNode
}}
connectionLineStyle={{ stroke: '#ddd', strokeWidth: 2 }}
connectionLineType="bezier"
backgroundColor="#888"
backgroundGap={16}
snapToGrid={true}
snapGrid={[16, 16]}
>
<MiniMap
nodeColor={n => {
if (n.type === 'input') return 'blue';
if (n.type === 'output') return 'green';
if (n.type === 'default') return 'red';
return '#FFCC00';
}}
/>
<Controls />
<button
type="button"
onClick={addRandomNode}
style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}
>
add node
</button>
</Graph>
);
}
export default CustomNodesGraph;

View File

@@ -0,0 +1,46 @@
import React, { useState } from 'react';
import Graph, { MiniMap, Controls } from 'react-flow';
const initialElements = [
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } },
{ id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 } },
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 } },
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 } },
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e1-3', source: '1', target: '3' },
];
const EmptyGraph = () => {
const [isInteractive, setIsInteractive] = useState(false);
const onToggleInteractive = (evt) => {
setIsInteractive(evt.target.checked);
};
return (
<Graph
elements={initialElements}
style={{ width: '100%', height: '100%' }}
backgroundType="lines"
isInteractive={isInteractive}
>
<MiniMap />
<Controls />
<div
style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}
>
<label>
interactive
<input
type="checkbox"
checked={isInteractive}
onChange={onToggleInteractive}
className="react-flow__interactive"
/>
</label>
</div>
</Graph>
);
}
export default EmptyGraph;

77
example/src/Rich/index.js Normal file
View File

@@ -0,0 +1,77 @@
import React, { useState, useEffect } from 'react';
import Graph, { removeElements, addEdge, MiniMap, Controls } from 'react-flow';
const onNodeDragStop = node => console.log('drag stop', node);
const onElementClick = element => console.log('click', element);
const onLoad = (graph) => console.log('graph loaded:', graph);
const initialElements = [
{ id: '1', type: 'input', data: { label: 'Input Node 1' }, position: { x: 250, y: 5 } },
{ id: '2', data: { label: 'Node 2' }, position: { x: 150, y: 75 } },
{
id: '3', data: { label: 'Node 3 with custom style' }, position: { x: 250, y: 150 },
style: { background: '#eee', color: '#222', border: '1px solid #bbb' },
sourcePosition: 'right'
},
{ id: '4', data: { label: 'Node 4' }, position: { x: 500, y: 200 }, targetPosition: 'left' },
{ id: '5', type: 'output', data: { label: 'Output Node 5'}, position: { x: 300, y: 300 } },
{ id: '6', type: 'output', data: { label: 'Output Node 6' }, position: { x: 600, y: 400 } },
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e2-3', source: '2', target: '3', animated: true },
{ id: 'e3-4', source: '3', target: '4', animated: true },
{ id: 'e3-5', source: '4', target: '5', animated: true, type: 'step' },
{ id: 'e5-6b', source: '4', target: '6', type: 'step' },
]
const RichGraph = () => {
const [elements, setElements] = useState(initialElements);
const addRandomNode = () => {
setElements(els => els.concat({
id: (els.length + 1).toString(),
data: { label: 'Added node' },
position: { x: Math.random() * window.innerWidth, y: Math.random() * window.innerHeight }
}));
};
const onElementsRemove = (elementsToRemove) => setElements(els => removeElements(elementsToRemove, els));
const onConnect = (params) => setElements(els => addEdge(params, els));
return (
<Graph
elements={elements}
onElementClick={onElementClick}
onElementsRemove={onElementsRemove}
onConnect={onConnect}
onNodeDragStop={onNodeDragStop}
style={{ width: '100%', height: '100%' }}
onLoad={onLoad}
connectionLineStyle={{ stroke: '#ddd', strokeWidth: 2 }}
connectionLineType="bezier"
backgroundColor="#888"
backgroundGap={16}
snapToGrid={true}
snapGrid={[16, 16]}
>
<MiniMap
nodeColor={n => {
if (n.type === 'input') return 'blue';
if (n.type === 'output') return 'green';
if (n.type === 'default') return 'red';
return '#FFCC00';
}}
/>
<Controls />
<button
type="button"
onClick={addRandomNode}
style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}
>
add node
</button>
</Graph>
);
}
export default RichGraph;

View File

@@ -1,3 +1,7 @@
body {
font-family: sans-serif;
}
html, body {
margin: 0;
}

View File

@@ -2,9 +2,11 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import CustomNodes from './CustomNodes';
import CustomNode from './CustomNode';
import Rich from './Rich';
import Basic from './Basic';
import Empty from './Empty';
import Inactive from './Inactive';
import './index.css';
@@ -17,8 +19,14 @@ ReactDOM.render((
<Route path="/empty">
<Empty />
</Route>
<Route path="/inactive">
<Inactive />
</Route>
<Route path="/custom-node">
<CustomNode />
</Route>
<Route path="/">
<CustomNodes />
<Rich />
</Route>
</Switch>
</Router>

View File

@@ -10,6 +10,7 @@ interface ConnectionLineProps {
connectionLineType?: string | null;
nodes: Node[];
transform: Transform;
isInteractive: boolean;
connectionLineStyle?: SVGAttributes<{}>;
className?: string;
}
@@ -23,6 +24,7 @@ export default ({
nodes = [],
className,
transform,
isInteractive,
}: ConnectionLineProps) => {
const [sourceNode, setSourceNode] = useState<Node | null>(null);
const hasHandleId = connectionSourceId.includes('__');
@@ -35,23 +37,17 @@ export default ({
setSourceNode(nextSourceNode);
}, []);
if (!sourceNode) {
if (!sourceNode || !isInteractive) {
return null;
}
const edgeClasses: string = cx('react-flow__edge', 'connection', className);
const sourceHandle = handleId
? sourceNode.__rg.handleBounds.source.find(
(d: HandleElement) => d.id === handleId
)
? sourceNode.__rg.handleBounds.source.find((d: HandleElement) => d.id === handleId)
: sourceNode.__rg.handleBounds.source[0];
const sourceHandleX = sourceHandle
? sourceHandle.x + sourceHandle.width / 2
: sourceNode.__rg.width / 2;
const sourceHandleY = sourceHandle
? sourceHandle.y + sourceHandle.height / 2
: sourceNode.__rg.height;
const 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 sourceY = sourceNode.__rg.position.y + sourceHandleY;

View File

@@ -13,23 +13,15 @@ interface EdgeWrapperProps {
onClick: (edge: Edge) => void;
animated: boolean;
selected: boolean;
isInteractive: boolean;
}
export default (EdgeComponent: ComponentType<EdgeCompProps>) => {
const EdgeWrapper = memo(
({
id,
source,
target,
type,
animated,
selected,
onClick,
...rest
}: EdgeWrapperProps) => {
({ id, source, target, type, animated, selected, onClick, isInteractive, ...rest }: EdgeWrapperProps) => {
const edgeClasses = cx('react-flow__edge', { selected, animated });
const onEdgeClick = (evt: MouseEvent): void => {
if (isInputDOMNode(evt)) {
if (isInputDOMNode(evt) || !isInteractive) {
return;
}

View File

@@ -10,10 +10,10 @@ const nodeStyles: CSSProperties = {
width: 150,
};
export default ({ data, style }: NodeProps) => (
export default ({ data, targetPosition = Position.Top, sourcePosition = Position.Bottom, style }: NodeProps) => (
<div style={{ ...nodeStyles, ...style }}>
<Handle type="target" position={Position.Top} />
<Handle type="target" position={targetPosition} />
{data.label}
<Handle type="source" position={Position.Bottom} />
<Handle type="source" position={sourcePosition} />
</div>
);

View File

@@ -10,9 +10,9 @@ const nodeStyles: CSSProperties = {
width: 150,
};
export default ({ data, style }: NodeProps) => (
export default ({ data, style, sourcePosition = Position.Bottom }: NodeProps) => (
<div style={{ ...nodeStyles, ...style }}>
{data.label}
<Handle type="source" position={Position.Bottom} />
<Handle type="source" position={sourcePosition} />
</div>
);

View File

@@ -10,9 +10,9 @@ const nodeStyles: CSSProperties = {
width: 150,
};
export default ({ data, style }: NodeProps) => (
export default ({ data, style, targetPosition = Position.Top }: NodeProps) => (
<div style={{ ...nodeStyles, ...style }}>
<Handle type="target" position={Position.Top} />
<Handle type="target" position={targetPosition} />
{data.label}
</div>
);

View File

@@ -1,11 +1,4 @@
import React, {
useEffect,
useRef,
useState,
memo,
ComponentType,
CSSProperties,
} from 'react';
import React, { useEffect, useRef, useState, memo, ComponentType } from 'react';
import { DraggableCore, DraggableEvent } from 'react-draggable';
import cx from 'classnames';
import { ResizeObserver } from 'resize-observer';
@@ -21,21 +14,9 @@ import {
Transform,
ElementId,
NodeComponentProps,
WrapNodeProps,
} 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 target = evt.target as HTMLElement;
@@ -65,17 +46,13 @@ const getHandleBounds = (
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 handlePosition = (handle.getAttribute('data-handlepos') as unknown) as Position;
const nodeIdSplitted = nodeIdAttr ? nodeIdAttr.split('__') : null;
let handleId = null;
if (nodeIdSplitted) {
handleId = (nodeIdSplitted.length
? nodeIdSplitted[1]
: nodeIdSplitted) as string;
handleId = (nodeIdSplitted.length ? nodeIdSplitted[1] : nodeIdSplitted) as string;
}
return {
@@ -171,6 +148,9 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
onClick,
onNodeDragStop,
style,
isInteractive,
sourcePosition,
targetPosition,
}: WrapNodeProps) => {
const nodeElement = useRef<HTMLDivElement>(null);
const [offset, setOffset] = useState({ x: 0, y: 0 });
@@ -192,18 +172,8 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
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]
),
source: getHandleBounds('.source', nodeElement.current, bounds, storeState.transform[2]),
target: getHandleBounds('.target', nodeElement.current, bounds, storeState.transform[2]),
};
store.dispatch.updateNodeData({ id, ...dimensions, handleBounds });
};
@@ -232,33 +202,11 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
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
)
}
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]}
disabled={!isInteractive}
>
<div className={nodeClasses} ref={nodeElement} style={nodeStyle}>
<Provider value={id}>
@@ -268,6 +216,8 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
type={type}
style={style}
selected={selected}
sourcePosition={sourcePosition}
targetPosition={targetPosition}
/>
</Provider>
</div>

View File

@@ -3,6 +3,10 @@ import React, { useEffect, useRef, useState, memo } from 'react';
import { useStoreActions } from '../../store/hooks';
import { SelectionRect } from '../../types';
type UserSelectionProps = {
isInteractive: boolean;
};
const initialRect: SelectionRect = {
startX: 0,
startY: 0,
@@ -27,13 +31,17 @@ function getMousePosition(evt: MouseEvent) {
};
}
export default memo(() => {
export default memo(({ isInteractive }: UserSelectionProps) => {
const selectionPane = useRef<HTMLDivElement>(null);
const [rect, setRect] = useState(initialRect);
const setSelection = useStoreActions(a => a.setSelection);
const updateSelection = useStoreActions(a => a.updateSelection);
const setNodesSelection = useStoreActions(a => a.setNodesSelection);
if (!isInteractive) {
return null;
}
useEffect(() => {
function onMouseDown(evt: MouseEvent): void {
const mousePos = getMousePosition(evt);
@@ -70,12 +78,8 @@ export default memo(() => {
...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,
width: negativeX ? currentRect.startX - mousePos.x : mousePos.x - currentRect.startX,
height: negativeY ? currentRect.startY - mousePos.y : mousePos.y - currentRect.startY,
};
updateSelection(nextRect);

View File

@@ -113,7 +113,13 @@ function getEdgePositions(
};
}
function renderEdge(edge: Edge, props: EdgeRendererProps, nodes: Node[], selectedElements: Elements) {
function renderEdge(
edge: Edge,
props: EdgeRendererProps,
nodes: Node[],
selectedElements: Elements,
isInteractive: boolean
) {
const [sourceId, sourceHandleId] = edge.source.split('__');
const [targetId, targetHandleId] = edge.target.split('__');
@@ -128,7 +134,7 @@ function renderEdge(edge: Edge, props: EdgeRendererProps, nodes: Node[], selecte
throw new Error(`couldn't create edge for target id: ${targetId}`);
}
if (!sourceNode.__rg.width || !sourceNode.__rg.height) {
if (!sourceNode.__rg.width || !sourceNode.__rg.height) {
return null;
}
@@ -171,6 +177,7 @@ function renderEdge(edge: Edge, props: EdgeRendererProps, nodes: Node[], selecte
targetY={targetY}
sourcePosition={sourcePosition}
targetPosition={targetPosition}
isInteractive={isInteractive}
/>
);
}
@@ -183,7 +190,16 @@ const EdgeRenderer = memo((props: EdgeRendererProps) => {
connectionSourceId,
connectionPosition: { x, y },
selectedElements,
} = useStoreState(s => s);
isInteractive,
} = useStoreState(s => ({
transform: s.transform,
edges: s.edges,
nodes: s.nodes,
connectionSourceId: s.connectionSourceId,
connectionPosition: s.connectionPosition,
selectedElements: s.selectedElements,
isInteractive: s.isInteractive,
}));
const { width, height, connectionLineStyle, connectionLineType } = props;
@@ -197,7 +213,7 @@ const EdgeRenderer = memo((props: EdgeRendererProps) => {
return (
<svg width={width} height={height} className="react-flow__edges">
<g transform={transformStyle}>
{edges.map((e: Edge) => renderEdge(e, props, nodes, selectedElements))}
{edges.map((e: Edge) => renderEdge(e, props, nodes, selectedElements, isInteractive))}
{connectionSourceId && (
<ConnectionLine
nodes={nodes}
@@ -207,6 +223,7 @@ const EdgeRenderer = memo((props: EdgeRendererProps) => {
transform={transform}
connectionLineStyle={connectionLineStyle}
connectionLineType={connectionLineType}
isInteractive={isInteractive}
/>
)}
</g>

View File

@@ -1,4 +1,5 @@
import React, { useEffect, useRef, memo, SVGAttributes } from 'react';
import classnames from 'classnames';
import { useStoreState, useStoreActions } from '../../store/hooks';
import NodeRenderer from '../NodeRenderer';
@@ -12,13 +13,7 @@ import useGlobalKeyHandler from '../../hooks/useGlobalKeyHandler';
import useElementUpdater from '../../hooks/useElementUpdater';
import { getDimensions } from '../../utils';
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 {
elements: Elements;
@@ -40,7 +35,8 @@ export interface GraphViewProps {
backgroundType: GridType;
snapToGrid: boolean;
snapGrid: [number, number];
onlyRenderVisibleNodes: boolean
onlyRenderVisibleNodes: boolean;
isInteractive: boolean;
}
const GraphView = memo(
@@ -64,7 +60,8 @@ const GraphView = memo(
onConnect,
snapToGrid,
snapGrid,
onlyRenderVisibleNodes
onlyRenderVisibleNodes,
isInteractive,
}: GraphViewProps) => {
const zoomPane = useRef<HTMLDivElement>(null);
const rendererNode = useRef<HTMLDivElement>(null);
@@ -77,13 +74,12 @@ const GraphView = memo(
nodesSelectionActive: s.nodesSelectionActive,
}));
const updateSize = useStoreActions(actions => actions.updateSize);
const setNodesSelection = useStoreActions(
actions => actions.setNodesSelection
);
const setNodesSelection = useStoreActions(actions => actions.setNodesSelection);
const setOnConnect = useStoreActions(a => a.setOnConnect);
const setSnapGrid = useStoreActions(actions => actions.setSnapGrid);
const setInteractive = useStoreActions(actions => actions.setInteractive);
const selectionKeyPressed = useKeyPress(selectionKeyCode);
const rendererClasses = classnames('react-flow__renderer', { 'is-interactive': isInteractive });
const onZoomPaneClick = () => setNodesSelection({ isActive: false });
@@ -122,17 +118,17 @@ const GraphView = memo(
setSnapGrid({ snapToGrid, snapGrid });
}, [snapToGrid]);
useEffect(() => {
setInteractive(isInteractive);
}, [isInteractive]);
useGlobalKeyHandler({ onElementsRemove, deleteKeyCode });
useElementUpdater(elements);
return (
<div className="react-flow__renderer" ref={rendererNode}>
<div className={rendererClasses} ref={rendererNode}>
{showBackground && (
<BackgroundGrid
gap={backgroundGap}
color={backgroundColor}
backgroundType={backgroundType}
/>
<BackgroundGrid gap={backgroundGap} color={backgroundColor} backgroundType={backgroundType} />
)}
<NodeRenderer
nodeTypes={nodeTypes}
@@ -148,13 +144,9 @@ const GraphView = memo(
connectionLineType={connectionLineType}
connectionLineStyle={connectionLineStyle}
/>
{selectionKeyPressed && <UserSelection />}
{selectionKeyPressed && <UserSelection isInteractive={isInteractive} />}
{state.nodesSelectionActive && <NodesSelection />}
<div
className="react-flow__zoompane"
onClick={onZoomPaneClick}
ref={zoomPane}
/>
<div className="react-flow__zoompane" onClick={onZoomPaneClick} ref={zoomPane} />
</div>
);
}

View File

@@ -2,7 +2,7 @@ import React, { memo, ComponentType } from 'react';
import { useStoreState } from '../../store/hooks';
import { getNodesInside } from '../../utils/graph';
import { Node, Transform, NodeTypesType, NodeComponentProps, Elements } from '../../types';
import { Node, Transform, NodeTypesType, WrapNodeProps, Elements } from '../../types';
interface NodeRendererProps {
nodeTypes: NodeTypesType;
@@ -11,9 +11,15 @@ interface NodeRendererProps {
onlyRenderVisibleNodes?: boolean;
}
function renderNode(node: Node, props: NodeRendererProps, transform: Transform, selectedElements: Elements) {
function renderNode(
node: Node,
props: NodeRendererProps,
transform: Transform,
selectedElements: Elements,
isInteractive: boolean
) {
const nodeType = node.type || 'default';
const NodeComponent = (props.nodeTypes[nodeType] || props.nodeTypes.default) as ComponentType<NodeComponentProps>;
const NodeComponent = (props.nodeTypes[nodeType] || props.nodeTypes.default) as ComponentType<WrapNodeProps>;
if (!props.nodeTypes[nodeType]) {
console.warn(`No node type found for type "${nodeType}". Using fallback type "default".`);
}
@@ -33,23 +39,35 @@ function renderNode(node: Node, props: NodeRendererProps, transform: Transform,
transform={transform}
selected={isSelected}
style={node.style}
isInteractive={isInteractive}
sourcePosition={node.sourcePosition}
targetPosition={node.targetPosition}
/>
);
}
const NodeRenderer = memo(({ onlyRenderVisibleNodes = true, ...props }: NodeRendererProps) => {
const { nodes, transform, selectedElements, width, height } = useStoreState(s => s);
const { nodes, transform, selectedElements, width, height, isInteractive } = useStoreState(s => ({
nodes: s.nodes,
transform: s.transform,
selectedElements: s.selectedElements,
width: s.width,
height: s.height,
isInteractive: s.isInteractive,
}));
const [tx, ty, tScale] = transform;
const transformStyle = {
transform: `translate(${tx}px,${ty}px) scale(${tScale})`,
};
const renderNodes = onlyRenderVisibleNodes ? getNodesInside(nodes, { x: 0, y: 0, width, height }, transform, true) : nodes;
const renderNodes = onlyRenderVisibleNodes
? getNodesInside(nodes, { x: 0, y: 0, width, height }, transform, true)
: nodes;
return (
<div className="react-flow__nodes" style={transformStyle}>
{renderNodes.map(node => renderNode(node, props, transform, selectedElements))}
{renderNodes.map(node => renderNode(node, props, transform, selectedElements, isInteractive))}
</div>
);
});

View File

@@ -18,18 +18,11 @@ import StraightEdge from '../../components/Edges/StraightEdge';
import StepEdge from '../../components/Edges/StepEdge';
import { createEdgeTypes } from '../EdgeRenderer/utils';
import store from '../../store';
import {
Elements,
NodeTypesType,
EdgeTypesType,
GridType,
OnLoadFunc,
} from '../../types';
import { Elements, NodeTypesType, EdgeTypesType, GridType, OnLoadFunc } from '../../types';
import '../../style.css';
export interface ReactFlowProps
extends Omit<HTMLAttributes<HTMLDivElement>, 'onLoad'> {
export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onLoad'> {
elements: Elements;
onElementClick: () => void;
onElementsRemove: (elements: Elements) => void;
@@ -49,7 +42,8 @@ export interface ReactFlowProps
backgroundType: GridType;
snapToGrid: boolean;
snapGrid: [number, number];
onlyRenderVisibleNodes: boolean
onlyRenderVisibleNodes: boolean;
isInteractive: boolean;
}
const ReactFlow = ({
@@ -74,7 +68,8 @@ const ReactFlow = ({
backgroundColor,
snapToGrid,
snapGrid,
onlyRenderVisibleNodes
onlyRenderVisibleNodes,
isInteractive,
}: ReactFlowProps) => {
const nodeTypesParsed = useMemo(() => createNodeTypes(nodeTypes), []);
const edgeTypesParsed = useMemo(() => createEdgeTypes(edgeTypes), []);
@@ -103,6 +98,7 @@ const ReactFlow = ({
snapToGrid={snapToGrid}
snapGrid={snapGrid}
onlyRenderVisibleNodes={onlyRenderVisibleNodes}
isInteractive={isInteractive}
/>
{children}
</StoreProvider>
@@ -139,7 +135,8 @@ ReactFlow.defaultProps = {
backgroundType: GridType.Dots,
snapToGrid: false,
snapGrid: [16, 16],
onlyRenderVisibleNodes: true
onlyRenderVisibleNodes: true,
isInteractive: true,
};
export default ReactFlow;

View File

@@ -9,11 +9,7 @@ const d3ZoomInstance = d3Zoom
.scaleExtent([0.5, 2])
.filter(() => !event.button);
export default (
zoomPane: MutableRefObject<Element | null>,
onMove: () => void,
shiftPressed: boolean
): void => {
export default (zoomPane: MutableRefObject<Element | null>, onMove: () => void, shiftPressed: boolean): void => {
const state = useStoreState(s => ({
transform: s.transform,
d3Selection: s.d3Selection,
@@ -35,10 +31,7 @@ export default (
d3ZoomInstance.on('zoom', null);
} else {
d3ZoomInstance.on('zoom', () => {
if (
event.sourceEvent &&
event.sourceEvent.target !== zoomPane.current
) {
if (event.sourceEvent && event.sourceEvent.target !== zoomPane.current) {
return;
}

View File

@@ -2,11 +2,7 @@ import { createStore, Action, action } from 'easy-peasy';
import isEqual from 'fast-deep-equal';
import { Selection as D3Selection, ZoomBehavior } from 'd3';
import {
getNodesInside,
getConnectedEdges,
getRectOfNodes,
} from '../utils/graph';
import { getNodesInside, getConnectedEdges, getRectOfNodes } from '../utils/graph';
import {
ElementId,
Elements,
@@ -80,6 +76,8 @@ export interface StoreModel {
snapToGrid: boolean;
snapGrid: [number, number];
isInteractive: boolean;
onConnect: OnConnectFunc;
setOnConnect: Action<StoreModel, OnConnectFunc>;
@@ -111,6 +109,8 @@ export interface StoreModel {
setConnectionPosition: Action<StoreModel, XYPosition>;
setConnectionSourceId: Action<StoreModel, ElementId | null>;
setInteractive: Action<StoreModel, boolean>;
}
const storeModel: StoreModel = {
@@ -136,6 +136,8 @@ const storeModel: StoreModel = {
snapGrid: [16, 16],
snapToGrid: true,
isInteractive: true,
onConnect: () => {},
setOnConnect: action((state, onConnect) => {
@@ -195,11 +197,7 @@ const storeModel: StoreModel = {
return;
}
const selectedNodes = getNodesInside(
state.nodes,
selection,
state.transform
);
const selectedNodes = getNodesInside(state.nodes, selection, state.transform);
if (!selectedNodes.length) {
state.nodesSelectionActive = false;
@@ -218,35 +216,21 @@ const storeModel: StoreModel = {
setSelectedElements: action((state, elements) => {
const selectedElementsArr = Array.isArray(elements) ? elements : [elements];
const selectedElementsUpdated = !isEqual(
selectedElementsArr,
state.selectedElements
);
const selectedElements = selectedElementsUpdated
? selectedElementsArr
: state.selectedElements;
const selectedElementsUpdated = !isEqual(selectedElementsArr, state.selectedElements);
const selectedElements = selectedElementsUpdated ? selectedElementsArr : state.selectedElements;
state.selectedElements = selectedElements;
}),
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 nextSelectedElements = [...selectedNodes, ...selectedEdges];
const selectedElementsUpdated = !isEqual(
nextSelectedElements,
state.selectedElements
);
const selectedElementsUpdated = !isEqual(nextSelectedElements, state.selectedElements);
state.selection = selection;
state.selectedElements = selectedElementsUpdated
? nextSelectedElements
: state.selectedElements;
state.selectedElements = selectedElementsUpdated ? nextSelectedElements : state.selectedElements;
}),
updateTransform: action((state, transform) => {
@@ -276,6 +260,10 @@ const storeModel: StoreModel = {
state.snapToGrid = snapToGrid;
state.snapGrid = snapGrid;
}),
setInteractive: action((state, isInteractive) => {
state.isInteractive = isInteractive;
}),
};
const store = createStore(storeModel);

View File

@@ -67,7 +67,9 @@
}
@keyframes dashdraw {
from {stroke-dashoffset: 10}
from {
stroke-dashoffset: 10;
}
}
.react-flow__nodes {
@@ -79,21 +81,30 @@
transform-origin: 0 0;
}
.is-interactive {
.react-flow__node {
cursor: grab;
&:hover > * {
box-shadow: 0 1px 5px 2px rgba(0, 0, 0, 0.08);
}
}
.react-flow__handle {
cursor: crosshair;
}
}
.react-flow__node {
position: absolute;
color: #222;
font-family: sans-serif;
font-size: 12px;
text-align: center;
cursor: grab;
user-select: none;
pointer-events: all;
transform-origin: 0 0;
&:hover > * {
box-shadow: 0 1px 5px 2px rgba(0, 0, 0, 0.08);
}
&.selected > * {
box-shadow: 0 0 0 2px #555;
}
@@ -104,7 +115,6 @@
width: 10px;
height: 8px;
background: rgba(255, 255, 255, 0.4);
cursor: crosshair;
&.bottom {
top: auto;
@@ -123,7 +133,6 @@
top: 50%;
left: 0;
transform: translate(0, -50%);
}
&.right {

View File

@@ -54,6 +54,8 @@ export interface Node {
__rg?: any;
data?: any;
style?: CSSProperties;
targetPosition?: Position;
sourcePosition?: Position;
}
export interface Edge {
@@ -83,6 +85,8 @@ export interface NodeProps {
type: string;
data: any;
selected: boolean;
targetPosition?: Position;
sourcePosition?: Position;
style?: CSSProperties;
}
@@ -94,11 +98,29 @@ export interface NodeComponentProps {
transform?: Transform;
xPos?: number;
yPos?: number;
targetPosition?: Position;
sourcePosition?: Position;
onClick?: (node: Node) => void | undefined;
onNodeDragStop?: () => any;
style?: CSSProperties;
}
export interface WrapNodeProps {
id: ElementId;
type: string;
data: any;
selected: boolean;
transform: Transform;
xPos: number;
yPos: number;
isInteractive: boolean;
onClick: (node: Node) => void | undefined;
onNodeDragStop: (node: Node) => void;
style?: CSSProperties;
sourcePosition?: Position;
targetPosition?: Position;
}
export type FitViewParams = {
padding: number;
};

View File

@@ -1,13 +1,9 @@
import { DraggableEvent } from 'react-draggable';
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;
return (
e && target && ['INPUT', 'SELECT', 'TEXTAREA'].includes(target.nodeName)
);
return e && target && ['INPUT', 'SELECT', 'TEXTAREA', 'BUTTON'].includes(target.nodeName);
};
export const getDimensions = (node: HTMLDivElement) => ({