Merge pull request #501 from wbkd/develop

Develop
This commit is contained in:
Moritz
2020-09-10 18:33:54 +02:00
committed by GitHub
11 changed files with 225 additions and 386 deletions
+30 -26
View File
@@ -31,10 +31,10 @@ React Flow is a library for building node-based graphs. You can easily implement
## Key Features
* **Easy to use:** Seamless zooming & panning behaviour and single and multi-selections of elements
* **Customizable:** Different [node](#node-types--custom-nodes) and [edge types](#edge-types--custom-edges) and support for custom nodes with multiple handles and edges
* **Customizable:** Different [node](#node-types--custom-nodes) and [edge types](#edge-types--custom-edges) and support for custom nodes with multiple handles and custom edges
* **Fast rendering:** Only nodes that have changed are re-rendered and only those that are in the viewport are displayed
* **Utils:** Snap-to-grid, background styles and graph [helper functions](#helper-functions)
* **Components:** [Background, Minimap and graph controls](#components)
* **Utils:** Snap-to-grid and graph [helper functions](#helper-functions)
* **Components:** [Background, Minimap and Controls](#components)
* **Reliable**: Written in [Typescript](https://www.typescriptlang.org/) and tested with [cypress](https://www.cypress.io/)
In order to make this library as flexible as possible we dont do any state updates besides the positions. This means that you need to pass the functions to remove an element or connect nodes by yourself. You can implement your own ones or use the helper functions that come with the library.
@@ -78,9 +78,10 @@ const BasicFlow = () => <ReactFlow elements={elements} />;
- `snapToGrid`: default: `false`
- `snapGrid`: [x, y] array - default: `[16, 16]`
- `onlyRenderVisibleNodes`: default: `true`
- `translateExtent`: [default `[[-∞, -∞], [+∞, +∞]]`](https://github.com/d3/d3-zoom#zoom_translateExtent)
#### Event Handlers
- `onElementClick(event: MouseEvent, element: Node | Edge)`: called when user clicks node or edge click
- `onElementClick(event: MouseEvent, element: Node | Edge)`: called when user clicks node or edge
- `onElementsRemove(elements: Elements)`: called when user removes node or edge
- `onNodeDragStart(event: MouseEvent, node: Node)`: node drag start
- `onNodeDragStop(event: MouseEvent, node: Node)`: node drag stop
@@ -93,21 +94,21 @@ const BasicFlow = () => <ReactFlow elements={elements} />;
- `onConnectStop(event: MouseEvent)`: called when user stops to drag connection line
- `onConnectEnd(event: MouseEvent)`: called after user stops or connects nodes
- `onLoad(reactFlowInstance)`: called after flow is initialized
- `onMove()`: called when user is panning or zooming
- `onMoveStart()`: called when user starts panning or zooming
- `onMoveEnd()`: called when user ends panning or zooming
- `onMove(flowTransform: FlowTransform)`: called when user is panning or zooming
- `onMoveStart(flowTransform: FlowTransform)`: called when user starts panning or zooming
- `onMoveEnd(flowTransform: FlowTransform)`: called when user ends panning or zooming
- `onSelectionChange(elements: Elements)`: called when user selects one or multiple elements
- `onSelectionDragStart(evt: MouseEvent, nodes: Node[])`: called when user starts to drag a selection
- `onSelectionDrag(evt: MouseEvent, nodes: Node[])`: called when user drags a selection
- `onSelectionDragStop(evt: MouseEvent, nodes: Node[])`: called when user stops to drag a selection
- `onPaneClick(event: MouseEvent)`: called when user clicks directly on the canvas
- `onPaneContextMenu(event: MouseEvent)`: called when user does a right-click on the canvas
- `onPaneScroll(event: WheelEvent)`: called when user scrolls pane (only works when `zoomOnScroll` is set to `false)
- `onPaneScroll(event: WheelEvent)`: called when user scrolls pane (only works when `zoomOnScroll` is set to `false)
#### Interaction
- `nodesDraggable`: default: `true`. This applies to all nodes. You can also change the behavior of a specific node with the `draggable` node option.
- `nodesConnectable`: default: `true`. This applies to all nodes. You can also change the behavior of a specific node with the `connectable` node option.
- `elementsSelectable`: default: `true`. This applies to all elements. You can also change the behavior of a specific node with the `selectable` node option.
- `nodesDraggable`: default: `true`. This applies to all nodes. You can also change the behavior of a specific node with the `draggable` node option
- `nodesConnectable`: default: `true`. This applies to all nodes. You can also change the behavior of a specific node with the `connectable` node option
- `elementsSelectable`: default: `true`. This applies to all elements. You can also change the behavior of a specific node with the `selectable` node option
- `zoomOnScroll`: default: `true`
- `zoomOnDoubleClick`: default: `true`
- `selectNodesOnDrag`: default: `true`
@@ -145,13 +146,14 @@ const BasicFlow = () => <ReactFlow onLoad={onLoad} elements={[]} />;
### project
Transforms pixel coordinates to the internal React Flow coordinate system
Transforms pixel coordinates to the internal ReactFlow coordinate system.
This can be used when you drag nodes (from a side bar for example) and need the position on the pane.
`project = (position: XYPosition): XYPosition`
### fitView
Fits view port so that all nodes are visible
Fits view port so that all nodes are inside the view port.
`fitView = ({ padding }): void`
@@ -173,7 +175,7 @@ Fits view port so that all nodes are visible
### setTransform
Sets position and zoom of the pane
Sets position and zoom of the pane.
`setTransform = (transform: FlowTransform): void`
@@ -194,9 +196,9 @@ Node example: `{ id: '1', type: 'input', data: { label: 'Node 1' }, position: {
- `targetPosition`: 'left' | 'right' | 'top' | 'bottom' handle position - default: 'top'
- `sourcePosition`: 'left' | 'right' | 'top' | 'bottom' handle position - default: 'bottom'
- `isHidden`: if `true`, the node will not be rendered
- `draggable`: boolean - if option is not set, the node is draggable (overwrites generel `nodesDraggable` option)
- `connectable`: boolean - if option is not set, the node is connectable (overwrites generel `nodesConnectable` option)
- `selectable`: boolean - if option is not set, the node is selectable (overwrites generel `elementsSelectable` option)
- `draggable`: boolean - if option is not set, the node is draggable (overwrites general `nodesDraggable` option)
- `connectable`: boolean - if option is not set, the node is connectable (overwrites general `nodesConnectable` option)
- `selectable`: boolean - if option is not set, the node is selectable (overwrites general `elementsSelectable` option)
## Node Types & Custom Nodes
@@ -236,7 +238,7 @@ Your custom nodes are wrapped so that the basic functions like dragging or selec
### Prevent dragging
If you have controls inside your custom node that should not drag the node you can add the class name `nodrag`.
If you have controls or other elements inside your custom node that should not drag the node you can add the class name `nodrag`.
## Handle Component
@@ -531,10 +533,12 @@ The React Flow wrapper has the className `react-flow`. If you want to change the
You could achieve the same effect by passing a style prop to the `ReactFlow` component:
```javascript
const style = { background: 'red', width: '100%' height: '300px' };
const FlowWithRedBg = (
<ReactFlow
elements={elements}
style={{ background: 'red', width: '100%' height: '300px' }}
style={style}
/>
);
```
@@ -549,37 +553,37 @@ import ReactFlow, { isNode, isEdge, removeElements, addEdge } from 'react-flow-r
### isEdge
Returns true if element is an edge
Returns `true` if the passed element is an edge.
`isEdge = (element: Node | Edge): element is Edge`
### isNode
Returns true if element is a node
Returns `true` if the passed element is a node.
`isNode = (element: Node | Edge): element is Node`
### removeElements
Returns an array of elements without the ones from `elementsToRemove`. It also removes all incoming/outgoing edges if you just pass one or multiple nodes
Returns an array of elements without the ones from `elementsToRemove`. It also removes all incoming/outgoing edges if you just pass one or multiple nodes.
`removeElements = (elementsToRemove: Elements, elements: Elements): Elements`
### addEdge
Returns elements array with added edge
Returns an array with elements with the added edge.
`addEdge = (edgeParams: Edge, elements: Elements): Elements`
### getOutgoers
Returns all direct child nodes of the passed node
Returns all direct child nodes of the passed node.
`getOutgoers = (node: Node, elements: Elements): Node[]`
### getConnectedEdges
Returns all edges that are connected to the passed nodes
Returns all edges that are connected to the passed nodes.
`getConnectedEdges = (nodes: Node[], edges: Edge[]): Edge[]`
@@ -657,7 +661,7 @@ This serves the content of the `example` folder and watches changes inside the `
# Testing
Testing is done with cypress. You can find all tests in the [`integration/flow`](/cypress/integration/flow) folder. In order to run the tests do:
Testing is done with cypress. You can find the tests in the [`integration/flow`](/cypress/integration/flow) folder. In order to run the tests do:
```
npm run test
+10 -1
View File
@@ -1,9 +1,14 @@
import React from 'react';
import { useStoreState } from 'react-flow-renderer';
import { useStoreState, useStoreActions } from 'react-flow-renderer';
export default () => {
const nodes = useStoreState((store) => store.nodes);
const transform = useStoreState((store) => store.transform);
const setSelectedElements = useStoreActions((actions) => actions.setSelectedElements);
const selectAll = () => {
setSelectedElements(nodes.map((node) => ({ id: node.id, type: node.type })));
};
return (
<aside>
@@ -20,6 +25,10 @@ export default () => {
Node {node.id} - x: {node.__rf.position.x.toFixed(2)}, y: {node.__rf.position.y.toFixed(2)}
</div>
))}
<div className="selectall">
<button onClick={selectAll}>select all nodes</button>
</div>
</aside>
);
};
+4
View File
@@ -30,3 +30,7 @@
flex-grow: 1;
height: 100%;
}
.providerflow .selectall {
margin-top: 10px;
}
+37 -147
View File
@@ -63,13 +63,13 @@
}
},
"@babel/core": {
"version": "7.11.5",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.5.tgz",
"integrity": "sha512-fsEANVOcZHzrsV6dMVWqpSeXClq3lNbYrfFGme6DE25FQWe7pyeYpXyx9guqUnpy466JLzZ8z4uwSr2iv60V5Q==",
"version": "7.11.6",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.6.tgz",
"integrity": "sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.10.4",
"@babel/generator": "^7.11.5",
"@babel/generator": "^7.11.6",
"@babel/helper-module-transforms": "^7.11.0",
"@babel/helpers": "^7.10.4",
"@babel/parser": "^7.11.5",
@@ -83,7 +83,7 @@
"lodash": "^4.17.19",
"resolve": "^1.3.2",
"semver": "^5.4.1",
"source-map": "^0.6.1"
"source-map": "^0.5.0"
},
"dependencies": {
"@babel/code-frame": {
@@ -95,6 +95,17 @@
"@babel/highlight": "^7.10.4"
}
},
"@babel/generator": {
"version": "7.11.6",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz",
"integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==",
"dev": true,
"requires": {
"@babel/types": "^7.11.5",
"jsesc": "^2.5.1",
"source-map": "^0.5.0"
}
},
"@babel/helper-member-expression-to-functions": {
"version": "7.11.0",
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz",
@@ -212,12 +223,6 @@
"lodash": "^4.17.19",
"to-fast-properties": "^2.0.0"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
}
}
},
@@ -4144,9 +4149,9 @@
}
},
"@octokit/plugin-paginate-rest": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.3.1.tgz",
"integrity": "sha512-81A+ONLpcSX7vWxnEmVZteQPNsbdeScSVUqjgMYPSk1trzG69iYkhS42wPRWtN0nYw6OEmT48DNeQCjHeyroYw==",
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.3.2.tgz",
"integrity": "sha512-PjHbMhKryxClCrmfvRpGaKCTxUcHIf2zirWRV9SMGf0EmxD/rFew/abSqbMiLl9uQgRZvqtTyCRMGMlUv1ZsBg==",
"dev": true,
"requires": {
"@octokit/types": "^5.3.0"
@@ -6104,12 +6109,6 @@
"supports-color": "^5.3.0"
}
},
"changelog-filename-regex": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/changelog-filename-regex/-/changelog-filename-regex-1.1.2.tgz",
"integrity": "sha512-kpOfKlZ9x2UpeC4at6FAXHLKfi/JEUqUqkPCb1JUCa5FnNbJIzOHRM9RfeQ1QDcpj+Gxuc/UoHqASgmEeFDejQ==",
"dev": true
},
"chardet": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
@@ -7118,15 +7117,6 @@
"object-keys": "^1.0.12"
}
},
"define-property": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
"integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
"dev": true,
"requires": {
"is-descriptor": "^1.0.0"
}
},
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -7157,26 +7147,6 @@
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==",
"dev": true
},
"detect-repo-changelog": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/detect-repo-changelog/-/detect-repo-changelog-1.0.1.tgz",
"integrity": "sha1-whCMu/CWTae8B0ZFtq0dO4CmvW0=",
"dev": true,
"requires": {
"changelog-filename-regex": "^1.1.0",
"is-regular-file": "^1.0.1",
"lodash.find": "^4.6.0",
"pify": "^2.3.0"
},
"dependencies": {
"pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
}
}
},
"dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -8311,9 +8281,9 @@
}
},
"rxjs": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.2.tgz",
"integrity": "sha512-BHdBMVoWC2sL26w//BCu3YzKT4s2jip/WhwsGEDmeKYBhKDZeYezVUnHatYB7L85v5xs0BAQmg6BEYJEKxBabg==",
"version": "6.6.3",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz",
"integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==",
"dev": true,
"requires": {
"tslib": "^1.9.0"
@@ -8360,15 +8330,6 @@
"integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=",
"dev": true
},
"is-accessor-descriptor": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
"integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
"dev": true,
"requires": {
"kind-of": "^6.0.0"
}
},
"is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
@@ -8419,32 +8380,12 @@
"rgba-regex": "^1.0.0"
}
},
"is-data-descriptor": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
"integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
"dev": true,
"requires": {
"kind-of": "^6.0.0"
}
},
"is-date-object": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz",
"integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==",
"dev": true
},
"is-descriptor": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
"integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
"dev": true,
"requires": {
"is-accessor-descriptor": "^1.0.0",
"is-data-descriptor": "^1.0.0",
"kind-of": "^6.0.2"
}
},
"is-directory": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz",
@@ -8566,12 +8507,6 @@
"has": "^1.0.3"
}
},
"is-regular-file": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-regular-file/-/is-regular-file-1.1.1.tgz",
"integrity": "sha512-+1U3MZrVwC4HM6VUKk3L5fiHtNd2d9kayzEJhmQ+B+uIBPE/p8Fy8QVdkx0HIr3o9J5TOKJY40eI5GfTfBqbdA==",
"dev": true
},
"is-resolvable": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz",
@@ -8791,12 +8726,6 @@
"json-buffer": "3.0.1"
}
},
"kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true
},
"latest-version": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz",
@@ -9039,12 +8968,6 @@
"integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=",
"dev": true
},
"lodash.find": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.find/-/lodash.find-4.6.0.tgz",
"integrity": "sha1-ywcE1Hq3F4n/oN6Ll92Sb7iLE7E=",
"dev": true
},
"lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@@ -9418,9 +9341,9 @@
"dev": true
},
"node-fetch": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
"integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==",
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==",
"dev": true
},
"node-releases": {
@@ -11102,9 +11025,9 @@
}
},
"release-it": {
"version": "13.7.1",
"resolved": "https://registry.npmjs.org/release-it/-/release-it-13.7.1.tgz",
"integrity": "sha512-OAupGLMZJBGrGRJmb8RvZ8ZQAQquKlkGGYvgRrJmkHE+jnGdf+YSvEmItBv08CwhDcwCyXdTkkN/rJ2Flt68+g==",
"version": "14.0.2",
"resolved": "https://registry.npmjs.org/release-it/-/release-it-14.0.2.tgz",
"integrity": "sha512-YJEI8z/cl0gXuVN3a4n/oZ9LYjR9tzEiuUbUROMKG172tzrIDPILT48CR3PQ0fpTGqGimraNFCZrTFiZWqP4pA==",
"dev": true,
"requires": {
"@iarna/toml": "2.2.5",
@@ -11114,7 +11037,6 @@
"cosmiconfig": "7.0.0",
"debug": "4.1.1",
"deprecated-obj": "2.0.0",
"detect-repo-changelog": "1.0.1",
"execa": "4.0.3",
"find-up": "5.0.0",
"form-data": "3.0.0",
@@ -11131,11 +11053,9 @@
"parse-json": "5.1.0",
"semver": "7.3.2",
"shelljs": "0.8.4",
"supports-color": "7.2.0",
"update-notifier": "4.1.1",
"url-join": "4.0.1",
"uuid": "8.3.0",
"window-size": "1.1.1",
"yaml": "1.10.0",
"yargs-parser": "19.0.4"
},
@@ -11377,9 +11297,9 @@
}
},
"rollup": {
"version": "2.26.9",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.26.9.tgz",
"integrity": "sha512-XIiWYLayLqV+oY4S2Lub/shJq4uk/QQLwWToYCL4LjZbYHbFK3czea4UDVRUJu+zNmKmxq5Zb/OG7c5HSvH2TQ==",
"version": "2.26.10",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.26.10.tgz",
"integrity": "sha512-dUnjCWOA0h9qNX6qtcHidyatz8FAFZxVxt1dbcGtKdlJkpSxGK3G9+DLCYvtZr9v94D129ij9zUhG+xbRoqepw==",
"dev": true,
"requires": {
"fsevents": "~2.1.2"
@@ -11640,9 +11560,9 @@
}
},
"rollup-plugin-terser": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.1.tgz",
"integrity": "sha512-HL0dgzSxBYG/Ly9i/E5Sc+PuKKZ0zBzk11VmLCfdUtpqH4yYqkLclPkTqRy85FU9246yetImOClaQ/ufnj08vg==",
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz",
"integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.10.4",
@@ -12257,9 +12177,9 @@
"dev": true
},
"terser": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.2.1.tgz",
"integrity": "sha512-/AOtjRtAMNGO0fIF6m8HfcvXTw/2AKpsOzDn36tA5RfhRdeXyb4RvHxJ5Pah7iL6dFkLk+gOnCaNHGwJPl6TrQ==",
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.3.0.tgz",
"integrity": "sha512-XTT3D3AwxC54KywJijmY2mxZ8nJiEjBHVYzq8l9OaYuRFWeQNBwvipuzzYEP4e+/AVcd1hqG/CqgsdIRyT45Fg==",
"dev": true,
"requires": {
"commander": "^2.20.0",
@@ -12687,36 +12607,6 @@
"string-width": "^4.0.0"
}
},
"window-size": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/window-size/-/window-size-1.1.1.tgz",
"integrity": "sha512-5D/9vujkmVQ7pSmc0SCBmHXbkv6eaHwXEx65MywhmUMsI8sGqJ972APq1lotfcwMKPFLuCFfL8xGHLIp7jaBmA==",
"dev": true,
"requires": {
"define-property": "^1.0.0",
"is-number": "^3.0.0"
},
"dependencies": {
"is-number": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
"integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
"dev": true,
"requires": {
"kind-of": "^3.0.2"
}
},
"kind-of": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"dev": true,
"requires": {
"is-buffer": "^1.1.5"
}
}
}
},
"windows-release": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/windows-release/-/windows-release-4.0.0.tgz",
+4 -4
View File
@@ -35,7 +35,7 @@
"resize-observer": "^1.0.0"
},
"devDependencies": {
"@babel/core": "^7.11.5",
"@babel/core": "^7.11.6",
"@babel/preset-env": "^7.11.5",
"@babel/preset-react": "^7.10.4",
"@svgr/rollup": "^5.4.0",
@@ -51,8 +51,8 @@
"prettier": "2.1.1",
"prop-types": "^15.7.2",
"react": "^16.13.1",
"release-it": "^13.7.1",
"rollup": "^2.26.9",
"release-it": "^14.0.2",
"rollup": "^2.26.10",
"rollup-plugin-babel": "^4.4.0",
"rollup-plugin-bundle-size": "^1.0.3",
"rollup-plugin-commonjs": "^10.1.0",
@@ -62,7 +62,7 @@
"rollup-plugin-postcss": "^3.1.8",
"rollup-plugin-replace": "^2.2.0",
"rollup-plugin-serve": "^1.0.4",
"rollup-plugin-terser": "^7.0.1",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-typescript2": "^0.27.2",
"rollup-plugin-uglify": "^6.0.4",
"start-server-and-test": "^1.11.3",
+107 -205
View File
@@ -16,151 +16,11 @@ import { ResizeObserver } from 'resize-observer';
import { useStoreActions } from '../../store/hooks';
import { Provider } from '../../contexts/NodeIdContext';
import {
Node,
XYPosition,
Transform,
ElementId,
NodeComponentProps,
WrapNodeProps,
Elements,
Edge,
NodePosUpdate,
} from '../../types';
import { noop } from '../../utils';
import { Node, NodeComponentProps, WrapNodeProps } from '../../types';
const getMouseEvent = (event: MouseEvent | TouchEvent) =>
typeof TouchEvent !== 'undefined' && event instanceof TouchEvent ? event.touches[0] : (event as MouseEvent);
interface OnDragStartParams {
event: MouseEvent | TouchEvent;
id: ElementId;
type: string;
data: any;
selectNodesOnDrag: boolean;
isSelectable: boolean;
setOffset: (pos: XYPosition) => void;
transform: Transform;
position: XYPosition;
setSelectedElements: (elms: Elements | Node | Edge) => void;
onNodeDragStart?: (event: MouseEvent, node: Node) => void;
}
const onStart = ({
event,
onNodeDragStart,
id,
type,
data,
selectNodesOnDrag,
setOffset,
transform,
position,
setSelectedElements,
isSelectable,
}: OnDragStartParams): false | void => {
const startEvent = getMouseEvent(event);
const scaledClient: XYPosition = {
x: startEvent.clientX * (1 / transform[2]),
y: startEvent.clientY * (1 / transform[2]),
};
const offsetX = scaledClient.x - position.x - transform[0];
const offsetY = scaledClient.y - position.y - transform[1];
const node = { id, type, position, data };
setOffset({ x: offsetX, y: offsetY });
if (onNodeDragStart) {
onNodeDragStart(event as MouseEvent, node);
}
if (selectNodesOnDrag && isSelectable) {
setSelectedElements({ id, type } as Node);
}
};
interface OnDragParams {
event: MouseEvent | TouchEvent;
setDragging: (isDragging: boolean) => void;
id: ElementId;
offset: XYPosition;
transform: Transform;
updateNodePos: (params: NodePosUpdate) => void;
}
const onDrag = ({ event, setDragging, id, offset, transform, updateNodePos }: OnDragParams): void => {
const dragEvent = getMouseEvent(event);
const scaledClient = {
x: dragEvent.clientX / transform[2],
y: dragEvent.clientY / transform[2],
};
setDragging(true);
updateNodePos({
id,
pos: {
x: scaledClient.x - transform[0] - offset.x,
y: scaledClient.y - transform[1] - offset.y,
},
});
};
interface OnDragStopParams {
event: MouseEvent;
isDragging: boolean;
setDragging: (isDragging: boolean) => void;
id: ElementId;
type: string;
position: XYPosition;
data: any;
selectNodesOnDrag: boolean;
isSelectable: boolean;
setSelectedElements: (elms: Elements | Node | Edge) => void;
onNodeDragStop?: (event: MouseEvent, node: Node) => void;
onClick?: (event: MouseEvent, node: Node) => void;
}
const onStop = ({
event,
id,
type,
position,
data,
isDragging,
setDragging,
selectNodesOnDrag,
isSelectable,
onNodeDragStop,
onClick,
setSelectedElements,
}: OnDragStopParams): void => {
const node = {
id,
type,
position,
data,
} as Node;
if (!isDragging && isSelectable) {
if (!selectNodesOnDrag) {
setSelectedElements({ id, type } as Node);
}
if (onClick) {
return onClick(event, node);
}
}
setDragging(false);
if (onNodeDragStop) {
onNodeDragStop(event, node);
}
};
export default (NodeComponent: ComponentType<NodeComponentProps>) => {
const NodeWrapper = ({
id,
@@ -195,60 +55,129 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
const nodeElement = useRef<HTMLDivElement>(null);
const [offset, setOffset] = useState({ x: 0, y: 0 });
const [isDragging, setDragging] = useState(false);
const position = { x: xPos, y: yPos };
const nodeClasses = cc([
'react-flow__node',
`react-flow__node-${type}`,
className,
{
selected,
selectable: isSelectable,
},
]);
const node = { id, type, position, data };
const position = useMemo(() => ({ x: xPos, y: yPos }), [xPos, yPos]);
const node = useMemo(() => ({ id, type, position, data }), [id, type, position, data]);
const nodeStyle: CSSProperties = useMemo(
() => ({
zIndex: selected ? 10 : 3,
transform: `translate(${xPos}px,${yPos}px)`,
pointerEvents: isSelectable || isDraggable || onClick ? 'all' : 'none',
opacity: isInitialized ? 1 : 0, // prevents jumping of nodes on start
...style,
}),
[selected, xPos, yPos, isSelectable, isDraggable, onClick, isInitialized, style]
);
const onMouseEnterHandler = useMemo(() => {
if (!onMouseEnter || isDragging) {
return noop;
return;
}
return (event: MouseEvent) => onMouseEnter(event, node);
}, [onMouseEnter, isDragging]);
}, [onMouseEnter, isDragging, node]);
const onMouseMoveHandler = useMemo(() => {
if (!onMouseMove || isDragging) {
return noop;
return;
}
return (event: MouseEvent) => onMouseMove(event, node);
}, [onMouseMove, isDragging]);
}, [onMouseMove, isDragging, node]);
const onMouseLeaveHandler = useMemo(() => {
if (!onMouseLeave || isDragging) {
return noop;
return;
}
return (event: MouseEvent) => onMouseLeave(event, node);
}, [onMouseLeave, isDragging]);
}, [onMouseLeave, isDragging, node]);
const onContextMenuHandler = useMemo(() => {
if (!onContextMenu) {
return noop;
return;
}
return (event: MouseEvent) => onContextMenu(event, node);
}, [onContextMenu]);
}, [onContextMenu, node]);
const onSelectNodeHandler = useCallback(
(event: MouseEvent) => {
if (!isDraggable && isSelectable) {
setSelectedElements({ id: node.id, type: node.type } as Node);
}
if (!isDraggable) {
if (isSelectable) {
setSelectedElements({ id: node.id, type: node.type } as Node);
}
if (onClick) {
onClick(event, node);
if (onClick) {
onClick(event, node);
}
}
},
[isSelectable, isDraggable, node]
[isSelectable, isDraggable, onClick, node]
);
const onDragStart = useCallback(
(event) => {
const startEvent = getMouseEvent(event);
const scaledClientX = startEvent.clientX / transform[2];
const scaledClientY = startEvent.clientY / transform[2];
const offsetX = scaledClientX - position.x - transform[0];
const offsetY = scaledClientY - position.y - transform[1];
setOffset({ x: offsetX, y: offsetY });
if (onNodeDragStart) {
onNodeDragStart(event as MouseEvent, node);
}
if (selectNodesOnDrag && isSelectable) {
setSelectedElements({ id: node.id, type: node.type } as Node);
}
},
[node, transform, position, selectNodesOnDrag, isSelectable, onNodeDragStart]
);
const onDrag = useCallback(
(event) => {
const dragEvent = getMouseEvent(event);
const scaledClientX = dragEvent.clientX / transform[2];
const scaledClientY = dragEvent.clientY / transform[2];
setDragging(true);
updateNodePos({
id,
pos: {
x: scaledClientX - transform[0] - offset.x,
y: scaledClientY - transform[1] - offset.y,
},
});
},
[id, transform, offset]
);
const onDragStop = useCallback(
(event) => {
if (!isDragging) {
if (isSelectable && !selectNodesOnDrag) {
setSelectedElements({ id: node.id, type: node.type } as Node);
}
if (onClick) {
onClick(event as MouseEvent, node);
}
return;
}
setDragging(false);
if (onNodeDragStop) {
onNodeDragStop(event as MouseEvent, node);
}
},
[node, isDragging, isSelectable, selectNodesOnDrag, onClick, onNodeDragStop]
);
useEffect(() => {
@@ -279,48 +208,21 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
return null;
}
const nodeStyle: CSSProperties = {
zIndex: selected ? 10 : 3,
transform: `translate(${xPos}px,${yPos}px)`,
pointerEvents: isSelectable || isDraggable || onClick ? 'all' : 'none',
opacity: isInitialized ? 1 : 0, // prevents jumping of nodes on start
...style,
};
const nodeClasses = cc([
'react-flow__node',
`react-flow__node-${type}`,
className,
{
selected,
selectable: isSelectable,
},
]);
return (
<DraggableCore
onStart={(event) =>
onStart({
event: event as MouseEvent,
selectNodesOnDrag,
isSelectable,
onNodeDragStart,
id,
type,
data,
setOffset,
transform,
position,
setSelectedElements,
})
}
onDrag={(event) => onDrag({ event: event as MouseEvent, setDragging, id, offset, transform, updateNodePos })}
onStop={(event) =>
onStop({
event: event as MouseEvent,
onNodeDragStop,
selectNodesOnDrag,
isSelectable,
onClick,
isDragging,
setDragging,
id,
type,
position,
data,
setSelectedElements,
})
}
onStart={onDragStart}
onDrag={onDrag}
onStop={onDragStop}
scale={transform[2]}
disabled={!isDraggable}
cancel=".nodrag"
+10
View File
@@ -24,6 +24,7 @@ import {
OnConnectStartFunc,
OnConnectStopFunc,
OnConnectEndFunc,
TranslateExtent,
} from '../../types';
export interface GraphViewProps {
@@ -67,6 +68,7 @@ export interface GraphViewProps {
maxZoom: number;
defaultZoom: number;
defaultPosition: [number, number];
translateExtent?: TranslateExtent;
arrowHeadColor: string;
markerEndId?: string;
zoomOnScroll: boolean;
@@ -112,6 +114,7 @@ const GraphView = ({
maxZoom,
defaultZoom,
defaultPosition,
translateExtent,
arrowHeadColor,
markerEndId,
zoomOnScroll,
@@ -137,6 +140,7 @@ const GraphView = ({
const setElementsSelectable = useStoreActions((actions) => actions.setElementsSelectable);
const setInitTransform = useStoreActions((actions) => actions.setInitTransform);
const setMinMaxZoom = useStoreActions((actions) => actions.setMinMaxZoom);
const setTranslateExtent = useStoreActions((actions) => actions.setTranslateExtent);
const fitView = useStoreActions((actions) => actions.fitView);
const zoom = useStoreActions((actions) => actions.zoom);
const zoomTo = useStoreActions((actions) => actions.zoomTo);
@@ -254,6 +258,12 @@ const GraphView = ({
setMinMaxZoom({ minZoom, maxZoom });
}, [minZoom, maxZoom]);
useEffect(() => {
if (typeof translateExtent !== 'undefined') {
setTranslateExtent(translateExtent);
}
}, [translateExtent]);
return (
<div className="react-flow__renderer" ref={rendererNode}>
<NodeRenderer
+4
View File
@@ -30,6 +30,7 @@ import {
OnConnectStartFunc,
OnConnectStopFunc,
OnConnectEndFunc,
TranslateExtent,
} from '../../types';
import '../../style.css';
@@ -77,6 +78,7 @@ export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'on
maxZoom: number;
defaultZoom: number;
defaultPosition: [number, number];
translateExtent?: TranslateExtent;
arrowHeadColor: string;
markerEndId?: string;
zoomOnScroll: boolean;
@@ -125,6 +127,7 @@ const ReactFlow = ({
maxZoom,
defaultZoom,
defaultPosition,
translateExtent,
arrowHeadColor,
markerEndId,
zoomOnScroll,
@@ -176,6 +179,7 @@ const ReactFlow = ({
maxZoom={maxZoom}
defaultZoom={defaultZoom}
defaultPosition={defaultPosition}
translateExtent={translateExtent}
arrowHeadColor={arrowHeadColor}
markerEndId={markerEndId}
zoomOnScroll={zoomOnScroll}
+17 -1
View File
@@ -26,6 +26,7 @@ import {
SetConnectionId,
NodePosUpdate,
FitViewParams,
TranslateExtent,
} from '../types';
type TransformXYK = {
@@ -65,6 +66,7 @@ export interface StoreModel {
d3Initialised: boolean;
minZoom: number;
maxZoom: number;
translateExtent: TranslateExtent;
nodesSelectionActive: boolean;
selectionActive: boolean;
@@ -116,6 +118,8 @@ export interface StoreModel {
setMinMaxZoom: Action<StoreModel, SetMinMaxZoom>;
setTranslateExtent: Action<StoreModel, TranslateExtent>;
setSnapGrid: Action<StoreModel, SetSnapGrid>;
setConnectionPosition: Action<StoreModel, XYPosition>;
@@ -154,6 +158,10 @@ export const storeModel: StoreModel = {
d3Initialised: false,
minZoom: 0.5,
maxZoom: 2,
translateExtent: [
[Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
[Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY],
],
nodesSelectionActive: false,
selectionActive: false,
@@ -351,7 +359,7 @@ export const storeModel: StoreModel = {
}),
initD3: action((state, zoomPaneNode) => {
const d3ZoomInstance = zoom().scaleExtent([state.minZoom, state.maxZoom]);
const d3ZoomInstance = zoom().scaleExtent([state.minZoom, state.maxZoom]).translateExtent(state.translateExtent);
const selection = select(zoomPaneNode).call(d3ZoomInstance);
@@ -369,6 +377,14 @@ export const storeModel: StoreModel = {
}
}),
setTranslateExtent: action((state, translateExtent) => {
state.translateExtent = translateExtent;
if (state.d3Zoom) {
state.d3Zoom.translateExtent(translateExtent);
}
}),
setConnectionPosition: action((state, position) => {
state.connectionPosition = position;
}),
+2
View File
@@ -272,3 +272,5 @@ export type FlowTransform = {
y: number;
zoom: number;
};
export type TranslateExtent = [[number, number], [number, number]];
-2
View File
@@ -1,8 +1,6 @@
import { DraggableEvent } from 'react-draggable';
import { MouseEvent as ReactMouseEvent } from 'react';
export const noop = () => {};
export const isInputDOMNode = (e: ReactMouseEvent | DraggableEvent | KeyboardEvent) => {
const target = e.target as HTMLElement;
return e && target && ['INPUT', 'SELECT', 'TEXTAREA', 'BUTTON'].includes(target.nodeName);