Merge remote-tracking branch 'upstream/v11' into feat/cancel-connection
This commit is contained in:
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"modules": false
|
||||
}
|
||||
],
|
||||
"@babel/preset-typescript",
|
||||
"@babel/preset-react"
|
||||
],
|
||||
"plugins": ["@babel/plugin-transform-runtime"]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
# Changesets
|
||||
|
||||
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
|
||||
with multi-package repos, or single-package repos to help you version and publish your code. You can
|
||||
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
|
||||
|
||||
We have a quick list of common questions to get you started engaging with this project in
|
||||
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@2.1.1/schema.json",
|
||||
"changelog": ["@changesets/changelog-github", { "repo": "wbkd/react-flow" }],
|
||||
"commit": false,
|
||||
"fixed": [],
|
||||
"linked": [],
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": []
|
||||
}
|
||||
@@ -9,3 +9,6 @@ dist
|
||||
stats.html
|
||||
.eslintcache
|
||||
.idea
|
||||
.log
|
||||
.turbo
|
||||
.rollup.cache
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
.DS_Store
|
||||
.cache
|
||||
.vscode
|
||||
.dependabot
|
||||
node_modules
|
||||
examples
|
||||
cypress
|
||||
.env
|
||||
tsconfig.json
|
||||
src
|
||||
@@ -1,2 +1,3 @@
|
||||
registry = "https://registry.npmjs.com/"
|
||||
legacy-peer-deps = true
|
||||
registry="https://registry.npmjs.com/"
|
||||
legacy-peer-deps=true
|
||||
strict-peer-dependencies=false
|
||||
@@ -1,7 +0,0 @@
|
||||
|
||||
{
|
||||
"plugins": {
|
||||
"autoprefixer" : {},
|
||||
"postcss-nested": {}
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -1 +1,5 @@
|
||||
*.md
|
||||
*.md
|
||||
dist
|
||||
node_modules
|
||||
.next
|
||||
build
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"git": {
|
||||
"commitMessage": "chore: release v${version}"
|
||||
},
|
||||
"github": {
|
||||
"release": true
|
||||
},
|
||||
"hooks": {
|
||||
"after:bump": "npm run build",
|
||||
"after:release": "echo Successfully released ${name} v${version}."
|
||||
}
|
||||
}
|
||||
Vendored
-3
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
||||
+4
-4
@@ -25,7 +25,7 @@ The docs have its own [react-flow-docs repo](https://github.com/wbkd/react-flow-
|
||||
If you want to contribute improvements or new features we are happy to review your PR :)
|
||||
Please use a meaningful commit message and add a little description of your changes.
|
||||
|
||||
1. Install dependencies `npm install` and example dependencies `cd example && npm install`
|
||||
2. Run library start task `npm start` and example start task `cd example && npm start`
|
||||
3. Now you can test your changes with the existing examples. Feel free to add a new one if it's needed for your changes
|
||||
4. Run tests `npm test` and add new ones if you are introducing a new feature
|
||||
1. Install dependencies `pnpm install`
|
||||
2. Start dev server `pnpm dev`
|
||||
3. Test your changes with the existing examples or add a new one if it's needed for your changes
|
||||
4. Run tests `pnpm test` and add new new tests if you are introducing a new feature
|
||||
|
||||
@@ -3,19 +3,18 @@
|
||||
|
||||
<div align="center">
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
### Wire Your Ideas With React Flow!
|
||||
A highly customizable React component for building interactive graphs and node-based editors.
|
||||
|
||||
[🚀 Getting Started](https://reactflow.dev/docs/getting-started/installation) | [📖 Documentation](https://reactflow.dev/docs/api/react-flow-props) | [📺 Examples](https://reactflow.dev/docs/examples/overview) | [☎️ Discord](https://discord.gg/RVmnytFmGW) | [💎 React Flow Pro](https://pro.reactflow.dev/pricing)
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
## Key Features
|
||||
|
||||
@@ -34,10 +33,10 @@ You can find more information in our [React Flow Pro FAQs](https://pro.reactflow
|
||||
|
||||
## Installation
|
||||
|
||||
The easiest way to get the latest version of React Flow is to install it via npm:
|
||||
The easiest way to get the latest version of React Flow is to install it via npm, yarn or pnpm:
|
||||
|
||||
```bash
|
||||
npm install react-flow-renderer
|
||||
npm install reactflow
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
@@ -45,7 +44,7 @@ npm install react-flow-renderer
|
||||
This is only a very basic usage example of React Flow. To see everything that is possible with the library, please refer to the [website](https://reactflow.dev) for [guides](https://reactflow.dev/docs/guides/custom-nodes), [examples](https://reactflow.dev/docs/examples/overview) and [API reference](https://reactflow.dev/docs/api/react-flow-props).
|
||||
|
||||
```jsx
|
||||
import ReactFlow, { MiniMap, Controls } from 'react-flow-renderer';
|
||||
import ReactFlow, { MiniMap, Controls } from 'reactflow';
|
||||
|
||||
function Flow({ nodes, edges, onNodesChange, onEdgesChange, onConnect }) {
|
||||
return (
|
||||
@@ -65,52 +64,40 @@ function Flow({ nodes, edges, onNodesChange, onEdgesChange, onConnect }) {
|
||||
|
||||
## Development
|
||||
|
||||
Before you start you need to build the project using `npm run build`. Then install the React Flow dependencies via `npm install` and the ones of the examples `cd example && npm install`.
|
||||
Before you can start developing please make sure that you have [pnpm](https://pnpm.io/) installed (`npm i -g pnpm`). Then install the dependencies using pnpm: `pnpm install`.
|
||||
|
||||
If you want to contribute or develop custom features the easiest way is to start the dev server:
|
||||
|
||||
```sh
|
||||
npm start
|
||||
```
|
||||
|
||||
and the example app via:
|
||||
|
||||
```sh
|
||||
cd example && npm start
|
||||
```
|
||||
|
||||
The example app serves the content of the `example` folder and watches changes inside the `src` folder. The examples are using the source of the `src` folder.
|
||||
For local development, you can use `pnpm dev`.
|
||||
|
||||
## Testing
|
||||
|
||||
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:
|
||||
Testing is done with cypress. You can find the tests in the [`examples/cypress`](/examples/cypress/) folder. In order to run the tests do:
|
||||
|
||||
```sh
|
||||
npm run test
|
||||
pnpm test
|
||||
```
|
||||
|
||||
## Maintainers
|
||||
|
||||
React Flow is developed and maintained by [webkid](https://webkid.io), a web development agency with focus on data driven applications from Berlin. If you need help or want to talk to us about a collaboration, feel free to contact us:
|
||||
|
||||
* Moritz Klack • [Twitter](https://twitter.com/moklick) • [Github](https://github.com/moklick)
|
||||
* Christopher Möller • [Twitter](https://twitter.com/chrtze) • [Github](https://github.com/chrtze)
|
||||
- Moritz Klack • [Twitter](https://twitter.com/moklick) • [Github](https://github.com/moklick)
|
||||
- Christopher Möller • [Twitter](https://twitter.com/chrtze) • [Github](https://github.com/chrtze)
|
||||
|
||||
You can also use our [contact form](https://pro.reactflow.dev/contact) or join the [React Flow Discord Server](https://discord.gg/Bqt6xrs).
|
||||
|
||||
## Community Packages
|
||||
|
||||
* [useUndoable](https://github.com/Infinium8/useUndoable) - Hook for undo/redo functionality with an explicit React Flow example
|
||||
* [react-flow-smart-edge](https://github.com/tisoap/react-flow-smart-edge) - Custom edge that doesn't intersect with nodes
|
||||
* [Feliz.ReactFlow](https://github.com/tforkmann/Feliz.ReactFlow) - Feliz React Bindings for React Flow
|
||||
- [useUndoable](https://github.com/Infinium8/useUndoable) - Hook for undo/redo functionality with an explicit React Flow example
|
||||
- [react-flow-smart-edge](https://github.com/tisoap/react-flow-smart-edge) - Custom edge that doesn't intersect with nodes
|
||||
- [Feliz.ReactFlow](https://github.com/tforkmann/Feliz.ReactFlow) - Feliz React Bindings for React Flow
|
||||
|
||||
## Credits
|
||||
|
||||
React Flow was initially developed for [datablocks](https://datablocks.pro), a graph-based editor for transforming, analyzing and visualizing data in your browser. Under the hood, React Flow depends on these great libraries:
|
||||
React Flow was initially developed for [datablocks](https://datablocks.pro), a graph-based editor for transforming, analyzing and visualizing data in the browser. Under the hood, React Flow depends on these great libraries:
|
||||
|
||||
* [d3-zoom](https://github.com/d3/d3-zoom) - used for zoom, pan and drag interactions with the graph canvas
|
||||
* [d3-drag](https://github.com/d3/d3-drag) - used for making the nodes draggable
|
||||
* [zustand](https://github.com/pmndrs/zustand) - internal state management
|
||||
- [d3-zoom](https://github.com/d3/d3-zoom) - used for zoom, pan and drag interactions with the graph canvas
|
||||
- [d3-drag](https://github.com/d3/d3-drag) - used for making the nodes draggable
|
||||
- [zustand](https://github.com/pmndrs/zustand) - internal state management
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 30">
|
||||
<path d="M3.692 4.63c0-.53.4-.938.939-.938h5.215V0H4.708C2.13 0 0 2.054 0 4.63v5.216h3.692V4.631zM27.354 0h-5.2v3.692h5.17c.53 0 .984.4.984.939v5.215H32V4.631A4.624 4.624 0 0027.354 0zm.954 24.83c0 .532-.4.94-.939.94h-5.215v3.768h5.215c2.577 0 4.631-2.13 4.631-4.707v-5.139h-3.692v5.139zm-23.677.94c-.531 0-.939-.4-.939-.94v-5.138H0v5.139c0 2.577 2.13 4.707 4.708 4.707h5.138V25.77H4.631z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 463 B |
@@ -1,3 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 32">
|
||||
<path d="M21.333 10.667H19.81V7.619C19.81 3.429 16.38 0 12.19 0 8 0 4.571 3.429 4.571 7.619v3.048H3.048A3.056 3.056 0 000 13.714v15.238A3.056 3.056 0 003.048 32h18.285a3.056 3.056 0 003.048-3.048V13.714a3.056 3.056 0 00-3.048-3.047zM12.19 24.533a3.056 3.056 0 01-3.047-3.047 3.056 3.056 0 013.047-3.048 3.056 3.056 0 013.048 3.048 3.056 3.056 0 01-3.048 3.047zm4.724-13.866H7.467V7.619c0-2.59 2.133-4.724 4.723-4.724 2.591 0 4.724 2.133 4.724 4.724v3.048z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 530 B |
@@ -1,3 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 5">
|
||||
<path d="M0 0h32v4.2H0z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 96 B |
@@ -1,3 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<path d="M32 18.133H18.133V32h-4.266V18.133H0v-4.266h13.867V0h4.266v13.867H32z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 152 B |
@@ -1,3 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 32">
|
||||
<path d="M21.333 10.667H19.81V7.619C19.81 3.429 16.38 0 12.19 0c-4.114 1.828-1.37 2.133.305 2.438 1.676.305 4.42 2.59 4.42 5.181v3.048H3.047A3.056 3.056 0 000 13.714v15.238A3.056 3.056 0 003.048 32h18.285a3.056 3.056 0 003.048-3.048V13.714a3.056 3.056 0 00-3.048-3.047zM12.19 24.533a3.056 3.056 0 01-3.047-3.047 3.056 3.056 0 013.047-3.048 3.056 3.056 0 013.048 3.048 3.056 3.056 0 01-3.048 3.047z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 472 B |
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"baseUrl": "http://localhost:3000",
|
||||
"viewportWidth": 1280,
|
||||
"viewportHeight": 720,
|
||||
"video": false,
|
||||
"pluginsFile": false
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add("login", (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||
|
||||
Cypress.Commands.add('drag', (selector, { x, y }) => {
|
||||
return cy
|
||||
.window()
|
||||
.then((window) =>
|
||||
cy
|
||||
.get(selector)
|
||||
.trigger('mousedown', { which: 1, view: window })
|
||||
.trigger('mousemove', { clientX: x, clientY: y, force: true })
|
||||
.wait(50)
|
||||
.trigger('mouseup', { view: window, force: true })
|
||||
);
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
/src_oldapi
|
||||
@@ -1,23 +0,0 @@
|
||||
# React Flow Examples
|
||||
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
## Start
|
||||
|
||||
Starts a dev server at http://localhost:3000
|
||||
|
||||
```sh
|
||||
npm start
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
Generated
-39423
File diff suppressed because it is too large
Load Diff
@@ -1,43 +0,0 @@
|
||||
{
|
||||
"name": "react-flow-examples",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"dagre": "^0.8.5",
|
||||
"localforage": "^1.10.0",
|
||||
"react": "file:../node_modules/react",
|
||||
"react-dom": "file:../node_modules/react-dom",
|
||||
"react-flow-renderer": "file:../",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/dagre": "^0.7.47",
|
||||
"@types/localforage": "0.0.34",
|
||||
"@types/react": "file:../node_modules/@types/react",
|
||||
"@types/react-dom": "file:../node_modules/@types/react-dom",
|
||||
"@types/react-router-dom": "^5.3.3"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
/* /index.html 200
|
||||
@@ -1,12 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<title>React Flow Examples</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,3 +0,0 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
@@ -1,25 +0,0 @@
|
||||
import React, { DragEvent } from 'react';
|
||||
|
||||
const onDragStart = (event: DragEvent, nodeType: string) => {
|
||||
event.dataTransfer.setData('application/reactflow', nodeType);
|
||||
event.dataTransfer.effectAllowed = 'move';
|
||||
};
|
||||
|
||||
const Sidebar = () => {
|
||||
return (
|
||||
<aside>
|
||||
<div className="description">You can drag these nodes to the pane on the left.</div>
|
||||
<div className="react-flow__node-input" onDragStart={(event: DragEvent) => onDragStart(event, 'input')} draggable>
|
||||
Input Node
|
||||
</div>
|
||||
<div className="react-flow__node-default" onDragStart={(event: DragEvent) => onDragStart(event, 'default')} draggable>
|
||||
Default Node
|
||||
</div>
|
||||
<div className="react-flow__node-output" onDragStart={(event: DragEvent) => onDragStart(event, 'output')} draggable>
|
||||
Output Node
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
|
||||
export default Sidebar;
|
||||
@@ -1,76 +0,0 @@
|
||||
import { Node, Edge, XYPosition, MarkerType } from 'react-flow-renderer';
|
||||
|
||||
const position: XYPosition = { x: 0, y: 0 };
|
||||
|
||||
const nodes: Node[] = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'input',
|
||||
data: { label: 'input' },
|
||||
position,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
data: { label: 'node 2' },
|
||||
position,
|
||||
},
|
||||
{
|
||||
id: '2a',
|
||||
data: { label: 'node 2a' },
|
||||
position,
|
||||
},
|
||||
{
|
||||
id: '2b',
|
||||
data: { label: 'node 2b' },
|
||||
position,
|
||||
},
|
||||
{
|
||||
id: '2c',
|
||||
data: { label: 'node 2c' },
|
||||
position,
|
||||
},
|
||||
{
|
||||
id: '2d',
|
||||
data: { label: 'node 2d' },
|
||||
position,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
data: { label: 'node 3' },
|
||||
position,
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
data: { label: 'node 4' },
|
||||
position,
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
data: { label: 'node 5' },
|
||||
position,
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
type: 'output',
|
||||
data: { label: 'output' },
|
||||
position,
|
||||
},
|
||||
{ id: '7', type: 'output', data: { label: 'output' }, position: { x: 400, y: 450 } },
|
||||
];
|
||||
|
||||
const edges: Edge[] = [
|
||||
{ id: 'e12', source: '1', target: '2', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } },
|
||||
{ id: 'e13', source: '1', target: '3', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } },
|
||||
{ id: 'e22a', source: '2', target: '2a', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } },
|
||||
{ id: 'e22b', source: '2', target: '2b', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } },
|
||||
{ id: 'e22c', source: '2', target: '2c', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } },
|
||||
{ id: 'e2c2d', source: '2c', target: '2d', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } },
|
||||
|
||||
{ id: 'e45', source: '4', target: '5', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } },
|
||||
{ id: 'e56', source: '5', target: '6', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } },
|
||||
{ id: 'e57', source: '5', target: '7', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } },
|
||||
];
|
||||
|
||||
const nodesAndEdges = { nodes, edges };
|
||||
|
||||
export default nodesAndEdges;
|
||||
@@ -1,13 +0,0 @@
|
||||
.react-flow__example-multiflows {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.react-flow__example-multiflows .react-flow {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.react-flow__example-multiflows .react-flow:first-child {
|
||||
border-right: 2px solid #333;
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
import { MouseEvent } from 'react';
|
||||
import ReactFlow, { addEdge, Node, Connection, Edge, useNodesState, useEdgesState } from 'react-flow-renderer';
|
||||
|
||||
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
|
||||
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
||||
|
||||
const nodesA: Node[] = [
|
||||
{ id: '1a', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 }, className: 'light' },
|
||||
{ id: '2a', data: { label: 'Node 2' }, position: { x: 100, y: 100 }, className: 'light' },
|
||||
{ id: '3a', data: { label: 'Node 3' }, position: { x: 400, y: 100 }, className: 'light' },
|
||||
{ id: '4a', data: { label: 'Node 4' }, position: { x: 400, y: 200 }, className: 'light' },
|
||||
];
|
||||
|
||||
const edgesA: Edge[] = [
|
||||
{ id: 'e1-2', source: '1a', target: '2a' },
|
||||
{ id: 'e1-3', source: '1a', target: '3a' },
|
||||
];
|
||||
|
||||
const nodesB: Node[] = [
|
||||
{ id: 'inputb', type: 'input', data: { label: 'Input' }, position: { x: 300, y: 5 }, className: 'light' },
|
||||
{ id: '1b', data: { label: 'Node 1' }, position: { x: 0, y: 100 }, className: 'light' },
|
||||
{ id: '2b', data: { label: 'Node 2' }, position: { x: 200, y: 100 }, className: 'light' },
|
||||
{ id: '3b', data: { label: 'Node 3' }, position: { x: 400, y: 100 }, className: 'light' },
|
||||
{ id: '4b', data: { label: 'Node 4' }, position: { x: 600, y: 100 }, className: 'light' },
|
||||
];
|
||||
|
||||
const edgesB: Edge[] = [
|
||||
{ id: 'e1b', source: 'inputb', target: '1b' },
|
||||
{ id: 'e2b', source: 'inputb', target: '2b' },
|
||||
{ id: 'e3b', source: 'inputb', target: '3b' },
|
||||
{ id: 'e4b', source: 'inputb', target: '4b' },
|
||||
];
|
||||
|
||||
const BasicFlow = () => {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(nodesA);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(edgesA);
|
||||
|
||||
const onConnect = (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds));
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onNodeClick={onNodeClick}
|
||||
onConnect={onConnect}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
>
|
||||
<div style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}>
|
||||
<button
|
||||
onClick={() => {
|
||||
setNodes(nodesA);
|
||||
setEdges(edgesA);
|
||||
}}
|
||||
style={{ marginRight: 5 }}
|
||||
>
|
||||
flow a
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setNodes(nodesB);
|
||||
setEdges(edgesB);
|
||||
}}
|
||||
>
|
||||
flow b
|
||||
</button>
|
||||
</div>
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default BasicFlow;
|
||||
@@ -1,31 +0,0 @@
|
||||
.validationflow .react-flow__node {
|
||||
width: 150px;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
color: #555;
|
||||
border: 1px solid #ddd;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.validationflow .react-flow__node-customnode {
|
||||
background: #e6e6e9;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.react-flow__node-custominput .react-flow__handle {
|
||||
background: #e6e6e9;
|
||||
}
|
||||
|
||||
.validationflow .react-flow__node-custominput {
|
||||
background: #fff;
|
||||
|
||||
}
|
||||
|
||||
.validationflow .react-flow__handle-connecting {
|
||||
background: #ff6060;
|
||||
}
|
||||
|
||||
.validationflow .react-flow__handle-valid {
|
||||
background: #55dd99;
|
||||
}
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
/// <reference types="react-scripts" />
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["src_oldapi"]
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
@@ -0,0 +1,19 @@
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
baseUrl: 'http://localhost:3000',
|
||||
viewportWidth: 1280,
|
||||
viewportHeight: 720,
|
||||
video: false,
|
||||
screenshotOnRunFailure: false,
|
||||
},
|
||||
component: {
|
||||
devServer: {
|
||||
framework: 'react',
|
||||
bundler: 'vite',
|
||||
},
|
||||
screenshotOnRunFailure: false,
|
||||
video: false,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import ReactFlow, { Edge, useEdges } from 'reactflow';
|
||||
|
||||
import { nodes as initialNodes, edges as initialEdges } from '../../fixtures/simpleflow';
|
||||
|
||||
describe('useEdges.cy.tsx', () => {
|
||||
it('handles edges', () => {
|
||||
const onChangeSpy = cy.spy().as('onChangeSpy');
|
||||
|
||||
cy.mount(
|
||||
<ReactFlow nodes={initialNodes} edges={initialEdges}>
|
||||
<HookHelperComponent onChange={onChangeSpy} />
|
||||
</ReactFlow>
|
||||
);
|
||||
|
||||
cy.get('@onChangeSpy').should('have.been.calledWith', []);
|
||||
cy.get('@onChangeSpy').should('have.been.calledWith', initialEdges);
|
||||
});
|
||||
|
||||
it('handles defaultEdges', () => {
|
||||
const onChangeSpy = cy.spy().as('onChangeSpy');
|
||||
|
||||
cy.mount(
|
||||
<ReactFlow defaultNodes={initialNodes} defaultEdges={initialEdges}>
|
||||
<HookHelperComponent onChange={onChangeSpy} />
|
||||
</ReactFlow>
|
||||
);
|
||||
|
||||
cy.get('@onChangeSpy').should('have.been.calledWith', []);
|
||||
cy.get('@onChangeSpy').should('have.been.calledWith', initialEdges);
|
||||
});
|
||||
});
|
||||
|
||||
const HookHelperComponent = ({ onChange }: { onChange: (edges: Edge[]) => void }) => {
|
||||
const edges = useEdges();
|
||||
|
||||
useEffect(() => {
|
||||
onChange(edges);
|
||||
}, [edges]);
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
import { useEffect } from 'react';
|
||||
import ReactFlow, { Node, useNodes } from 'reactflow';
|
||||
|
||||
import { nodes } from '../../fixtures/simpleflow';
|
||||
|
||||
const nodeDimensions = {
|
||||
width: 100,
|
||||
height: 50,
|
||||
};
|
||||
|
||||
const initialNodes: Node[] = nodes.map((n) => ({
|
||||
...n,
|
||||
style: nodeDimensions,
|
||||
}));
|
||||
|
||||
const expectedNodes: Node[] = initialNodes.map((n) => ({
|
||||
...n,
|
||||
positionAbsolute: n.position,
|
||||
...nodeDimensions,
|
||||
}));
|
||||
|
||||
describe('useNodes.cy.tsx', () => {
|
||||
it('handles nodes', () => {
|
||||
const onChangeSpy = cy.spy().as('onChangeSpy');
|
||||
|
||||
cy.mount(
|
||||
<ReactFlow nodes={initialNodes}>
|
||||
<HookHelperComponent onChange={onChangeSpy} />
|
||||
</ReactFlow>
|
||||
);
|
||||
|
||||
cy.get('@onChangeSpy').should('have.been.calledWith', []);
|
||||
cy.get('@onChangeSpy').should('have.been.calledWith', expectedNodes);
|
||||
});
|
||||
|
||||
it('handles defaultNodes', () => {
|
||||
const onChangeSpy = cy.spy().as('onChangeSpy');
|
||||
|
||||
cy.mount(
|
||||
<ReactFlow defaultNodes={initialNodes}>
|
||||
<HookHelperComponent onChange={onChangeSpy} />
|
||||
</ReactFlow>
|
||||
);
|
||||
|
||||
cy.get('@onChangeSpy').should('have.been.calledWith', []);
|
||||
cy.get('@onChangeSpy').should('have.been.calledWith', expectedNodes);
|
||||
});
|
||||
});
|
||||
|
||||
const HookHelperComponent = ({ onChange }: { onChange: (nodes: Node[]) => void }) => {
|
||||
const nodes = useNodes();
|
||||
|
||||
useEffect(() => {
|
||||
onChange(nodes);
|
||||
}, [nodes]);
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
import ReactFlow, { useNodesInitialized } from 'reactflow';
|
||||
|
||||
import { nodes } from '../../fixtures/simpleflow';
|
||||
import ControlledFlow from '../../support/ControlledFlow';
|
||||
|
||||
describe('useNodesInitialized.cy.tsx', () => {
|
||||
it('returns false for no nodes', () => {
|
||||
const initSpy = cy.spy().as('initSpy');
|
||||
|
||||
cy.mount(
|
||||
<ReactFlow nodes={[]}>
|
||||
<HookHelperComponent onChange={initSpy} />
|
||||
</ReactFlow>
|
||||
);
|
||||
|
||||
// cy.get('@initSpy').should('to.be.calledOnce');
|
||||
cy.get('@initSpy').should('have.been.calledWith', false);
|
||||
});
|
||||
|
||||
it('returns false without onNodesChange', () => {
|
||||
const initSpy = cy.spy().as('initSpy');
|
||||
|
||||
cy.mount(
|
||||
<ReactFlow nodes={nodes}>
|
||||
<HookHelperComponent onChange={initSpy} />
|
||||
</ReactFlow>
|
||||
);
|
||||
|
||||
cy.get('@initSpy').should('to.be.calledOnce');
|
||||
cy.get('@initSpy').should('have.be.calledWith', false);
|
||||
});
|
||||
|
||||
it('returns true for defaultNodes', () => {
|
||||
const initSpy = cy.spy().as('initSpy');
|
||||
|
||||
cy.mount(
|
||||
<ReactFlow defaultNodes={nodes}>
|
||||
<HookHelperComponent onChange={initSpy} />
|
||||
</ReactFlow>
|
||||
);
|
||||
|
||||
cy.get('@initSpy').should('have.been.calledWith', false);
|
||||
cy.get('@initSpy').should('have.been.calledWith', true);
|
||||
});
|
||||
|
||||
it('returns true for nodes + onNodesChange', () => {
|
||||
const initSpy = cy.spy().as('initSpy');
|
||||
|
||||
cy.mount(
|
||||
<ControlledFlow initialNodes={nodes}>
|
||||
<HookHelperComponent onChange={initSpy} />
|
||||
</ControlledFlow>
|
||||
);
|
||||
|
||||
cy.get('@initSpy').should('have.been.calledWith', false);
|
||||
cy.get('@initSpy').should('have.been.calledWith', true);
|
||||
});
|
||||
});
|
||||
|
||||
// test specific helpers
|
||||
|
||||
function HookHelperComponent({ onChange }: { onChange: (init: boolean) => void }) {
|
||||
const initialized = useNodesInitialized();
|
||||
|
||||
onChange(initialized);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import ReactFlow, { useOnViewportChange, Viewport } from 'reactflow';
|
||||
|
||||
describe('useOnViewportChange.cy.tsx', () => {
|
||||
it('listen to viewport drag', () => {
|
||||
const onStartSpy = cy.spy().as('onStartSpy');
|
||||
const onChangeSpy = cy.spy().as('onChangeSpy');
|
||||
const onEndSpy = cy.spy().as('onEndSpy');
|
||||
|
||||
cy.mount(
|
||||
<ReactFlow>
|
||||
<HookHelperComponent onStart={onStartSpy} onChange={onChangeSpy} onEnd={onEndSpy} />
|
||||
</ReactFlow>
|
||||
);
|
||||
|
||||
cy.dragPane({ from: { x: 50, y: 50 }, to: { x: 50, y: 100 } }).then(() => {
|
||||
cy.get('@onStartSpy').should('have.been.calledWith', { x: 0, y: 0, zoom: 1 });
|
||||
cy.get('@onChangeSpy').should('have.been.calledWith', { x: 0, y: 50, zoom: 1 });
|
||||
cy.get('@onEndSpy').should('have.been.calledWith', { x: 0, y: 50, zoom: 1 });
|
||||
});
|
||||
});
|
||||
|
||||
it('handles zoom in', () => {
|
||||
const onStartSpy = cy.spy().as('onStartSpy');
|
||||
const onChangeSpy = cy.spy().as('onChangeSpy');
|
||||
const onEndSpy = cy.spy().as('onEndSpy');
|
||||
|
||||
cy.mount(
|
||||
<ReactFlow>
|
||||
<HookHelperComponent onStart={onStartSpy} onChange={onChangeSpy} onEnd={onEndSpy} />
|
||||
</ReactFlow>
|
||||
);
|
||||
|
||||
cy.zoomPane(-200).then(() => {
|
||||
cy.get('@onStartSpy').should('have.been.callCount', 1);
|
||||
cy.get('@onChangeSpy').should('have.been.callCount', 1);
|
||||
cy.get('@onEndSpy').should('have.been.callCount', 1);
|
||||
|
||||
expect(getLatestViewport(onChangeSpy).zoom).to.be.gt(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('handles zoom out', () => {
|
||||
const onStartSpy = cy.spy().as('onStartSpy');
|
||||
const onChangeSpy = cy.spy().as('onChangeSpy');
|
||||
const onEndSpy = cy.spy().as('onEndSpy');
|
||||
|
||||
cy.mount(
|
||||
<ReactFlow>
|
||||
<HookHelperComponent onStart={onStartSpy} onChange={onChangeSpy} onEnd={onEndSpy} />
|
||||
</ReactFlow>
|
||||
);
|
||||
|
||||
cy.zoomPane(200).then(() => {
|
||||
cy.get('@onStartSpy').should('have.been.callCount', 1);
|
||||
cy.get('@onChangeSpy').should('have.been.callCount', 1);
|
||||
cy.get('@onEndSpy').should('have.been.callCount', 1);
|
||||
|
||||
expect(getLatestViewport(onChangeSpy).zoom).to.be.lt(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('handles default viewport', () => {
|
||||
const defaultViewport = { x: 100, y: 100, zoom: 0.5 };
|
||||
const onStartSpy = cy.spy().as('onStartSpy');
|
||||
|
||||
cy.mount(
|
||||
<ReactFlow defaultViewport={defaultViewport}>
|
||||
<HookHelperComponent onStart={onStartSpy} />
|
||||
</ReactFlow>
|
||||
);
|
||||
|
||||
cy.dragPane({ from: { x: 50, y: 50 }, to: { x: 50, y: 100 } }).then(() => {
|
||||
cy.get('@onStartSpy').should('have.been.calledWith', defaultViewport);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// test specific helpers
|
||||
|
||||
function HookHelperComponent({
|
||||
onStart,
|
||||
onChange,
|
||||
onEnd,
|
||||
}: {
|
||||
onStart?: (viewport: Viewport) => void;
|
||||
onChange?: (viewport: Viewport) => void;
|
||||
onEnd?: (viewport: Viewport) => void;
|
||||
}) {
|
||||
useOnViewportChange({ onStart, onChange, onEnd });
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getLatestViewport(onChangeSpy: any) {
|
||||
return onChangeSpy.lastCall.args[0] as unknown as Viewport;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import ReactFlow, { useViewport, Viewport } from 'reactflow';
|
||||
|
||||
describe('useViewport.cy.tsx', () => {
|
||||
it('handles drag', () => {
|
||||
const onChangeSpy = cy.spy().as('onChangeSpy');
|
||||
|
||||
cy.mount(
|
||||
<ReactFlow>
|
||||
<HookHelperComponent onChange={onChangeSpy} />
|
||||
</ReactFlow>
|
||||
);
|
||||
|
||||
cy.get('@onChangeSpy').should('have.been.calledWith', { x: 0, y: 0, zoom: 1 });
|
||||
|
||||
cy.dragPane({ from: { x: 50, y: 0 }, to: { x: 50, y: 400 } }).then(() => {
|
||||
cy.get('@onChangeSpy').should('have.been.callCount', 2);
|
||||
cy.get('@onChangeSpy').should('have.been.calledWith', { x: 0, y: 0, zoom: 1 });
|
||||
|
||||
expect(getLatestViewport(onChangeSpy)).to.eql({ x: 0, y: 400, zoom: 1 });
|
||||
});
|
||||
});
|
||||
|
||||
it('handles zoom in', () => {
|
||||
const onChangeSpy = cy.spy().as('onChangeSpy');
|
||||
|
||||
cy.mount(
|
||||
<ReactFlow>
|
||||
<HookHelperComponent onChange={onChangeSpy} />
|
||||
</ReactFlow>
|
||||
);
|
||||
|
||||
cy.zoomPane(-200).then(() => {
|
||||
cy.get('@onChangeSpy').should('have.been.callCount', 2);
|
||||
expect(getLatestViewport(onChangeSpy).zoom).to.be.gt(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('handles zoom out', () => {
|
||||
const onChangeSpy = cy.spy().as('onChangeSpy');
|
||||
|
||||
cy.mount(
|
||||
<ReactFlow>
|
||||
<HookHelperComponent onChange={onChangeSpy} />
|
||||
</ReactFlow>
|
||||
);
|
||||
|
||||
cy.zoomPane(200).then(() => {
|
||||
cy.get('@onChangeSpy').should('have.been.callCount', 2);
|
||||
expect(getLatestViewport(onChangeSpy).zoom).to.be.lt(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('handles default viewport', () => {
|
||||
const defaultViewport = { x: 100, y: 100, zoom: 0.5 };
|
||||
const onChangeSpy = cy.spy().as('onChangeSpy');
|
||||
|
||||
cy.mount(
|
||||
<ReactFlow defaultViewport={defaultViewport}>
|
||||
<HookHelperComponent onChange={onChangeSpy} />
|
||||
</ReactFlow>
|
||||
);
|
||||
|
||||
cy.get('@onChangeSpy').should('have.been.calledWith', defaultViewport);
|
||||
});
|
||||
});
|
||||
|
||||
// test specific helpers
|
||||
|
||||
function HookHelperComponent({ onChange }: { onChange: (vp: Viewport) => void }) {
|
||||
const viewport = useViewport();
|
||||
|
||||
onChange(viewport);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getLatestViewport(onChangeSpy: any) {
|
||||
return onChangeSpy.lastCall.args[0] as unknown as Viewport;
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
import ReactFlow, { EdgeProps } from 'reactflow';
|
||||
|
||||
import ControlledFlow from '../../support/ControlledFlow';
|
||||
import * as simpleflow from '../../fixtures/simpleflow';
|
||||
|
||||
describe('<ReactFlow />: Basic Props', () => {
|
||||
describe('uses defaultNodes and defaultEdges', () => {
|
||||
beforeEach(() => {
|
||||
cy.mount(<ReactFlow defaultNodes={simpleflow.nodes} defaultEdges={simpleflow.edges} />);
|
||||
});
|
||||
|
||||
it('mounts nodes and edges', () => {
|
||||
cy.get('.react-flow__node').should('have.length', simpleflow.nodes.length);
|
||||
cy.get('.react-flow__edge').should('have.length', simpleflow.edges.length);
|
||||
});
|
||||
|
||||
it('drags a node', () => {
|
||||
const styleBeforeDrag = Cypress.$('.react-flow__node:first').css('transform');
|
||||
|
||||
cy.drag('.react-flow__node:first', { x: 200, y: 25 }).then(($el: JQuery<HTMLElement>) => {
|
||||
const styleAfterDrag = $el.css('transform');
|
||||
expect(styleBeforeDrag).to.not.equal(styleAfterDrag);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('uses nodes and edges', () => {
|
||||
beforeEach(() => {
|
||||
cy.mount(
|
||||
<ControlledFlow
|
||||
addOnNodeChangeHandler={false}
|
||||
initialNodes={simpleflow.nodes}
|
||||
initialEdges={simpleflow.edges}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
it('mounts nodes and edges', () => {
|
||||
cy.get('.react-flow__node').should('have.length', simpleflow.nodes.length);
|
||||
cy.get('.react-flow__edge').should('have.length', simpleflow.edges.length);
|
||||
});
|
||||
|
||||
it('can not drag a node', () => {
|
||||
const styleBeforeDrag = Cypress.$('.react-flow__node:first').css('transform');
|
||||
|
||||
cy.drag('.react-flow__node:first', { x: 200, y: 25 }).then(($el: JQuery<HTMLElement>) => {
|
||||
const styleAfterDrag = $el.css('transform');
|
||||
expect(styleBeforeDrag).to.equal(styleAfterDrag);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('uses onNodesChange handler', () => {
|
||||
beforeEach(() => {
|
||||
cy.mount(<ControlledFlow addOnConnectHandler={false} initialNodes={simpleflow.nodes} />);
|
||||
});
|
||||
|
||||
it('mounts nodes', () => {
|
||||
cy.get('.react-flow__node').should('have.length', simpleflow.nodes.length);
|
||||
cy.get('.react-flow__edge').should('have.length', 0);
|
||||
});
|
||||
|
||||
it('drags a node', () => {
|
||||
const styleBeforeDrag = Cypress.$('.react-flow__node:first').css('transform');
|
||||
|
||||
cy.drag('.react-flow__node:first', { x: 200, y: 25 }).then(($el: JQuery<HTMLElement>) => {
|
||||
const styleAfterDrag = $el.css('transform');
|
||||
expect(styleBeforeDrag).to.not.equal(styleAfterDrag);
|
||||
});
|
||||
});
|
||||
|
||||
it('can not connect nodes', () => {
|
||||
cy.get('.react-flow__node').first().find('.react-flow__handle.source').trigger('mousedown', { button: 0 });
|
||||
|
||||
cy.get('.react-flow__node')
|
||||
.last()
|
||||
.find('.react-flow__handle.target')
|
||||
.trigger('mousemove')
|
||||
.trigger('mouseup', { force: true });
|
||||
|
||||
cy.get('.react-flow__edge').should('have.length', 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('uses onEdgeChange handler', () => {
|
||||
beforeEach(() => {
|
||||
cy.mount(<ControlledFlow initialNodes={simpleflow.nodes} initialEdges={simpleflow.edges} />);
|
||||
});
|
||||
|
||||
it('mounts nodes and edges', () => {
|
||||
cy.get('.react-flow__node').should('have.length', simpleflow.nodes.length);
|
||||
cy.get('.react-flow__edge').should('have.length', simpleflow.edges.length);
|
||||
});
|
||||
|
||||
it('selects an edge', () => {
|
||||
cy.get('.react-flow__edge').first().click().should('have.class', 'selected');
|
||||
});
|
||||
});
|
||||
|
||||
describe('uses onConnect handlers', () => {
|
||||
beforeEach(() => {
|
||||
cy.mount(<ControlledFlow initialNodes={simpleflow.nodes} />);
|
||||
});
|
||||
|
||||
it('mounts nodes', () => {
|
||||
cy.get('.react-flow__node').should('have.length', simpleflow.nodes.length);
|
||||
cy.get('.react-flow__edge').should('have.length', 0);
|
||||
});
|
||||
|
||||
it('connects nodes', () => {
|
||||
cy.get('.react-flow__node').first().find('.react-flow__handle.source').trigger('mousedown', { button: 0 });
|
||||
|
||||
cy.get('.react-flow__node')
|
||||
.last()
|
||||
.find('.react-flow__handle.target')
|
||||
.trigger('mousemove')
|
||||
.trigger('mouseup', { force: true });
|
||||
|
||||
cy.get('.react-flow__edge').should('have.length', 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('uses nodeTypes', () => {
|
||||
it('renders custom nodes', () => {
|
||||
const nodeTypes = {
|
||||
custom: () => <div className="customnode">custom node</div>,
|
||||
};
|
||||
|
||||
const initialNodes = simpleflow.nodes.map((n) => ({
|
||||
...n,
|
||||
type: 'custom',
|
||||
}));
|
||||
|
||||
cy.mount(<ControlledFlow nodeTypes={nodeTypes} initialNodes={initialNodes} />);
|
||||
|
||||
cy.get('.react-flow__node-custom').should('have.length', simpleflow.nodes.length);
|
||||
cy.get('.react-flow').contains('custom node');
|
||||
});
|
||||
|
||||
it('tries invalid node type - should fallback to default', () => {
|
||||
const initialNodes = simpleflow.nodes.map((n) => ({
|
||||
...n,
|
||||
type: 'invalid',
|
||||
}));
|
||||
|
||||
cy.mount(<ControlledFlow initialNodes={initialNodes} />);
|
||||
|
||||
cy.get('.react-flow__node-invalid').should('have.length', 0);
|
||||
cy.get('.react-flow__node-default').should('have.length', simpleflow.nodes.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe('uses edgeTypes', () => {
|
||||
it('renders custom edge', () => {
|
||||
const edgeTypes = {
|
||||
custom: ({ sourceX, sourceY, targetX, targetY }: EdgeProps) => (
|
||||
<path d={`M${sourceX} ${sourceY} L${targetX} ${targetY}`} stroke="black" />
|
||||
),
|
||||
};
|
||||
|
||||
const initialEdges = simpleflow.edges.map((e) => ({
|
||||
...e,
|
||||
type: 'custom',
|
||||
}));
|
||||
|
||||
cy.mount(<ControlledFlow edgeTypes={edgeTypes} initialNodes={simpleflow.nodes} initialEdges={initialEdges} />);
|
||||
|
||||
cy.get('.react-flow__edge-custom').should('have.length', simpleflow.edges.length);
|
||||
});
|
||||
|
||||
it('tries invalid edge type - should fallback to default', () => {
|
||||
const initialEdges = simpleflow.edges.map((e) => ({
|
||||
...e,
|
||||
type: 'invalid',
|
||||
}));
|
||||
|
||||
cy.mount(<ControlledFlow initialNodes={simpleflow.nodes} initialEdges={initialEdges} />);
|
||||
|
||||
cy.get('.react-flow__edge-invalid').should('have.length', 0);
|
||||
cy.get('.react-flow__edge-default').should('have.length', simpleflow.edges.length);
|
||||
});
|
||||
});
|
||||
|
||||
it('uses style', () => {
|
||||
const styles = {
|
||||
backgroundColor: 'red',
|
||||
};
|
||||
|
||||
cy.mount(<ControlledFlow style={styles} />);
|
||||
cy.get('.react-flow').should('have.css', 'background-color', 'rgb(255, 0, 0)');
|
||||
});
|
||||
|
||||
it('uses classname', () => {
|
||||
cy.mount(<ControlledFlow className="custom" />);
|
||||
cy.get('.react-flow').should('have.class', 'custom');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,210 @@
|
||||
import { skipOn } from '@cypress/skip-test';
|
||||
|
||||
import { nodes, edges } from '../../fixtures/simpleflow';
|
||||
import ControlledFlow from '../../support/ControlledFlow';
|
||||
|
||||
describe('<ReactFlow />: Event handlers', () => {
|
||||
describe('Node event handlers', () => {
|
||||
it('handles onInit', () => {
|
||||
const onInitSpy = cy.spy().as('onInitSpy');
|
||||
|
||||
cy.mount(<ControlledFlow initialNodes={nodes} initialEdges={edges} onInit={onInitSpy} />)
|
||||
.wait(100)
|
||||
.then(() => {
|
||||
expect(onInitSpy.callCount).to.be.eq(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('handles onNodeClick', () => {
|
||||
const onNodeClickSpy = cy.spy().as('onNodeClickSpy');
|
||||
|
||||
cy.mount(<ControlledFlow initialNodes={nodes} initialEdges={edges} onNodeClick={onNodeClickSpy} />).then(() => {
|
||||
expect(onNodeClickSpy.callCount).to.be.eq(0);
|
||||
cy.get('.react-flow__node:first')
|
||||
.click()
|
||||
.then(() => {
|
||||
expect(onNodeClickSpy.callCount).to.be.eq(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('handles onNodeDrag handlers', () => {
|
||||
const onNodeDragStart = cy.spy().as('onNodeDragStart');
|
||||
const onNodeDrag = cy.spy().as('onNodeDrag');
|
||||
const onNodeDragStop = cy.spy().as('onNodeDragStop');
|
||||
|
||||
cy.mount(
|
||||
<ControlledFlow
|
||||
initialNodes={nodes}
|
||||
initialEdges={edges}
|
||||
onNodeDragStart={onNodeDragStart}
|
||||
onNodeDrag={onNodeDrag}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
/>
|
||||
).then(() => {
|
||||
expect(onNodeDragStart.callCount).to.be.eq(0);
|
||||
expect(onNodeDrag.callCount).to.be.eq(0);
|
||||
expect(onNodeDragStop.callCount).to.be.eq(0);
|
||||
|
||||
cy.drag('.react-flow__node:first', { x: 200, y: 0 }).then(() => {
|
||||
expect(onNodeDragStart.callCount).to.be.gt(0);
|
||||
expect(onNodeDrag.callCount).to.be.gt(0);
|
||||
expect(onNodeDragStop.callCount).to.be.gt(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('handles onNodeMouse handlers', () => {
|
||||
const onNodeMouseEnter = cy.spy().as('onNodeMouseEnter');
|
||||
const onNodeMouseMove = cy.spy().as('onNodeMouseMove');
|
||||
const onNodeMouseLeave = cy.spy().as('onNodeMouseLeave');
|
||||
const onNodeContextMenu = cy.spy().as('onNodeContextMenu');
|
||||
const onNodeDoubleClick = cy.spy().as('onNodeDoubleClick');
|
||||
|
||||
cy.mount(
|
||||
<ControlledFlow
|
||||
initialNodes={nodes}
|
||||
initialEdges={edges}
|
||||
onNodeMouseEnter={onNodeMouseEnter}
|
||||
onNodeMouseMove={onNodeMouseMove}
|
||||
onNodeMouseLeave={onNodeMouseLeave}
|
||||
onNodeContextMenu={onNodeContextMenu}
|
||||
onNodeDoubleClick={onNodeDoubleClick}
|
||||
/>
|
||||
)
|
||||
.wait(200)
|
||||
.then(() => {
|
||||
expect(onNodeMouseEnter.callCount).to.be.eq(0);
|
||||
expect(onNodeMouseMove.callCount).to.be.eq(0);
|
||||
expect(onNodeMouseLeave.callCount).to.be.eq(0);
|
||||
expect(onNodeContextMenu.callCount).to.be.eq(0);
|
||||
expect(onNodeDoubleClick.callCount).to.be.eq(0);
|
||||
|
||||
const node = cy.get('.react-flow__node').contains('Node 1');
|
||||
|
||||
node
|
||||
.rightclick()
|
||||
.dblclick()
|
||||
.then(() => {
|
||||
expect(onNodeContextMenu.callCount).to.be.eq(1);
|
||||
expect(onNodeDoubleClick.callCount).to.be.eq(1);
|
||||
});
|
||||
|
||||
skipOn('firefox');
|
||||
|
||||
node
|
||||
.realHover()
|
||||
.realMouseMove(100, 100)
|
||||
.then(() => {
|
||||
expect(onNodeMouseEnter.callCount).to.be.gt(0);
|
||||
expect(onNodeMouseMove.callCount).to.be.gt(0);
|
||||
expect(onNodeMouseLeave.callCount).to.be.gt(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge event handlers', () => {
|
||||
it('handles onEdgeClick', () => {
|
||||
const onEdgeClick = cy.spy().as('onEdgeClick');
|
||||
|
||||
cy.mount(<ControlledFlow initialNodes={nodes} initialEdges={edges} onEdgeClick={onEdgeClick} />).then(() => {
|
||||
expect(onEdgeClick.callCount).to.be.eq(0);
|
||||
cy.get('.react-flow__edge:first')
|
||||
.click()
|
||||
.then(() => {
|
||||
expect(onEdgeClick.callCount).to.be.eq(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('handles onEdgeMouse handlers', () => {
|
||||
const onEdgeMouseEnter = cy.spy().as('onEdgeMouseEnter');
|
||||
const onEdgeMouseMove = cy.spy().as('onEdgeMouseMove');
|
||||
const onEdgeMouseLeave = cy.spy().as('onEdgeMouseLeave');
|
||||
const onEdgeContextMenu = cy.spy().as('onEdgeContextMenu');
|
||||
const onEdgeDoubleClick = cy.spy().as('onEdgedoubleClick');
|
||||
|
||||
cy.mount(
|
||||
<ControlledFlow
|
||||
initialNodes={nodes}
|
||||
initialEdges={edges}
|
||||
onEdgeMouseEnter={onEdgeMouseEnter}
|
||||
onEdgeMouseMove={onEdgeMouseMove}
|
||||
onEdgeMouseLeave={onEdgeMouseLeave}
|
||||
onEdgeContextMenu={onEdgeContextMenu}
|
||||
onEdgeDoubleClick={onEdgeDoubleClick}
|
||||
/>
|
||||
)
|
||||
.wait(200)
|
||||
.then(() => {
|
||||
expect(onEdgeMouseEnter.callCount).to.be.eq(0);
|
||||
expect(onEdgeMouseMove.callCount).to.be.eq(0);
|
||||
expect(onEdgeMouseLeave.callCount).to.be.eq(0);
|
||||
expect(onEdgeContextMenu.callCount).to.be.eq(0);
|
||||
expect(onEdgeDoubleClick.callCount).to.be.eq(0);
|
||||
|
||||
const edge = cy.get('.react-flow__edge:first');
|
||||
|
||||
edge
|
||||
.rightclick()
|
||||
.dblclick()
|
||||
.then(() => {
|
||||
expect(onEdgeContextMenu.callCount).to.be.eq(1);
|
||||
});
|
||||
|
||||
skipOn('firefox');
|
||||
|
||||
edge
|
||||
.realHover()
|
||||
.realMouseMove(100, 100)
|
||||
.then(() => {
|
||||
expect(onEdgeMouseEnter.callCount).to.be.gt(0);
|
||||
expect(onEdgeMouseMove.callCount).to.be.gt(0);
|
||||
expect(onEdgeMouseLeave.callCount).to.be.gt(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Pane event handlers', () => {
|
||||
it('handles onMove handlers', () => {
|
||||
const onMoveStart = cy.spy().as('onMoveStart');
|
||||
const onMove = cy.spy().as('onMove');
|
||||
const onMoveEnd = cy.spy().as('onMoveEnd');
|
||||
|
||||
cy.mount(<ControlledFlow onMoveStart={onMoveStart} onMove={onMove} onMoveEnd={onMoveEnd} />).then(() => {
|
||||
expect(onMoveStart.callCount).to.be.eq(0);
|
||||
expect(onMove.callCount).to.be.eq(0);
|
||||
expect(onMoveEnd.callCount).to.be.eq(0);
|
||||
|
||||
cy.dragPane({ from: { x: 10, y: 200 }, to: { x: 100, y: 200 } })
|
||||
.wait(100)
|
||||
.then(() => {
|
||||
expect(onMoveStart.callCount).to.be.eq(1);
|
||||
expect(onMove.callCount).to.be.gt(0);
|
||||
expect(onMoveEnd.callCount).to.be.eq(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('handles click handlers', () => {
|
||||
const onPaneClick = cy.spy().as('onPaneClick');
|
||||
const onPaneContextMenu = cy.spy().as('onPaneContextMenu');
|
||||
|
||||
cy.mount(<ControlledFlow onPaneClick={onPaneClick} onPaneContextMenu={onPaneContextMenu} />).then(() => {
|
||||
expect(onPaneClick.callCount).to.be.eq(0);
|
||||
expect(onPaneContextMenu.callCount).to.be.eq(0);
|
||||
|
||||
cy.get('.react-flow__pane')
|
||||
.rightclick()
|
||||
.click()
|
||||
.wait(100)
|
||||
.then(() => {
|
||||
expect(onPaneClick.callCount).to.be.eq(1);
|
||||
expect(onPaneContextMenu.callCount).to.be.eq(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import ReactFlow from 'reactflow';
|
||||
|
||||
import { nodes, edges } from '../../fixtures/simpleflow';
|
||||
|
||||
describe('<ReactFlow />: Uncontrolled Flow', () => {
|
||||
beforeEach(() => {
|
||||
cy.mount(<ReactFlow defaultNodes={nodes} defaultEdges={edges} />);
|
||||
});
|
||||
|
||||
it('mounts nodes and edges', () => {
|
||||
cy.get('.react-flow__node').should('have.length', nodes.length);
|
||||
cy.get('.react-flow__edge').should('have.length', edges.length);
|
||||
});
|
||||
|
||||
it('selects a node', () => {
|
||||
cy.get('.react-flow__node').first().click().should('have.class', 'selected');
|
||||
cy.get('.react-flow__pane').click();
|
||||
cy.get('.react-flow__node').first().should('not.have.class', 'selected');
|
||||
});
|
||||
|
||||
it('drags a node', () => {
|
||||
const styleBeforeDrag = Cypress.$('.react-flow__node:first').css('transform');
|
||||
|
||||
cy.drag('.react-flow__node:first', { x: 200, y: 25 }).then(($el: JQuery<HTMLElement>) => {
|
||||
const styleAfterDrag = $el.css('transform');
|
||||
expect(styleBeforeDrag).to.not.equal(styleAfterDrag);
|
||||
});
|
||||
});
|
||||
|
||||
it('selects an edge', () => {
|
||||
cy.get('.react-flow__edge').first().click().should('have.class', 'selected');
|
||||
cy.get('.react-flow__pane').click();
|
||||
cy.get('.react-flow__edge').first().should('not.have.class', 'selected');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,312 @@
|
||||
import ReactFlow, { ReactFlowProps, Viewport, useViewport, SnapGrid, CoordinateExtent, Node } from 'reactflow';
|
||||
|
||||
import ControlledFlow from '../../support/ControlledFlow';
|
||||
import * as simpleflow from '../../fixtures/simpleflow';
|
||||
|
||||
const nodesOutsideOfView = [
|
||||
{
|
||||
...simpleflow.nodes[0],
|
||||
position: {
|
||||
x: -500,
|
||||
y: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
...simpleflow.nodes[1],
|
||||
position: {
|
||||
x: 500,
|
||||
y: 0,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('<ReactFlow />: View Props', () => {
|
||||
it('uses fitView', () => {
|
||||
cy.mount(<Comp nodes={nodesOutsideOfView} fitView minZoom={0.2} />).wait(200);
|
||||
cy.get('.react-flow__node:first').isWithinViewport();
|
||||
});
|
||||
|
||||
it('uses fitViewOptions', () => {
|
||||
const fitViewOptions = { padding: 0.5 };
|
||||
|
||||
cy.mount(<ReactFlow nodes={[nodesOutsideOfView[0]]} fitView fitViewOptions={fitViewOptions} />).wait(200);
|
||||
cy.get('.react-flow__node:first').isWithinViewport();
|
||||
});
|
||||
|
||||
describe('uses minZoom', () => {
|
||||
it('minZoom: 0.2', () => {
|
||||
const onChangeSpy = cy.spy().as('onChangeSpy');
|
||||
|
||||
cy.mount(<Comp minZoom={0.2} onChange={onChangeSpy} />);
|
||||
cy.zoomPane(10000).then(() => {
|
||||
expect(getLatestViewport(onChangeSpy).zoom).to.be.eq(0.2);
|
||||
});
|
||||
});
|
||||
|
||||
it('minZoom: 1', () => {
|
||||
const onChangeSpy = cy.spy().as('onChangeSpy');
|
||||
|
||||
cy.mount(<Comp minZoom={1} onChange={onChangeSpy} />);
|
||||
cy.zoomPane(10000).then(() => {
|
||||
expect(getLatestViewport(onChangeSpy).zoom).to.be.eq(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('sets invalid defaultZoom', () => {
|
||||
const onChangeSpy = cy.spy().as('onChangeSpy');
|
||||
const defaultViewport = { x: 0, y: 0, zoom: 0.2 };
|
||||
|
||||
cy.mount(<Comp minZoom={1} defaultViewport={defaultViewport} onChange={onChangeSpy} />);
|
||||
cy.zoomPane(10000).then(() => {
|
||||
expect(getLatestViewport(onChangeSpy).zoom).to.be.eq(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('uses maxZoom', () => {
|
||||
it('maxZoom: 1', () => {
|
||||
const onChangeSpy = cy.spy().as('onChangeSpy');
|
||||
|
||||
cy.mount(<Comp maxZoom={1} onChange={onChangeSpy} />);
|
||||
cy.zoomPane(-10000).then(() => {
|
||||
expect(getLatestViewport(onChangeSpy).zoom).to.be.eq(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('maxZoom: 2', () => {
|
||||
const onChangeSpy = cy.spy().as('onChangeSpy');
|
||||
|
||||
cy.mount(<Comp maxZoom={2} onChange={onChangeSpy} />);
|
||||
cy.zoomPane(-10000).then(() => {
|
||||
expect(getLatestViewport(onChangeSpy).zoom).to.be.eq(2);
|
||||
});
|
||||
});
|
||||
|
||||
it('sets invalid defaultZoom', () => {
|
||||
const onChangeSpy = cy.spy().as('onChangeSpy');
|
||||
const defaultViewport = { x: 0, y: 0, zoom: 2 };
|
||||
|
||||
cy.mount(<Comp maxZoom={1.5} defaultViewport={defaultViewport} onChange={onChangeSpy} />);
|
||||
cy.zoomPane(-10000).then(() => {
|
||||
expect(getLatestViewport(onChangeSpy).zoom).to.be.eq(1.5);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('uses defaultViewport', () => {
|
||||
it('defaultViewport: { x: 0, y: 0, zoom: 1 }', () => {
|
||||
const onChangeSpy = cy.spy().as('onChangeSpy');
|
||||
const defaultViewport = { x: 0, y: 0, zoom: 1 };
|
||||
|
||||
cy.mount(<Comp nodes={simpleflow.nodes} defaultViewport={defaultViewport} onChange={onChangeSpy} />).then(() => {
|
||||
expect(getLatestViewport(onChangeSpy)).to.be.eql(defaultViewport);
|
||||
});
|
||||
});
|
||||
|
||||
it('defaultViewport: { x: 0, y: 0, zoom: 1.5 }', () => {
|
||||
const onChangeSpy = cy.spy().as('onChangeSpy');
|
||||
const defaultViewport = { x: 0, y: 0, zoom: 1.5 };
|
||||
|
||||
cy.mount(<Comp nodes={simpleflow.nodes} defaultViewport={defaultViewport} onChange={onChangeSpy} />).then(() => {
|
||||
expect(getLatestViewport(onChangeSpy)).to.be.eql(defaultViewport);
|
||||
});
|
||||
});
|
||||
|
||||
it('defaultViewport: { x: 100, y: 100, zoom: 1.5 }', () => {
|
||||
const onChangeSpy = cy.spy().as('onChangeSpy');
|
||||
const defaultViewport = { x: 100, y: 100, zoom: 1.5 };
|
||||
|
||||
cy.mount(<Comp nodes={simpleflow.nodes} defaultViewport={defaultViewport} onChange={onChangeSpy} />).then(() => {
|
||||
expect(getLatestViewport(onChangeSpy)).to.be.eql(defaultViewport);
|
||||
});
|
||||
});
|
||||
|
||||
it('defaultViewport: invalid { x: 0, y: 0, zoom: -1 }', () => {
|
||||
const onChangeSpy = cy.spy().as('onChangeSpy');
|
||||
const defaultViewport = { x: 0, y: 0, zoom: -1 };
|
||||
|
||||
cy.mount(<Comp nodes={simpleflow.nodes} defaultViewport={defaultViewport} onChange={onChangeSpy} />).then(() => {
|
||||
// this should be min zoom because zoom is clamped
|
||||
expect(getLatestViewport(onChangeSpy).zoom).to.be.eql(0.5);
|
||||
});
|
||||
});
|
||||
|
||||
it('defaultViewport: invalid { x: 0, y: 0, zoom: 100 }', () => {
|
||||
const onChangeSpy = cy.spy().as('onChangeSpy');
|
||||
const defaultViewport = { x: 0, y: 0, zoom: 100 };
|
||||
|
||||
cy.mount(<Comp nodes={simpleflow.nodes} defaultViewport={defaultViewport} onChange={onChangeSpy} />).then(() => {
|
||||
// this should be max zoom because zoom is clamped
|
||||
expect(getLatestViewport(onChangeSpy).zoom).to.be.eql(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('uses snapGrid and snapToGrid', () => {
|
||||
const snapGrid: SnapGrid = [100, 100];
|
||||
|
||||
cy.mount(<ControlledFlow initialNodes={simpleflow.nodes} snapGrid={snapGrid} snapToGrid />).then(() => {
|
||||
cy.drag('.react-flow__node:first', { x: 125, y: 125 }).then(($el: any) => {
|
||||
const transformAfterDrag = $el.css('transform');
|
||||
const { m41: nodeX, m42: nodeY } = new DOMMatrixReadOnly(transformAfterDrag);
|
||||
expect([nodeX, nodeY]).to.eql(snapGrid);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('uses onlyRenderVisibleElements', () => {
|
||||
it('displays no nodes', () => {
|
||||
cy.mount(<ControlledFlow initialNodes={nodesOutsideOfView} onlyRenderVisibleElements />).then(() => {
|
||||
cy.get('.react-flow__node').should('have.length', 0);
|
||||
});
|
||||
});
|
||||
|
||||
it('displays one node', () => {
|
||||
const nodes = nodesOutsideOfView.map((n) => {
|
||||
if (n.id === '1') {
|
||||
return { ...n, position: { x: 0, y: 0 } };
|
||||
}
|
||||
return n;
|
||||
});
|
||||
cy.mount(<ControlledFlow initialNodes={nodes} onlyRenderVisibleElements />).then(() => {
|
||||
cy.get('.react-flow__node').should('have.length', 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('uses translateExtent', () => {
|
||||
const translateExtent: CoordinateExtent = [
|
||||
[0, 0],
|
||||
[1000, 1000],
|
||||
];
|
||||
|
||||
cy.mount(<ControlledFlow translateExtent={translateExtent} />).then(() => {
|
||||
const transformBeforeDrag = Cypress.$('.react-flow__viewport').css('transform');
|
||||
|
||||
cy.dragPane({ from: { x: 0, y: 0 }, to: { x: 100, y: 100 } }).then(() => {
|
||||
const transformAfterDrag = Cypress.$('.react-flow__viewport').css('transform');
|
||||
expect(transformBeforeDrag).to.eql(transformAfterDrag);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('uses nodeExtent', () => {
|
||||
const nodes: Node[] = [
|
||||
{
|
||||
id: '1',
|
||||
position: { x: 200, y: 200 },
|
||||
data: { label: '1' },
|
||||
},
|
||||
];
|
||||
|
||||
const nodeExtent: CoordinateExtent = [
|
||||
[0, 0],
|
||||
[50, 50],
|
||||
];
|
||||
|
||||
cy.mount(<ControlledFlow nodes={nodes} nodeExtent={nodeExtent} />)
|
||||
.wait(500)
|
||||
.then(() => {
|
||||
const transform = Cypress.$('.react-flow__node:first').css('transform');
|
||||
const { m41: nodeX, m42: nodeY } = new DOMMatrixReadOnly(transform);
|
||||
expect(nodeX).to.equal(50);
|
||||
expect(nodeY).to.equal(50);
|
||||
});
|
||||
});
|
||||
|
||||
describe('uses preventScrolling', () => {
|
||||
function ScrollFlow({ preventScrolling }: { preventScrolling: boolean }) {
|
||||
return (
|
||||
<div className="wrapper" style={{ height: 2000 }}>
|
||||
<div className="inner" style={{ height: 500 }}>
|
||||
<ControlledFlow preventScrolling={preventScrolling} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
it('lets user scroll', () => {
|
||||
cy.mount(<ScrollFlow preventScrolling={false} />).then(() => {
|
||||
cy.window().then((window) => {
|
||||
cy.get('.react-flow__pane').trigger('wheel', {
|
||||
wheelDelta: 240,
|
||||
wheelDeltaX: 0,
|
||||
wheelDeltaY: 240,
|
||||
eventConstructor: 'WheelEvent',
|
||||
view: window,
|
||||
});
|
||||
|
||||
// @TODO: why is this not working?
|
||||
// it seems that the event is somehow broken. This works fine when using the mouse.
|
||||
// cy.window().its('scrollY').should('be.gt', 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('does not user scroll', () => {
|
||||
cy.mount(<ScrollFlow preventScrolling={true} />).then(() => {
|
||||
cy.window().then((window) => {
|
||||
cy.get('.react-flow__pane').trigger('wheel', {
|
||||
wheelDelta: 240,
|
||||
wheelDeltaX: 0,
|
||||
wheelDeltaY: 240,
|
||||
eventConstructor: 'WheelEvent',
|
||||
view: window,
|
||||
});
|
||||
|
||||
cy.window().its('scrollY').should('be.equal', 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('uses attributionPosition', () => {
|
||||
it('displays it on the bottom right', () => {
|
||||
cy.mount(<ControlledFlow />).then(() => {
|
||||
cy.get('.react-flow__attribution').then(($el) => {
|
||||
const { left, top, width, height } = $el[0].getBoundingClientRect();
|
||||
|
||||
expect(left).equal(window.innerWidth - width);
|
||||
expect(top).equal(window.innerHeight - height);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('displays it on the top left', () => {
|
||||
cy.mount(<ControlledFlow attributionPosition="top-left" />).then(() => {
|
||||
cy.get('.react-flow__attribution').then(($el) => {
|
||||
const { left, top } = $el[0].getBoundingClientRect();
|
||||
|
||||
expect(left).equal(0);
|
||||
expect(top).equal(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// test specific helpers
|
||||
|
||||
type CompProps = {
|
||||
onChange?: (vp: Viewport) => void;
|
||||
};
|
||||
|
||||
function InnerComp({ onChange }: CompProps) {
|
||||
const viewport = useViewport();
|
||||
|
||||
onChange?.(viewport);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function Comp({ onChange, ...rest }: CompProps & ReactFlowProps) {
|
||||
return (
|
||||
<ReactFlow {...rest}>
|
||||
<InnerComp onChange={onChange} />
|
||||
</ReactFlow>
|
||||
);
|
||||
}
|
||||
|
||||
function getLatestViewport(onChangeSpy: any) {
|
||||
return onChangeSpy.lastCall.args[0] as unknown as Viewport;
|
||||
}
|
||||
+16
-8
@@ -1,13 +1,18 @@
|
||||
import { isNode, isEdge, getOutgoers, getIncomers, addEdge } from '../../../dist/esm/index.js';
|
||||
import { Node, Edge, isNode, isEdge, getOutgoers, getIncomers, addEdge } from 'reactflow';
|
||||
|
||||
const nodes = [
|
||||
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } },
|
||||
const nodes: Node[] = [
|
||||
{
|
||||
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 } },
|
||||
];
|
||||
|
||||
const edges = [
|
||||
const edges: Edge[] = [
|
||||
{ id: 'e1-2', source: '1', target: '2', animated: true },
|
||||
{ id: 'e1-3', source: '1', target: '3' },
|
||||
{ id: 'e2-3', source: '2', target: '3' },
|
||||
@@ -43,25 +48,26 @@ describe('Graph Utils Testing', () => {
|
||||
|
||||
describe('tests addEdge function', () => {
|
||||
it('adds edge', () => {
|
||||
const newEdge = { source: '1', target: '4' };
|
||||
const newEdge: Edge = { source: '1', target: '4', id: 'new-edge-1-4' };
|
||||
const nextEdges = addEdge(newEdge, edges);
|
||||
|
||||
expect(nextEdges.length).to.be.equal(edges.length + 1);
|
||||
});
|
||||
|
||||
it('tries to add existing edge', () => {
|
||||
const newEdge = { source: '2', target: '3' };
|
||||
const newEdge: Edge = { source: '2', target: '3', id: 'new-edge-2-3' };
|
||||
const nextEdges = addEdge(newEdge, edges);
|
||||
|
||||
expect(nextEdges.length).to.be.equal(edges.length);
|
||||
});
|
||||
|
||||
it('tries to add invalid edge', () => {
|
||||
const newEdge = { nosource: '1', notarget: '3' };
|
||||
// @ts-ignore
|
||||
const newEdge: Edge = { nosource: '1', notarget: '3' };
|
||||
|
||||
try {
|
||||
addEdge(newEdge, edges);
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
console.log(e.message);
|
||||
|
||||
expect(e.message).to.be.equal("Can't create edge. An edge needs a source and a target.");
|
||||
@@ -69,3 +75,5 @@ describe('Graph Utils Testing', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
export {};
|
||||
@@ -1,6 +1,6 @@
|
||||
describe('Basic Flow Rendering', () => {
|
||||
before(() => {
|
||||
cy.visit('/basic');
|
||||
cy.visit('/');
|
||||
});
|
||||
|
||||
it('renders a flow with three nodes', () => {
|
||||
@@ -95,7 +95,7 @@ describe('Basic Flow Rendering', () => {
|
||||
it('drags a node', () => {
|
||||
const styleBeforeDrag = Cypress.$('.react-flow__node:first').css('transform');
|
||||
|
||||
cy.drag('.react-flow__node:first', { x: 500, y: 25 }).then(($el) => {
|
||||
cy.drag('.react-flow__node:first', { x: 500, y: 25 }).then(($el: any) => {
|
||||
const styleAfterDrag = $el.css('transform');
|
||||
expect(styleBeforeDrag).to.not.equal(styleAfterDrag);
|
||||
});
|
||||
@@ -163,3 +163,5 @@ describe('Basic Flow Rendering', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
export {};
|
||||
+2
@@ -79,3 +79,5 @@ describe('Controls Testing', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
export {};
|
||||
+3
-1
@@ -1,6 +1,6 @@
|
||||
describe('DragHandle Flow Rendering', () => {
|
||||
before(() => {
|
||||
cy.visit('/draghandle');
|
||||
cy.visit('/DragHandle');
|
||||
});
|
||||
|
||||
it('renders a flow with a node', () => {
|
||||
@@ -28,3 +28,5 @@ describe('DragHandle Flow Rendering', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
export {};
|
||||
@@ -1,6 +1,6 @@
|
||||
describe('Empty Flow Rendering', () => {
|
||||
before(() => {
|
||||
cy.visit('/empty');
|
||||
cy.visit('/Empty');
|
||||
});
|
||||
|
||||
it('renders an empty flow', () => {
|
||||
@@ -10,12 +10,15 @@ describe('Empty Flow Rendering', () => {
|
||||
});
|
||||
|
||||
it('renders empty selection', () => {
|
||||
cy.get('.react-flow__renderer').click();
|
||||
cy.get('body')
|
||||
.type('{shift}', { release: false })
|
||||
.wait(50)
|
||||
.get('.react-flow__selectionpane')
|
||||
.trigger('mousedown', 'topLeft', { which: 1, force: true })
|
||||
.trigger('mousemove', 'bottomLeft', { which: 1 })
|
||||
.trigger('mouseup', 'bottomLeft', { force: true });
|
||||
.trigger('mousedown', 400, 50, { which: 1, force: true })
|
||||
.trigger('mousemove', 200, 200, { which: 1 })
|
||||
.wait(50)
|
||||
.trigger('mouseup', 200, 200, { force: true });
|
||||
|
||||
cy.get('body').type('{shift}', { release: true });
|
||||
});
|
||||
@@ -37,3 +40,5 @@ describe('Empty Flow Rendering', () => {
|
||||
cy.get('.react-flow__edge').should('have.length', 1);
|
||||
});
|
||||
});
|
||||
|
||||
export {};
|
||||
+3
-1
@@ -1,6 +1,6 @@
|
||||
describe('Hidden Flow Rendering', () => {
|
||||
before(() => {
|
||||
cy.visit('/hidden');
|
||||
cy.visit('/Hidden');
|
||||
});
|
||||
|
||||
it('renders empty flow', () => {
|
||||
@@ -30,3 +30,5 @@ describe('Hidden Flow Rendering', () => {
|
||||
cy.get('.react-flow__minimap-node').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
export {};
|
||||
+3
-1
@@ -1,6 +1,6 @@
|
||||
describe('Interaction Flow Rendering', () => {
|
||||
before(() => {
|
||||
cy.visit('/interaction');
|
||||
cy.visit('/Interaction');
|
||||
});
|
||||
|
||||
it('renders initial flow', () => {
|
||||
@@ -149,3 +149,5 @@ describe('Interaction Flow Rendering', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
export {};
|
||||
+9
-6
@@ -10,22 +10,23 @@ describe('Minimap Testing', () => {
|
||||
|
||||
it('has same number of nodes as the pane', () => {
|
||||
const paneNodes = Cypress.$('.react-flow__node').length;
|
||||
const minimapNodes = Cypress.$('.react-flow__minimap-node').length;
|
||||
|
||||
cy.wait(100);
|
||||
cy.wait(200);
|
||||
|
||||
const minimapNodes = Cypress.$('.react-flow__minimap-node').length;
|
||||
|
||||
expect(paneNodes).equal(minimapNodes);
|
||||
});
|
||||
|
||||
it('changes zoom level', () => {
|
||||
const viewBoxBeforeZoom = Cypress.$('.react-flow__minimap').attr('viewBox');
|
||||
const viewBoxBeforeZoom = Cypress.$('.react-flow__minimap svg').attr('viewBox');
|
||||
const maskPathBeforeZoom = Cypress.$('.react-flow__minimap-mask').attr('d');
|
||||
|
||||
cy.get('.react-flow__pane')
|
||||
.trigger('wheel', 'topLeft', { deltaY: -200 })
|
||||
.wait(50)
|
||||
.then(() => {
|
||||
const viewBoxAfterZoom = Cypress.$('.react-flow__minimap').attr('viewBox');
|
||||
const viewBoxAfterZoom = Cypress.$('.react-flow__minimap svg').attr('viewBox');
|
||||
const maskPathAfterZoom = Cypress.$('.react-flow__minimap-mask').attr('d');
|
||||
|
||||
expect(viewBoxBeforeZoom).to.not.equal(viewBoxAfterZoom);
|
||||
@@ -49,7 +50,7 @@ describe('Minimap Testing', () => {
|
||||
});
|
||||
|
||||
it('changes node positions via pane drag', () => {
|
||||
const viewBoxBeforeDrag = Cypress.$('.react-flow__minimap').attr('viewBox');
|
||||
const viewBoxBeforeDrag = Cypress.$('.react-flow__minimap svg').attr('viewBox');
|
||||
const maskPathBeforeDrag = Cypress.$('.react-flow__minimap-mask').attr('d');
|
||||
|
||||
// for d3 we have to pass the window to the event
|
||||
@@ -61,7 +62,7 @@ describe('Minimap Testing', () => {
|
||||
.wait(50)
|
||||
.trigger('mouseup', { force: true, view: win })
|
||||
.then(() => {
|
||||
const viewBoxAfterDrag = Cypress.$('.react-flow__minimap').attr('viewBox');
|
||||
const viewBoxAfterDrag = Cypress.$('.react-flow__minimap svg').attr('viewBox');
|
||||
const maskPathAfterDrag = Cypress.$('.react-flow__minimap-mask').attr('d');
|
||||
|
||||
expect(viewBoxBeforeDrag).to.not.equal(viewBoxAfterDrag);
|
||||
@@ -70,3 +71,5 @@ describe('Minimap Testing', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
export {};
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Node, Edge } from 'reactflow';
|
||||
|
||||
export const nodes: Node[] = [
|
||||
{
|
||||
id: '1',
|
||||
data: { label: 'Node 1' },
|
||||
position: { x: 0, y: 0 },
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
data: { label: 'Node 2' },
|
||||
position: { x: 200, y: 200 },
|
||||
},
|
||||
];
|
||||
|
||||
export const edges: Edge[] = [
|
||||
{
|
||||
id: 'e1',
|
||||
source: '1',
|
||||
target: '2',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,65 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import {
|
||||
ReactFlow,
|
||||
Node,
|
||||
Edge,
|
||||
NodeChange,
|
||||
EdgeChange,
|
||||
applyNodeChanges,
|
||||
applyEdgeChanges,
|
||||
Connection,
|
||||
addEdge,
|
||||
ReactFlowProps,
|
||||
} from 'reactflow';
|
||||
|
||||
function ControlledFlow({
|
||||
addOnNodeChangeHandler = true,
|
||||
addOnEdgeChangeHandler = true,
|
||||
addOnConnectHandler = true,
|
||||
initialNodes = [],
|
||||
initialEdges = [],
|
||||
...rest
|
||||
}: {
|
||||
initialNodes?: Node[];
|
||||
initialEdges?: Edge[];
|
||||
addOnNodeChangeHandler?: boolean;
|
||||
addOnEdgeChangeHandler?: boolean;
|
||||
addOnConnectHandler?: boolean;
|
||||
} & Partial<ReactFlowProps>) {
|
||||
const [nodes, setNodes] = useState<Node[]>(initialNodes);
|
||||
const [edges, setEdges] = useState<Edge[]>(initialEdges);
|
||||
|
||||
const onNodesChange = useCallback(
|
||||
(changes: NodeChange[]) => setNodes((nds) => applyNodeChanges(changes, nds)),
|
||||
[setNodes]
|
||||
);
|
||||
|
||||
const onEdgesChange = useCallback(
|
||||
(changes: EdgeChange[]) => setEdges((eds) => applyEdgeChanges(changes, eds)),
|
||||
[setEdges]
|
||||
);
|
||||
|
||||
const onConnect = useCallback((params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)), [setEdges]);
|
||||
|
||||
const handlers: {
|
||||
onNodesChange?: (changes: NodeChange[]) => void;
|
||||
onEdgesChange?: (changes: EdgeChange[]) => void;
|
||||
onConnect?: (params: Connection | Edge) => void;
|
||||
} = {};
|
||||
|
||||
if (addOnNodeChangeHandler) {
|
||||
handlers.onNodesChange = onNodesChange;
|
||||
}
|
||||
|
||||
if (addOnEdgeChangeHandler) {
|
||||
handlers.onEdgesChange = onEdgesChange;
|
||||
}
|
||||
|
||||
if (addOnConnectHandler) {
|
||||
handlers.onConnect = onConnect;
|
||||
}
|
||||
|
||||
return <ReactFlow nodes={nodes} edges={edges} {...handlers} {...rest} />;
|
||||
}
|
||||
|
||||
export default ControlledFlow;
|
||||
@@ -0,0 +1,58 @@
|
||||
Cypress.Commands.add('drag', (selector, { x, y }) =>
|
||||
cy.window().then((window) => {
|
||||
const elementToDrag = cy.get(selector as string);
|
||||
return elementToDrag.then(($el) => {
|
||||
const { left, top, width, height } = $el[0].getBoundingClientRect();
|
||||
const centerX = left + width / 2;
|
||||
const centerY = top + height / 2;
|
||||
const nextX: number = centerX + x;
|
||||
const nextY: number = centerY + y;
|
||||
|
||||
return elementToDrag
|
||||
.trigger('mousedown', { view: window })
|
||||
.trigger('mousemove', nextX, nextY, { force: true })
|
||||
.wait(50)
|
||||
.trigger('mouseup', { view: window, force: true });
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
Cypress.Commands.add('dragPane', ({ from, to }) =>
|
||||
cy
|
||||
.window()
|
||||
.then((window) =>
|
||||
cy
|
||||
.get('.react-flow__pane')
|
||||
.trigger('mousedown', from.x, from.y, { view: window })
|
||||
.trigger('mousemove', to.x, to.y)
|
||||
.trigger('mouseup', { force: true, view: window })
|
||||
)
|
||||
);
|
||||
|
||||
Cypress.Commands.add('zoomPane', (wheelDelta: number) =>
|
||||
cy.get('.react-flow__pane').trigger('wheel', 'center', { deltaY: wheelDelta }).wait(250)
|
||||
);
|
||||
|
||||
Cypress.Commands.add('isWithinViewport', { prevSubject: true }, (subject) => {
|
||||
const rect = subject[0].getBoundingClientRect();
|
||||
|
||||
expect(rect.top).to.be.within(0, window.innerHeight);
|
||||
expect(rect.right).to.be.within(0, window.innerWidth);
|
||||
expect(rect.bottom).to.be.within(0, window.innerHeight);
|
||||
expect(rect.left).to.be.within(0, window.innerWidth);
|
||||
|
||||
return subject;
|
||||
});
|
||||
|
||||
Cypress.Commands.add('isOutsideViewport', { prevSubject: true }, (subject) => {
|
||||
const rect = subject[0].getBoundingClientRect();
|
||||
|
||||
expect(rect.top).not.to.be.within(0, window.innerHeight);
|
||||
expect(rect.right).not.to.be.within(0, window.innerWidth);
|
||||
expect(rect.bottom).not.to.be.within(0, window.innerHeight);
|
||||
expect(rect.left).not.to.be.within(0, window.innerWidth);
|
||||
|
||||
return subject;
|
||||
});
|
||||
|
||||
export {};
|
||||
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>Components App</title>
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div data-cy-root id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,27 @@
|
||||
///<reference types="cypress" />
|
||||
///<reference types="@cypress/skip-test" />
|
||||
///<reference types="cypress-real-events" />
|
||||
|
||||
import './commands';
|
||||
import 'cypress-real-events/support';
|
||||
|
||||
import { mount } from 'cypress/react18';
|
||||
import { XYPosition } from 'reactflow';
|
||||
|
||||
import 'reactflow/dist/style.css';
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Cypress {
|
||||
interface Chainable {
|
||||
mount: typeof mount;
|
||||
drag: (selector: string, toPosition: XYPosition) => Cypress.Chainable<JQuery<HTMLElement>>;
|
||||
dragPane: ({ from, to }: { from: XYPosition; to: XYPosition }) => Cypress.Chainable<JQuery<HTMLElement>>;
|
||||
zoomPane: (wheelDelta: number) => Cypress.Chainable<JQuery<HTMLElement>>;
|
||||
isWithinViewport: () => Cypress.Chainable<JQuery<HTMLElement>>;
|
||||
isOutsideViewport: () => Cypress.Chainable<JQuery<HTMLElement>>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Cypress.Commands.add('mount', mount);
|
||||
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + React + TS</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "@reactflow/examples",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --port 3000 --open",
|
||||
"serve": "vite serve --port 3000",
|
||||
"test": "pnpm test-component && pnpm test-e2e",
|
||||
"test-component": "cypress run --component",
|
||||
"test-e2e-cypress": "cypress run --e2e --headless",
|
||||
"test-e2e": "start-server-and-test 'pnpm serve' http-get://localhost:3000 'pnpm test-e2e-cypress'"
|
||||
},
|
||||
"dependencies": {
|
||||
"dagre": "^0.8.5",
|
||||
"localforage": "^1.10.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"reactflow": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cypress/skip-test": "^2.6.1",
|
||||
"@types/react": "^18.0.17",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@vitejs/plugin-react": "^2.1.0",
|
||||
"cypress": "^10.6.0",
|
||||
"cypress-real-events": "^1.7.1",
|
||||
"start-server-and-test": "^1.14.0",
|
||||
"typescript": "^4.6.4",
|
||||
"vite": "^3.1.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -1,53 +1,53 @@
|
||||
import { ChangeEvent } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { BrowserRouter, Route, Routes, useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
import Basic from './Basic';
|
||||
import ControlledUncontrolled from './ControlledUncontrolled';
|
||||
import CustomConnectionLine from './CustomConnectionLine';
|
||||
import CustomNode from './CustomNode';
|
||||
import DefaultNodes from './DefaultNodes';
|
||||
import DragHandle from './DragHandle';
|
||||
import DragNDrop from './DragNDrop';
|
||||
import Edges from './Edges';
|
||||
import EdgeTypes from './EdgeTypes';
|
||||
import Empty from './Empty';
|
||||
import FloatingEdges from './FloatingEdges';
|
||||
import Hidden from './Hidden';
|
||||
import './index.css';
|
||||
import Interaction from './Interaction';
|
||||
import Layouting from './Layouting';
|
||||
import MultiFlows from './MultiFlows';
|
||||
import NestedNodes from './NestedNodes';
|
||||
import NodeTypeChange from './NodeTypeChange';
|
||||
import NodeTypesObjectChange from './NodeTypesObjectChange';
|
||||
import Overview from './Overview';
|
||||
import Provider from './Provider';
|
||||
import SaveRestore from './SaveRestore';
|
||||
import Stress from './Stress';
|
||||
import Subflow from './Subflow';
|
||||
import SwitchFlow from './Switch';
|
||||
import TouchDevice from './TouchDevice';
|
||||
import Undirectional from './Undirectional';
|
||||
import UpdatableEdge from './UpdatableEdge';
|
||||
import UpdateNode from './UpdateNode';
|
||||
import UseUpdateNodeInternals from './UseUpdateNodeInternals';
|
||||
import UseReactFlow from './UseReactFlow';
|
||||
import Validation from './Validation';
|
||||
import UseKeyPress from './UseKeyPress';
|
||||
import Basic from '../examples/Basic';
|
||||
import Backgrounds from '../examples/Backgrounds';
|
||||
import ControlledUncontrolled from '../examples/ControlledUncontrolled';
|
||||
import CustomConnectionLine from '../examples/CustomConnectionLine';
|
||||
import CustomNode from '../examples/CustomNode';
|
||||
import DefaultNodes from '../examples/DefaultNodes';
|
||||
import DragHandle from '../examples/DragHandle';
|
||||
import DragNDrop from '../examples/DragNDrop';
|
||||
import Edges from '../examples/Edges';
|
||||
import EdgeTypes from '../examples/EdgeTypes';
|
||||
import Empty from '../examples/Empty';
|
||||
import FloatingEdges from '../examples/FloatingEdges';
|
||||
import Hidden from '../examples/Hidden';
|
||||
import Interaction from '../examples/Interaction';
|
||||
import Layouting from '../examples/Layouting';
|
||||
import MultiFlows from '../examples/MultiFlows';
|
||||
import NestedNodes from '../examples/NestedNodes';
|
||||
import NodeTypeChange from '../examples/NodeTypeChange';
|
||||
import NodeTypesObjectChange from '../examples/NodeTypesObjectChange';
|
||||
import Overview from '../examples/Overview';
|
||||
import Provider from '../examples/Provider';
|
||||
import SaveRestore from '../examples/SaveRestore';
|
||||
import Stress from '../examples/Stress';
|
||||
import Subflow from '../examples/Subflow';
|
||||
import SwitchFlow from '../examples/Switch';
|
||||
import TouchDevice from '../examples/TouchDevice';
|
||||
import Undirectional from '../examples/Undirectional';
|
||||
import UpdatableEdge from '../examples/UpdatableEdge';
|
||||
import UpdateNode from '../examples/UpdateNode';
|
||||
import UseUpdateNodeInternals from '../examples/UseUpdateNodeInternals';
|
||||
import UseReactFlow from '../examples/UseReactFlow';
|
||||
import Validation from '../examples/Validation';
|
||||
import UseKeyPress from '../examples/UseKeyPress';
|
||||
import EdgeRouting from '../examples/EdgeRouting';
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
component: Overview,
|
||||
},
|
||||
{
|
||||
path: '/basic',
|
||||
component: Basic,
|
||||
},
|
||||
{
|
||||
path: '/default-nodes',
|
||||
component: DefaultNodes,
|
||||
path: '/backgrounds',
|
||||
component: Backgrounds,
|
||||
},
|
||||
{
|
||||
path: '/controlled-uncontrolled',
|
||||
component: ControlledUncontrolled,
|
||||
},
|
||||
{
|
||||
path: '/custom-connectionline',
|
||||
@@ -57,6 +57,10 @@ const routes = [
|
||||
path: '/custom-node',
|
||||
component: CustomNode,
|
||||
},
|
||||
{
|
||||
path: '/default-nodes',
|
||||
component: DefaultNodes,
|
||||
},
|
||||
{
|
||||
path: '/draghandle',
|
||||
component: DragHandle,
|
||||
@@ -73,6 +77,10 @@ const routes = [
|
||||
path: '/edge-types',
|
||||
component: EdgeTypes,
|
||||
},
|
||||
{
|
||||
path: '/edge-routing',
|
||||
component: EdgeRouting,
|
||||
},
|
||||
{
|
||||
path: '/empty',
|
||||
component: Empty,
|
||||
@@ -109,6 +117,10 @@ const routes = [
|
||||
path: '/nodetypesobject-change',
|
||||
component: NodeTypesObjectChange,
|
||||
},
|
||||
{
|
||||
path: '/overview',
|
||||
component: Overview,
|
||||
},
|
||||
{
|
||||
path: '/provider',
|
||||
component: Provider,
|
||||
@@ -157,10 +169,6 @@ const routes = [
|
||||
path: '/validation',
|
||||
component: Validation,
|
||||
},
|
||||
{
|
||||
path: '/controlled-uncontrolled',
|
||||
component: ControlledUncontrolled,
|
||||
},
|
||||
{
|
||||
path: '/use-key-press',
|
||||
component: UseKeyPress,
|
||||
@@ -180,7 +188,7 @@ const Header = () => {
|
||||
<select defaultValue={location.pathname} onChange={onChange}>
|
||||
{routes.map((route) => (
|
||||
<option value={route.path} key={route.path}>
|
||||
{route.path === '/' ? 'overview' : route.path.substring(1, route.path.length)}
|
||||
{route.path === '/' ? 'basic' : route.path.substring(1, route.path.length)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
@@ -188,10 +196,7 @@ const Header = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const container = document.getElementById('root');
|
||||
const root = createRoot(container!);
|
||||
|
||||
root.render(
|
||||
export default () => (
|
||||
<BrowserRouter>
|
||||
<Header />
|
||||
<Routes>
|
||||
@@ -0,0 +1,42 @@
|
||||
import React, { FC } from 'react';
|
||||
|
||||
import ReactFlow, {
|
||||
Node,
|
||||
ReactFlowProvider,
|
||||
useNodesState,
|
||||
Background,
|
||||
BackgroundProps,
|
||||
BackgroundVariant,
|
||||
} from 'reactflow';
|
||||
|
||||
import styles from './style.module.css';
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{
|
||||
id: '1',
|
||||
data: { label: 'Node 1' },
|
||||
position: { x: 50, y: 50 },
|
||||
},
|
||||
];
|
||||
|
||||
const Flow: FC<{ id: string; bgProps: BackgroundProps }> = ({ id, bgProps }) => {
|
||||
const [nodes, , onNodesChange] = useNodesState(initialNodes);
|
||||
|
||||
return (
|
||||
<ReactFlowProvider>
|
||||
<ReactFlow nodes={nodes} onNodesChange={onNodesChange} id={id}>
|
||||
<Background {...bgProps} />
|
||||
</ReactFlow>
|
||||
</ReactFlowProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const Backgrounds: FC = () => (
|
||||
<div className={styles.wrapper}>
|
||||
<Flow id="flow-a" bgProps={{ variant: BackgroundVariant.Dots }} />
|
||||
<Flow id="flow-b" bgProps={{ variant: BackgroundVariant.Lines, gap: [50, 50] }} />
|
||||
<Flow id="flow-c" bgProps={{ variant: BackgroundVariant.Cross, gap: [100, 50] }} />
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Backgrounds;
|
||||
@@ -0,0 +1,13 @@
|
||||
.wrapper {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.wrapper :global .react-flow {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.wrapper :global .react-flow {
|
||||
border-right: 1px solid #ddd;
|
||||
}
|
||||
@@ -1,23 +1,45 @@
|
||||
import { MouseEvent } from 'react';
|
||||
|
||||
import React, { MouseEvent } from 'react';
|
||||
import ReactFlow, {
|
||||
ReactFlowProvider,
|
||||
MiniMap,
|
||||
Background,
|
||||
BackgroundVariant,
|
||||
Controls,
|
||||
ReactFlowProvider,
|
||||
Node,
|
||||
Edge,
|
||||
useReactFlow,
|
||||
} from 'react-flow-renderer';
|
||||
} from 'reactflow';
|
||||
|
||||
const onNodeDrag = (_: MouseEvent, node: Node) => console.log('drag', node);
|
||||
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
|
||||
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 }, className: 'light' },
|
||||
{ id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 }, className: 'light' },
|
||||
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 }, className: 'light' },
|
||||
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 }, className: 'light' },
|
||||
{
|
||||
id: '1',
|
||||
type: 'input',
|
||||
data: { label: 'Node 1' },
|
||||
position: { x: 250, y: 5 },
|
||||
className: 'light',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
data: { label: 'Node 2' },
|
||||
position: { x: 100, y: 100 },
|
||||
className: 'light',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
data: { label: 'Node 3' },
|
||||
position: { x: 400, y: 100 },
|
||||
className: 'light',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
data: { label: 'Node 4' },
|
||||
position: { x: 400, y: 200 },
|
||||
className: 'light',
|
||||
},
|
||||
];
|
||||
|
||||
const initialEdges: Edge[] = [
|
||||
@@ -71,6 +93,8 @@ const BasicFlow = () => {
|
||||
selectNodesOnDrag={false}
|
||||
>
|
||||
<Background variant={BackgroundVariant.Lines} />
|
||||
<MiniMap />
|
||||
<Controls />
|
||||
|
||||
<div style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}>
|
||||
<button onClick={resetTransform} style={{ marginRight: 5 }}>
|
||||
+29
-7
@@ -1,19 +1,41 @@
|
||||
import React from 'react';
|
||||
import ReactFlow, {
|
||||
useReactFlow,
|
||||
Background,
|
||||
BackgroundVariant,
|
||||
Node,
|
||||
Edge,
|
||||
ReactFlowProvider,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
} from 'react-flow-renderer';
|
||||
Background,
|
||||
BackgroundVariant,
|
||||
} from 'reactflow';
|
||||
|
||||
const defaultNodes: Node[] = [
|
||||
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 }, className: 'light' },
|
||||
{ id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 }, className: 'light' },
|
||||
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 }, className: 'light' },
|
||||
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 }, className: 'light' },
|
||||
{
|
||||
id: '1',
|
||||
type: 'input',
|
||||
data: { label: 'Node 1' },
|
||||
position: { x: 250, y: 5 },
|
||||
className: 'light',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
data: { label: 'Node 2' },
|
||||
position: { x: 100, y: 100 },
|
||||
className: 'light',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
data: { label: 'Node 3' },
|
||||
position: { x: 400, y: 100 },
|
||||
className: 'light',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
data: { label: 'Node 4' },
|
||||
position: { x: 400, y: 200 },
|
||||
className: 'light',
|
||||
},
|
||||
];
|
||||
|
||||
const defaultEdges: Edge[] = [
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
import React, { FC } from 'react';
|
||||
import { ConnectionLineComponentProps } from 'react-flow-renderer';
|
||||
import { ConnectionLineComponentProps } from 'reactflow';
|
||||
|
||||
const ConnectionLine: FC<ConnectionLineComponentProps> = ({ sourceX, sourceY, targetX, targetY, toX, toY }) => {
|
||||
const ConnectionLine: FC<ConnectionLineComponentProps> = ({ fromX, fromY, toX, toY }) => {
|
||||
return (
|
||||
<g>
|
||||
<path
|
||||
@@ -9,7 +9,7 @@ const ConnectionLine: FC<ConnectionLineComponentProps> = ({ sourceX, sourceY, ta
|
||||
stroke="#222"
|
||||
strokeWidth={1.5}
|
||||
className="animated"
|
||||
d={`M${sourceX},${sourceY} C ${sourceX} ${targetY} ${sourceX} ${targetY} ${targetX},${targetY}`}
|
||||
d={`M${fromX},${fromY} C ${fromX} ${toY} ${fromX} ${toY} ${toX},${toY}`}
|
||||
/>
|
||||
<circle cx={toX} cy={toY} fill="#fff" r={3} stroke="#222" strokeWidth={1.5} />
|
||||
</g>
|
||||
+12
-5
@@ -1,18 +1,25 @@
|
||||
import { useCallback } from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import ReactFlow, {
|
||||
Node,
|
||||
addEdge,
|
||||
Background,
|
||||
BackgroundVariant,
|
||||
Connection,
|
||||
Edge,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
} from 'react-flow-renderer';
|
||||
Background,
|
||||
BackgroundVariant,
|
||||
} from 'reactflow';
|
||||
|
||||
import ConnectionLine from './ConnectionLine';
|
||||
|
||||
const initialNodes: Node[] = [{ id: '1', type: 'default', data: { label: 'Node 1' }, position: { x: 250, y: 5 } }];
|
||||
const initialNodes: Node[] = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'default',
|
||||
data: { label: 'Node 1' },
|
||||
position: { x: 250, y: 5 },
|
||||
},
|
||||
];
|
||||
const initialEdges: Edge[] = [];
|
||||
|
||||
const ConnectionLineFlow = () => {
|
||||
+17
-4
@@ -1,14 +1,27 @@
|
||||
import React, { memo, FC, CSSProperties } from 'react';
|
||||
|
||||
import { Handle, Position, NodeProps, Connection, Edge } from 'react-flow-renderer';
|
||||
import React, { memo, FC, CSSProperties, useCallback } from 'react';
|
||||
import { Handle, Position, NodeProps, Connection, Edge, useOnViewportChange, Viewport } from 'reactflow';
|
||||
|
||||
const targetHandleStyle: CSSProperties = { background: '#555' };
|
||||
const sourceHandleStyleA: CSSProperties = { ...targetHandleStyle, top: 10 };
|
||||
const sourceHandleStyleB: CSSProperties = { ...targetHandleStyle, bottom: 10, top: 'auto' };
|
||||
const sourceHandleStyleB: CSSProperties = {
|
||||
...targetHandleStyle,
|
||||
bottom: 10,
|
||||
top: 'auto',
|
||||
};
|
||||
|
||||
const onConnect = (params: Connection | Edge) => console.log('handle onConnect', params);
|
||||
|
||||
const ColorSelectorNode: FC<NodeProps> = ({ data, isConnectable }) => {
|
||||
const onStart = useCallback((viewport: Viewport) => console.log('onStart', viewport), []);
|
||||
const onChange = useCallback((viewport: Viewport) => console.log('onChange', viewport), []);
|
||||
const onEnd = useCallback((viewport: Viewport) => console.log('onEnd', viewport), []);
|
||||
|
||||
useOnViewportChange({
|
||||
onStart,
|
||||
onChange,
|
||||
onEnd,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Handle type="target" position={Position.Left} style={targetHandleStyle} onConnect={onConnect} />
|
||||
+31
-10
@@ -1,10 +1,8 @@
|
||||
import { useState, useEffect, MouseEvent } from 'react';
|
||||
import { ChangeEvent } from 'react';
|
||||
|
||||
import React, { useState, useEffect, MouseEvent, ChangeEvent, useCallback } from 'react';
|
||||
import ReactFlow, {
|
||||
addEdge,
|
||||
MiniMap,
|
||||
Controls,
|
||||
addEdge,
|
||||
Node,
|
||||
ReactFlowInstance,
|
||||
Position,
|
||||
@@ -12,7 +10,7 @@ import ReactFlow, {
|
||||
Connection,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
} from 'react-flow-renderer';
|
||||
} from 'reactflow';
|
||||
|
||||
import ColorSelectorNode from './ColorSelectorNode';
|
||||
|
||||
@@ -93,14 +91,37 @@ const CustomNodeFlow = () => {
|
||||
]);
|
||||
|
||||
setEdges([
|
||||
{ id: 'e1-2', source: '1', target: '2', animated: true, style: { stroke: '#fff' } },
|
||||
{ id: 'e2a-3', source: '2', sourceHandle: 'a', target: '3', animated: true, style: { stroke: '#fff' } },
|
||||
{ id: 'e2b-4', source: '2', sourceHandle: 'b', target: '4', animated: true, style: { stroke: '#fff' } },
|
||||
{
|
||||
id: 'e1-2',
|
||||
source: '1',
|
||||
target: '2',
|
||||
animated: true,
|
||||
style: { stroke: '#fff' },
|
||||
},
|
||||
{
|
||||
id: 'e2a-3',
|
||||
source: '2',
|
||||
sourceHandle: 'a',
|
||||
target: '3',
|
||||
animated: true,
|
||||
style: { stroke: '#fff' },
|
||||
},
|
||||
{
|
||||
id: 'e2b-4',
|
||||
source: '2',
|
||||
sourceHandle: 'b',
|
||||
target: '4',
|
||||
animated: true,
|
||||
style: { stroke: '#fff' },
|
||||
},
|
||||
]);
|
||||
}, []);
|
||||
|
||||
const onConnect = (connection: Connection) =>
|
||||
setEdges((eds) => addEdge({ ...connection, animated: true, style: { stroke: '#fff' } }, eds));
|
||||
const onConnect = useCallback(
|
||||
(connection: Connection) =>
|
||||
setEdges((eds) => addEdge({ ...connection, animated: true, style: { stroke: '#fff' } }, eds)),
|
||||
[setEdges]
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
+27
-12
@@ -1,17 +1,32 @@
|
||||
import ReactFlow, {
|
||||
useReactFlow,
|
||||
Background,
|
||||
BackgroundVariant,
|
||||
Node,
|
||||
Edge,
|
||||
ReactFlowProvider,
|
||||
} from 'react-flow-renderer';
|
||||
import React from 'react';
|
||||
import ReactFlow, { useReactFlow, Node, Edge, ReactFlowProvider, Background, BackgroundVariant } from 'reactflow';
|
||||
|
||||
const defaultNodes: Node[] = [
|
||||
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 }, className: 'light' },
|
||||
{ id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 }, className: 'light' },
|
||||
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 }, className: 'light' },
|
||||
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 }, className: 'light' },
|
||||
{
|
||||
id: '1',
|
||||
type: 'input',
|
||||
data: { label: 'Node 1' },
|
||||
position: { x: 250, y: 5 },
|
||||
className: 'light',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
data: { label: 'Node 2' },
|
||||
position: { x: 100, y: 100 },
|
||||
className: 'light',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
data: { label: 'Node 3' },
|
||||
position: { x: 400, y: 100 },
|
||||
className: 'light',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
data: { label: 'Node 4' },
|
||||
position: { x: 400, y: 200 },
|
||||
className: 'light',
|
||||
},
|
||||
];
|
||||
|
||||
const defaultEdges: Edge[] = [
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import { memo, FC } from 'react';
|
||||
import React, { memo, FC } from 'react';
|
||||
|
||||
import { Handle, Position, NodeProps, Connection, Edge } from 'react-flow-renderer';
|
||||
import { Handle, Position, NodeProps, Connection, Edge } from 'reactflow';
|
||||
|
||||
const onConnect = (params: Connection | Edge) => console.log('handle onConnect', params);
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { MouseEvent } from 'react';
|
||||
import ReactFlow, { Node, Edge, useNodesState, useEdgesState } from 'react-flow-renderer';
|
||||
import React, { MouseEvent } from 'react';
|
||||
import ReactFlow, { Node, Edge, useNodesState, useEdgesState } from 'reactflow';
|
||||
|
||||
import DragHandleNode from './DragHandleNode';
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import React, { DragEvent } from 'react';
|
||||
import styles from './dnd.module.css';
|
||||
|
||||
const onDragStart = (event: DragEvent, nodeType: string) => {
|
||||
event.dataTransfer.setData('application/reactflow', nodeType);
|
||||
event.dataTransfer.effectAllowed = 'move';
|
||||
};
|
||||
|
||||
const Sidebar = () => {
|
||||
return (
|
||||
<aside className={styles.aside}>
|
||||
<div className={styles.description}>
|
||||
You can drag these nodes to the pane on the left.
|
||||
</div>
|
||||
<div
|
||||
className='react-flow__node-input'
|
||||
onDragStart={(event: DragEvent) => onDragStart(event, 'input')}
|
||||
draggable
|
||||
>
|
||||
Input Node
|
||||
</div>
|
||||
<div
|
||||
className='react-flow__node-default'
|
||||
onDragStart={(event: DragEvent) => onDragStart(event, 'default')}
|
||||
draggable
|
||||
>
|
||||
Default Node
|
||||
</div>
|
||||
<div
|
||||
className='react-flow__node-output'
|
||||
onDragStart={(event: DragEvent) => onDragStart(event, 'output')}
|
||||
draggable
|
||||
>
|
||||
Output Node
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
|
||||
export default Sidebar;
|
||||
+9
-9
@@ -4,34 +4,34 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.dndflow aside {
|
||||
.aside {
|
||||
border-right: 1px solid #eee;
|
||||
padding: 15px 10px;
|
||||
font-size: 12px;
|
||||
background: #fcfcfc;
|
||||
}
|
||||
|
||||
.dndflow aside > * {
|
||||
.aside > * {
|
||||
margin-bottom: 10px;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.dndflow aside .description {
|
||||
.description {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.dndflow .reactflow-wrapper {
|
||||
.wrapper {
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.dndflow {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.dndflow aside {
|
||||
.aside {
|
||||
width: 20%;
|
||||
max-width: 180px;
|
||||
}
|
||||
|
||||
.dndflow {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
+22
-8
@@ -1,21 +1,29 @@
|
||||
import { useState, DragEvent } from 'react';
|
||||
import React, { useState, DragEvent } from 'react';
|
||||
import ReactFlow, {
|
||||
ReactFlowProvider,
|
||||
addEdge,
|
||||
Controls,
|
||||
ReactFlowInstance,
|
||||
Connection,
|
||||
Edge,
|
||||
Node,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
} from 'react-flow-renderer';
|
||||
Controls,
|
||||
NodeOrigin,
|
||||
} from 'reactflow';
|
||||
|
||||
import Sidebar from './Sidebar';
|
||||
|
||||
import './dnd.css';
|
||||
import styles from './dnd.module.css';
|
||||
|
||||
const initialNodes: Node[] = [{ id: '1', type: 'input', data: { label: 'input node' }, position: { x: 250, y: 5 } }];
|
||||
const initialNodes: Node[] = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'input',
|
||||
data: { label: 'input node' },
|
||||
position: { x: 250, y: 5 },
|
||||
},
|
||||
];
|
||||
|
||||
const onDragOver = (event: DragEvent) => {
|
||||
event.preventDefault();
|
||||
@@ -25,6 +33,8 @@ const onDragOver = (event: DragEvent) => {
|
||||
let id = 0;
|
||||
const getId = () => `dndnode_${id++}`;
|
||||
|
||||
const nodeOrigin: NodeOrigin = [0.5, 0.5];
|
||||
|
||||
const DnDFlow = () => {
|
||||
const [reactFlowInstance, setReactFlowInstance] = useState<ReactFlowInstance>();
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
@@ -38,7 +48,10 @@ const DnDFlow = () => {
|
||||
|
||||
if (reactFlowInstance) {
|
||||
const type = event.dataTransfer.getData('application/reactflow');
|
||||
const position = reactFlowInstance.project({ x: event.clientX, y: event.clientY - 40 });
|
||||
const position = reactFlowInstance.project({
|
||||
x: event.clientX,
|
||||
y: event.clientY - 40,
|
||||
});
|
||||
const newNode: Node = {
|
||||
id: getId(),
|
||||
type,
|
||||
@@ -51,9 +64,9 @@ const DnDFlow = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="dndflow">
|
||||
<div className={styles.dndflow}>
|
||||
<ReactFlowProvider>
|
||||
<div className="reactflow-wrapper">
|
||||
<div className={styles.wrapper}>
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
@@ -63,6 +76,7 @@ const DnDFlow = () => {
|
||||
onInit={onInit}
|
||||
onDrop={onDrop}
|
||||
onDragOver={onDragOver}
|
||||
nodeOrigin={nodeOrigin}
|
||||
>
|
||||
<Controls />
|
||||
</ReactFlow>
|
||||
@@ -0,0 +1,134 @@
|
||||
import React from 'react';
|
||||
import ReactFlow, { Node, Edge, Position, MarkerType } from 'reactflow';
|
||||
|
||||
const nodes: Node[] = [
|
||||
// LTR
|
||||
{
|
||||
id: '1',
|
||||
position: { x: 50, y: -100 },
|
||||
data: { label: 'Source' },
|
||||
sourcePosition: Position.Right,
|
||||
targetPosition: Position.Right,
|
||||
style: { background: 'rgba(255,255,255,0.5)' },
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
position: { x: -100, y: 0 },
|
||||
data: { label: 'Target' },
|
||||
sourcePosition: Position.Left,
|
||||
targetPosition: Position.Left,
|
||||
style: { background: 'rgba(255,255,255,0.5)' },
|
||||
},
|
||||
// Right Right
|
||||
{
|
||||
id: '3',
|
||||
position: { x: -100, y: 250 },
|
||||
data: { label: 'Source' },
|
||||
sourcePosition: Position.Right,
|
||||
targetPosition: Position.Right,
|
||||
style: { background: 'rgba(255,255,255,0.5)' },
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
position: { x: 50, y: 150 },
|
||||
data: { label: 'Target' },
|
||||
sourcePosition: Position.Right,
|
||||
targetPosition: Position.Right,
|
||||
style: { background: 'rgba(255,255,255,0.5)' },
|
||||
},
|
||||
// Right Top
|
||||
{
|
||||
id: '5',
|
||||
position: { x: -100, y: 450 },
|
||||
data: { label: 'Source' },
|
||||
sourcePosition: Position.Right,
|
||||
targetPosition: Position.Right,
|
||||
style: { background: 'rgba(255,255,255,0.5)' },
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
position: { x: 100, y: 400 },
|
||||
data: { label: 'Target' },
|
||||
sourcePosition: Position.Top,
|
||||
targetPosition: Position.Top,
|
||||
style: { background: 'rgba(255,255,255,0.5)' },
|
||||
},
|
||||
// Right Bottom
|
||||
{
|
||||
id: '7',
|
||||
position: { x: 100, y: 700 },
|
||||
data: { label: 'Source' },
|
||||
sourcePosition: Position.Right,
|
||||
targetPosition: Position.Right,
|
||||
style: { background: 'rgba(255,255,255,0.5)' },
|
||||
},
|
||||
{
|
||||
id: '8',
|
||||
position: { x: -100, y: 600 },
|
||||
data: { label: 'Target' },
|
||||
sourcePosition: Position.Bottom,
|
||||
targetPosition: Position.Bottom,
|
||||
style: { background: 'rgba(255,255,255,0.5)' },
|
||||
},
|
||||
];
|
||||
|
||||
const edges: Edge[] = [
|
||||
{
|
||||
id: 'e1-2',
|
||||
source: '1',
|
||||
target: '2',
|
||||
type: 'smoothstep',
|
||||
markerEnd: {
|
||||
type: MarkerType.ArrowClosed,
|
||||
},
|
||||
pathOptions: {
|
||||
offset: 30,
|
||||
},
|
||||
interactionWidth: 0,
|
||||
},
|
||||
{
|
||||
id: 'e3-4',
|
||||
source: '3',
|
||||
target: '4',
|
||||
type: 'smoothstep',
|
||||
markerEnd: {
|
||||
type: MarkerType.ArrowClosed,
|
||||
},
|
||||
pathOptions: {
|
||||
borderRadius: 2,
|
||||
},
|
||||
interactionWidth: 0,
|
||||
},
|
||||
|
||||
{
|
||||
id: 'e4-5',
|
||||
source: '5',
|
||||
target: '6',
|
||||
type: 'smoothstep',
|
||||
markerEnd: {
|
||||
type: MarkerType.ArrowClosed,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
id: 'e7-8',
|
||||
source: '7',
|
||||
target: '8',
|
||||
type: 'smoothstep',
|
||||
markerEnd: {
|
||||
type: MarkerType.ArrowClosed,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const defaultEdgeOptions = {
|
||||
style: {
|
||||
strokeWidth: 1,
|
||||
},
|
||||
};
|
||||
|
||||
const SimpleEdge = () => {
|
||||
return <ReactFlow defaultNodes={nodes} defaultEdges={edges} fitView defaultEdgeOptions={defaultEdgeOptions} />;
|
||||
};
|
||||
|
||||
export default SimpleEdge;
|
||||
+7
-6
@@ -1,18 +1,20 @@
|
||||
/**
|
||||
* Example for checking the different edge types and source and target positions
|
||||
*/
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import ReactFlow, {
|
||||
addEdge,
|
||||
MiniMap,
|
||||
Controls,
|
||||
Background,
|
||||
Controls,
|
||||
MiniMap,
|
||||
addEdge,
|
||||
ReactFlowInstance,
|
||||
Connection,
|
||||
Edge,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
} from 'react-flow-renderer';
|
||||
} from 'reactflow';
|
||||
|
||||
import { getElements } from './utils';
|
||||
|
||||
const onInit = (reactFlowInstance: ReactFlowInstance) => {
|
||||
@@ -28,7 +30,7 @@ const deleteKeyCode = ['AltLeft+KeyD', 'Backspace'];
|
||||
const EdgeTypesFlow = () => {
|
||||
const [nodes, , onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
const onConnect = (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds));
|
||||
const onConnect = useCallback((params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)), [setEdges]);
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
@@ -39,7 +41,6 @@ const EdgeTypesFlow = () => {
|
||||
onInit={onInit}
|
||||
onConnect={onConnect}
|
||||
minZoom={0.2}
|
||||
zoomOnScroll={false}
|
||||
selectionKeyCode="a+s"
|
||||
multiSelectionKeyCode={multiSelectionKeyCode}
|
||||
deleteKeyCode={deleteKeyCode}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Edge, Node, Position } from 'react-flow-renderer';
|
||||
import { Edge, Node, Position } from 'reactflow';
|
||||
|
||||
const nodeWidth = 80;
|
||||
const nodeGapWidth = nodeWidth * 2;
|
||||
@@ -63,7 +63,10 @@ export function getElements(): { nodes: Node[]; edges: Edge[] } {
|
||||
for (let offsetIndex = 0; offsetIndex < offsets.length; offsetIndex++) {
|
||||
const currOffset = offsets[offsetIndex];
|
||||
|
||||
const style = { ...nodeStyle, background: nodeColors[sourceTargetIndex][edgeTypeIndex] };
|
||||
const style = {
|
||||
...nodeStyle,
|
||||
background: nodeColors[sourceTargetIndex][edgeTypeIndex],
|
||||
};
|
||||
const sourcePosition = {
|
||||
x: offsetIndex * nodeWidth * 4,
|
||||
y: edgeTypeIndex * 300 + sourceTargetIndex * edgeTypes.length * 300,
|
||||
+10
-3
@@ -1,5 +1,5 @@
|
||||
import { FC } from 'react';
|
||||
import { EdgeProps, getBezierPath } from 'react-flow-renderer';
|
||||
import React, { FC } from 'react';
|
||||
import { EdgeProps, getBezierPath } from 'reactflow';
|
||||
|
||||
const CustomEdge: FC<EdgeProps> = ({
|
||||
id,
|
||||
@@ -11,7 +11,14 @@ const CustomEdge: FC<EdgeProps> = ({
|
||||
targetPosition,
|
||||
data,
|
||||
}) => {
|
||||
const edgePath = getBezierPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition });
|
||||
const edgePath = getBezierPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition,
|
||||
targetX,
|
||||
targetY,
|
||||
targetPosition,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
+10
-3
@@ -1,5 +1,5 @@
|
||||
import { FC } from 'react';
|
||||
import { EdgeProps, getBezierPath, EdgeText, getBezierEdgeCenter } from 'react-flow-renderer';
|
||||
import React, { FC } from 'react';
|
||||
import { EdgeProps, getBezierPath, EdgeText, getBezierEdgeCenter } from 'reactflow';
|
||||
|
||||
const CustomEdge: FC<EdgeProps> = ({
|
||||
id,
|
||||
@@ -11,7 +11,14 @@ const CustomEdge: FC<EdgeProps> = ({
|
||||
targetPosition,
|
||||
data,
|
||||
}) => {
|
||||
const edgePath = getBezierPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition });
|
||||
const edgePath = getBezierPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition,
|
||||
targetX,
|
||||
targetY,
|
||||
targetPosition,
|
||||
});
|
||||
const [centerX, centerY] = getBezierEdgeCenter({
|
||||
sourceX,
|
||||
sourceY,
|
||||
@@ -1,22 +1,21 @@
|
||||
import { MouseEvent } from 'react';
|
||||
import React, { MouseEvent, useCallback } from 'react';
|
||||
import ReactFlow, {
|
||||
addEdge,
|
||||
Background,
|
||||
Connection,
|
||||
Controls,
|
||||
Background,
|
||||
MiniMap,
|
||||
addEdge,
|
||||
Connection,
|
||||
Edge,
|
||||
EdgeTypes,
|
||||
MarkerType,
|
||||
MiniMap,
|
||||
Node,
|
||||
ReactFlowInstance,
|
||||
useEdgesState,
|
||||
useNodesState,
|
||||
} from 'react-flow-renderer';
|
||||
} from 'reactflow';
|
||||
|
||||
import CustomEdge from './CustomEdge';
|
||||
import CustomEdge2 from './CustomEdge2';
|
||||
|
||||
const onInit = (reactFlowInstance: ReactFlowInstance) => reactFlowInstance.fitView();
|
||||
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
|
||||
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
||||
const onEdgeClick = (_: MouseEvent, edge: Edge) => console.log('click', edge);
|
||||
@@ -26,7 +25,12 @@ const onEdgeMouseMove = (_: MouseEvent, edge: Edge) => console.log('move', edge)
|
||||
const onEdgeMouseLeave = (_: MouseEvent, edge: Edge) => console.log('leave', edge);
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{ id: '1', type: 'input', data: { label: 'Input 1' }, position: { x: 250, y: 0 } },
|
||||
{
|
||||
id: '1',
|
||||
type: 'input',
|
||||
data: { label: 'Input 1' },
|
||||
position: { x: 250, y: 0 },
|
||||
},
|
||||
{ id: '2', data: { label: 'Node 2' }, position: { x: 150, y: 100 } },
|
||||
{ id: '2a', data: { label: 'Node 2a' }, position: { x: 0, y: 180 } },
|
||||
{ id: '2b', data: { label: 'Node 2b' }, position: { x: -40, y: 300 } },
|
||||
@@ -34,15 +38,47 @@ const initialNodes: Node[] = [
|
||||
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 300 } },
|
||||
{ id: '3a', data: { label: 'Node 3a' }, position: { x: 150, y: 300 } },
|
||||
{ id: '5', data: { label: 'Node 5' }, position: { x: 250, y: 400 } },
|
||||
{ id: '6', type: 'output', data: { label: 'Output 6' }, position: { x: 50, y: 550 } },
|
||||
{ id: '7', type: 'output', data: { label: 'Output 7' }, position: { x: 250, y: 550 } },
|
||||
{ id: '8', type: 'output', data: { label: 'Output 8' }, position: { x: 525, y: 600 } },
|
||||
{ id: '9', type: 'output', data: { label: 'Output 9' }, position: { x: 675, y: 500 } },
|
||||
{
|
||||
id: '6',
|
||||
type: 'output',
|
||||
data: { label: 'Output 6' },
|
||||
position: { x: 50, y: 550 },
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
type: 'output',
|
||||
data: { label: 'Output 7' },
|
||||
position: { x: 250, y: 550 },
|
||||
},
|
||||
{
|
||||
id: '8',
|
||||
type: 'output',
|
||||
data: { label: 'Output 8' },
|
||||
position: { x: 525, y: 600 },
|
||||
},
|
||||
{
|
||||
id: '9',
|
||||
type: 'output',
|
||||
data: { label: 'Output 9' },
|
||||
position: { x: 675, y: 500 },
|
||||
},
|
||||
];
|
||||
|
||||
const initialEdges: Edge[] = [
|
||||
{ id: 'e1-2', source: '1', target: '2', label: 'bezier edge (default)', className: 'normal-edge' },
|
||||
{ id: 'e2-2a', source: '2', target: '2a', type: 'smoothstep', label: 'smoothstep edge' },
|
||||
{
|
||||
id: 'e1-2',
|
||||
source: '1',
|
||||
target: '2',
|
||||
label: 'bezier edge (default)',
|
||||
className: 'normal-edge',
|
||||
},
|
||||
{
|
||||
id: 'e2-2a',
|
||||
source: '2',
|
||||
target: '2a',
|
||||
type: 'smoothstep',
|
||||
label: 'smoothstep edge',
|
||||
},
|
||||
{
|
||||
id: 'e2a-2b',
|
||||
source: '2a',
|
||||
@@ -51,9 +87,29 @@ const initialEdges: Edge[] = [
|
||||
label: 'simple bezier edge',
|
||||
},
|
||||
{ id: 'e2-3', source: '2', target: '3', type: 'step', label: 'step edge' },
|
||||
{ id: 'e3-4', source: '3', target: '4', type: 'straight', label: 'straight edge' },
|
||||
{ id: 'e3-3a', source: '3', target: '3a', type: 'straight', label: 'label only edge', style: { stroke: 'none' } },
|
||||
{ id: 'e3-5', source: '4', target: '5', animated: true, label: 'animated styled edge', style: { stroke: 'red' } },
|
||||
{
|
||||
id: 'e3-4',
|
||||
source: '3',
|
||||
target: '4',
|
||||
type: 'straight',
|
||||
label: 'straight edge',
|
||||
},
|
||||
{
|
||||
id: 'e3-3a',
|
||||
source: '3',
|
||||
target: '3a',
|
||||
type: 'straight',
|
||||
label: 'label only edge',
|
||||
style: { stroke: 'none' },
|
||||
},
|
||||
{
|
||||
id: 'e3-5',
|
||||
source: '4',
|
||||
target: '5',
|
||||
animated: true,
|
||||
label: 'animated styled edge',
|
||||
style: { stroke: 'red' },
|
||||
},
|
||||
{
|
||||
id: 'e5-7',
|
||||
source: '5',
|
||||
@@ -121,7 +177,7 @@ const edgeTypes: EdgeTypes = {
|
||||
const EdgesFlow = () => {
|
||||
const [nodes, , onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
const onConnect = (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds));
|
||||
const onConnect = useCallback((params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)), [setEdges]);
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
@@ -132,7 +188,6 @@ const EdgesFlow = () => {
|
||||
onNodeClick={onNodeClick}
|
||||
onConnect={onConnect}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
onInit={onInit}
|
||||
snapToGrid={true}
|
||||
edgeTypes={edgeTypes}
|
||||
onEdgeClick={onEdgeClick}
|
||||
@@ -1,35 +1,43 @@
|
||||
import { MouseEvent, CSSProperties } from 'react';
|
||||
import React, { MouseEvent, CSSProperties, useCallback } from 'react';
|
||||
|
||||
import ReactFlow, {
|
||||
addEdge,
|
||||
Controls,
|
||||
Background,
|
||||
Node,
|
||||
BackgroundVariant,
|
||||
Controls,
|
||||
addEdge,
|
||||
Node,
|
||||
Connection,
|
||||
Edge,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
ReactFlowInstance,
|
||||
} from 'react-flow-renderer';
|
||||
} from 'reactflow';
|
||||
|
||||
const onInit = (reactFlowInstance: ReactFlowInstance) => console.log('flow loaded:', reactFlowInstance);
|
||||
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
||||
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
|
||||
|
||||
const buttonStyle: CSSProperties = { position: 'absolute', left: 10, top: 10, zIndex: 4 };
|
||||
const buttonStyle: CSSProperties = {
|
||||
position: 'absolute',
|
||||
left: 10,
|
||||
top: 10,
|
||||
zIndex: 4,
|
||||
};
|
||||
|
||||
const EmptyFlow = () => {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||
|
||||
const onConnect = (params: Connection | Edge) => setEdges((els) => addEdge(params, els));
|
||||
const onConnect = useCallback((params: Connection | Edge) => setEdges((els) => addEdge(params, els)), [setEdges]);
|
||||
const addRandomNode = () => {
|
||||
const nodeId = (nodes.length + 1).toString();
|
||||
const newNode: Node = {
|
||||
id: nodeId,
|
||||
data: { label: `Node: ${nodeId}` },
|
||||
position: { x: Math.random() * window.innerWidth, y: Math.random() * window.innerHeight },
|
||||
position: {
|
||||
x: Math.random() * window.innerWidth,
|
||||
y: Math.random() * window.innerHeight,
|
||||
},
|
||||
};
|
||||
setNodes((nds) => nds.concat(newNode));
|
||||
};
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { FC } from 'react';
|
||||
import { getBezierPath, ConnectionLineComponentProps, Node } from 'react-flow-renderer';
|
||||
import React, { FC } from 'react';
|
||||
import { getBezierPath, ConnectionLineComponentProps, Node } from 'reactflow';
|
||||
|
||||
import { getEdgeParams } from './utils';
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { FC, useMemo, CSSProperties } from 'react';
|
||||
import { EdgeProps, useStore, getBezierPath, ReactFlowState } from 'react-flow-renderer';
|
||||
import React, { FC, useMemo, CSSProperties } from 'react';
|
||||
import { EdgeProps, useStore, getBezierPath, ReactFlowState } from 'reactflow';
|
||||
|
||||
import { getEdgeParams } from './utils';
|
||||
|
||||
+8
-8
@@ -1,25 +1,25 @@
|
||||
import { useCallback } from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import ReactFlow, {
|
||||
addEdge,
|
||||
Background,
|
||||
addEdge,
|
||||
ReactFlowInstance,
|
||||
EdgeTypes,
|
||||
Connection,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
} from 'react-flow-renderer';
|
||||
} from 'reactflow';
|
||||
|
||||
import './style.css';
|
||||
import styles from './style.module.css';
|
||||
|
||||
import FloatingEdge from './FloatingEdge';
|
||||
import FloatingConnectionLine from './FloatingConnectionLine';
|
||||
import FloatingEdge from './FloatingEdge';
|
||||
import { createElements } from './utils';
|
||||
|
||||
const onInit = (reactFlowInstance: ReactFlowInstance) => reactFlowInstance.fitView();
|
||||
|
||||
const { nodes: initialNodes, edges: initialEdges } = createElements();
|
||||
|
||||
const { nodes: initialNodes, edges: initialEdges } =
|
||||
typeof window !== 'undefined' ? createElements() : { nodes: [], edges: [] };
|
||||
const edgeTypes: EdgeTypes = {
|
||||
floating: FloatingEdge,
|
||||
};
|
||||
@@ -33,7 +33,7 @@ const FloatingEdges = () => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="floatingedges">
|
||||
<div className={styles.floatingedges}>
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
+1
-1
@@ -4,6 +4,6 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.floatingedges .react-flow__handle {
|
||||
.floatingedges :global .react-flow__handle {
|
||||
opacity: 0;
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { Position, XYPosition, Node, Edge } from 'react-flow-renderer';
|
||||
import { Position, XYPosition, Node, Edge } from 'reactflow';
|
||||
|
||||
// this helper function returns the intersection point
|
||||
// of the line between the center of the intersectionNode and the target node
|
||||
@@ -1,22 +1,33 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import ReactFlow, {
|
||||
addEdge,
|
||||
MiniMap,
|
||||
Controls,
|
||||
Connection,
|
||||
Edge,
|
||||
Node,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
} from 'react-flow-renderer';
|
||||
import ReactFlow, { addEdge, Connection, Edge, Node, useNodesState, useEdgesState, MiniMap, Controls } from 'reactflow';
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{ id: '1', type: 'input', hidden: true, data: { label: 'Node 1' }, position: { x: 250, y: 5 } },
|
||||
{ id: '2', hidden: true, data: { label: 'Node 2' }, position: { x: 100, y: 100 } },
|
||||
{ id: '3', hidden: true, data: { label: 'Node 3' }, position: { x: 400, y: 100 } },
|
||||
{ id: '4', hidden: true, data: { label: 'Node 4' }, position: { x: 400, y: 200 } },
|
||||
{
|
||||
id: '1',
|
||||
type: 'input',
|
||||
hidden: true,
|
||||
data: { label: 'Node 1' },
|
||||
position: { x: 250, y: 5 },
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
hidden: true,
|
||||
data: { label: 'Node 2' },
|
||||
position: { x: 100, y: 100 },
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
hidden: true,
|
||||
data: { label: 'Node 3' },
|
||||
position: { x: 400, y: 100 },
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
hidden: true,
|
||||
data: { label: 'Node 4' },
|
||||
position: { x: 400, y: 200 },
|
||||
},
|
||||
];
|
||||
|
||||
const initialEdges: Edge[] = [
|
||||
@@ -49,8 +60,6 @@ const HiddenFlow = () => {
|
||||
setEdges(setHidden(isHidden));
|
||||
}, [isHidden, setEdges, setNodes]);
|
||||
|
||||
console.log(nodes);
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
+10
-5
@@ -1,8 +1,6 @@
|
||||
import { useState, MouseEvent as ReactMouseEvent, WheelEvent } from 'react';
|
||||
import React, { useState, MouseEvent as ReactMouseEvent, WheelEvent } from 'react';
|
||||
import ReactFlow, {
|
||||
addEdge,
|
||||
MiniMap,
|
||||
Controls,
|
||||
Node,
|
||||
Connection,
|
||||
Edge,
|
||||
@@ -10,10 +8,17 @@ import ReactFlow, {
|
||||
Viewport,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
} from 'react-flow-renderer';
|
||||
Controls,
|
||||
MiniMap,
|
||||
} from 'reactflow';
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } },
|
||||
{
|
||||
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 } },
|
||||
+11
-7
@@ -1,8 +1,8 @@
|
||||
import { useCallback } from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import ReactFlow, {
|
||||
Controls,
|
||||
ReactFlowProvider,
|
||||
addEdge,
|
||||
Controls,
|
||||
Connection,
|
||||
CoordinateExtent,
|
||||
Position,
|
||||
@@ -10,12 +10,13 @@ import ReactFlow, {
|
||||
useEdgesState,
|
||||
MarkerType,
|
||||
EdgeMarker,
|
||||
} from 'react-flow-renderer';
|
||||
} from 'reactflow';
|
||||
|
||||
import dagre from 'dagre';
|
||||
|
||||
import initialItems from './initial-elements';
|
||||
|
||||
import './layouting.css';
|
||||
import styles from './layouting.module.css';
|
||||
|
||||
const dagreGraph = new dagre.graphlib.Graph();
|
||||
dagreGraph.setDefaultEdgeLabel(() => ({}));
|
||||
@@ -56,7 +57,10 @@ const LayoutFlow = () => {
|
||||
node.sourcePosition = isHorizontal ? Position.Right : Position.Bottom;
|
||||
// we need to pass a slightly different position in order to notify react flow about the change
|
||||
// @TODO how can we change the position handling so that we dont need this hack?
|
||||
node.position = { x: nodeWithPosition.x + Math.random() / 1000, y: nodeWithPosition.y };
|
||||
node.position = {
|
||||
x: nodeWithPosition.x + Math.random() / 1000,
|
||||
y: nodeWithPosition.y,
|
||||
};
|
||||
|
||||
return node;
|
||||
});
|
||||
@@ -80,7 +84,7 @@ const LayoutFlow = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="layoutflow">
|
||||
<div className={styles.layoutflow}>
|
||||
<ReactFlowProvider>
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
@@ -93,7 +97,7 @@ const LayoutFlow = () => {
|
||||
>
|
||||
<Controls />
|
||||
</ReactFlow>
|
||||
<div className="controls">
|
||||
<div className={styles.controls}>
|
||||
<button onClick={() => onLayout('TB')} style={{ marginRight: 10 }}>
|
||||
vertical layout
|
||||
</button>
|
||||
@@ -0,0 +1,135 @@
|
||||
import { Node, Edge, XYPosition, MarkerType } from 'reactflow';
|
||||
|
||||
const position: XYPosition = { x: 0, y: 0 };
|
||||
|
||||
const nodes: Node[] = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'input',
|
||||
data: { label: 'input' },
|
||||
position,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
data: { label: 'node 2' },
|
||||
position,
|
||||
},
|
||||
{
|
||||
id: '2a',
|
||||
data: { label: 'node 2a' },
|
||||
position,
|
||||
},
|
||||
{
|
||||
id: '2b',
|
||||
data: { label: 'node 2b' },
|
||||
position,
|
||||
},
|
||||
{
|
||||
id: '2c',
|
||||
data: { label: 'node 2c' },
|
||||
position,
|
||||
},
|
||||
{
|
||||
id: '2d',
|
||||
data: { label: 'node 2d' },
|
||||
position,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
data: { label: 'node 3' },
|
||||
position,
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
data: { label: 'node 4' },
|
||||
position,
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
data: { label: 'node 5' },
|
||||
position,
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
type: 'output',
|
||||
data: { label: 'output' },
|
||||
position,
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
type: 'output',
|
||||
data: { label: 'output' },
|
||||
position: { x: 400, y: 450 },
|
||||
},
|
||||
];
|
||||
|
||||
const edges: Edge[] = [
|
||||
{
|
||||
id: 'e12',
|
||||
source: '1',
|
||||
target: '2',
|
||||
type: 'smoothstep',
|
||||
markerEnd: { type: MarkerType.Arrow },
|
||||
},
|
||||
{
|
||||
id: 'e13',
|
||||
source: '1',
|
||||
target: '3',
|
||||
type: 'smoothstep',
|
||||
markerEnd: { type: MarkerType.Arrow },
|
||||
},
|
||||
{
|
||||
id: 'e22a',
|
||||
source: '2',
|
||||
target: '2a',
|
||||
type: 'smoothstep',
|
||||
markerEnd: { type: MarkerType.Arrow },
|
||||
},
|
||||
{
|
||||
id: 'e22b',
|
||||
source: '2',
|
||||
target: '2b',
|
||||
type: 'smoothstep',
|
||||
markerEnd: { type: MarkerType.Arrow },
|
||||
},
|
||||
{
|
||||
id: 'e22c',
|
||||
source: '2',
|
||||
target: '2c',
|
||||
type: 'smoothstep',
|
||||
markerEnd: { type: MarkerType.Arrow },
|
||||
},
|
||||
{
|
||||
id: 'e2c2d',
|
||||
source: '2c',
|
||||
target: '2d',
|
||||
type: 'smoothstep',
|
||||
markerEnd: { type: MarkerType.Arrow },
|
||||
},
|
||||
|
||||
{
|
||||
id: 'e45',
|
||||
source: '4',
|
||||
target: '5',
|
||||
type: 'smoothstep',
|
||||
markerEnd: { type: MarkerType.Arrow },
|
||||
},
|
||||
{
|
||||
id: 'e56',
|
||||
source: '5',
|
||||
target: '6',
|
||||
type: 'smoothstep',
|
||||
markerEnd: { type: MarkerType.Arrow },
|
||||
},
|
||||
{
|
||||
id: 'e57',
|
||||
source: '5',
|
||||
target: '7',
|
||||
type: 'smoothstep',
|
||||
markerEnd: { type: MarkerType.Arrow },
|
||||
},
|
||||
];
|
||||
|
||||
const nodesAndEdges = { nodes, edges };
|
||||
|
||||
export default nodesAndEdges;
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.layoutflow .controls {
|
||||
.controls {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
+39
-10
@@ -1,8 +1,8 @@
|
||||
import { FC } from 'react';
|
||||
import React, { FC } from 'react';
|
||||
|
||||
import ReactFlow, {
|
||||
addEdge,
|
||||
Background,
|
||||
addEdge,
|
||||
Node,
|
||||
Edge,
|
||||
Connection,
|
||||
@@ -10,19 +10,47 @@ import ReactFlow, {
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
MarkerType,
|
||||
} from 'react-flow-renderer';
|
||||
MiniMap,
|
||||
} from 'reactflow';
|
||||
|
||||
import './multiflows.css';
|
||||
import styles from './multiflows.module.css';
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 }, className: 'light' },
|
||||
{ id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 }, className: 'light' },
|
||||
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 }, className: 'light' },
|
||||
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 }, className: 'light' },
|
||||
{
|
||||
id: '1',
|
||||
type: 'input',
|
||||
data: { label: 'Node 1' },
|
||||
position: { x: 250, y: 5 },
|
||||
className: 'light',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
data: { label: 'Node 2' },
|
||||
position: { x: 100, y: 100 },
|
||||
className: 'light',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
data: { label: 'Node 3' },
|
||||
position: { x: 400, y: 100 },
|
||||
className: 'light',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
data: { label: 'Node 4' },
|
||||
position: { x: 400, y: 200 },
|
||||
className: 'light',
|
||||
},
|
||||
];
|
||||
|
||||
const initialEdges: Edge[] = [
|
||||
{ id: 'e1-2', source: '1', target: '2', animated: true, markerEnd: { type: MarkerType.Arrow } },
|
||||
{
|
||||
id: 'e1-2',
|
||||
source: '1',
|
||||
target: '2',
|
||||
animated: true,
|
||||
markerEnd: { type: MarkerType.Arrow },
|
||||
},
|
||||
{ id: 'e1-3', source: '1', target: '3' },
|
||||
];
|
||||
|
||||
@@ -43,13 +71,14 @@ const Flow: FC<{ id: string }> = ({ id }) => {
|
||||
id={id}
|
||||
>
|
||||
<Background />
|
||||
<MiniMap />
|
||||
</ReactFlow>
|
||||
</ReactFlowProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const MultiFlows: FC = () => (
|
||||
<div className="react-flow__example-multiflows">
|
||||
<div className={styles.multiflows}>
|
||||
<Flow id="flow-a" />
|
||||
<Flow id="flow-b" />
|
||||
</div>
|
||||
@@ -0,0 +1,13 @@
|
||||
.multiflows {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.multiflows :global .react-flow {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.multiflows :global .react-flow:first-child {
|
||||
border-right: 2px solid #333;
|
||||
}
|
||||
+29
-13
@@ -1,24 +1,29 @@
|
||||
import { useState, MouseEvent, useCallback } from 'react';
|
||||
|
||||
import React, { useState, MouseEvent, useCallback } from 'react';
|
||||
import ReactFlow, {
|
||||
addEdge,
|
||||
Controls,
|
||||
MiniMap,
|
||||
Background,
|
||||
addEdge,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
MiniMap,
|
||||
Controls,
|
||||
Node,
|
||||
Edge,
|
||||
ReactFlowInstance,
|
||||
Connection,
|
||||
} from 'react-flow-renderer';
|
||||
} from 'reactflow';
|
||||
|
||||
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
|
||||
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
||||
const onEdgeClick = (_: MouseEvent, edge: Edge) => console.log('click', edge);
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 }, className: 'light' },
|
||||
{
|
||||
id: '1',
|
||||
type: 'input',
|
||||
data: { label: 'Node 1' },
|
||||
position: { x: 250, y: 5 },
|
||||
className: 'light',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
data: { label: 'Node 2' },
|
||||
@@ -32,7 +37,12 @@ const initialNodes: Node[] = [
|
||||
position: { x: 10, y: 50 },
|
||||
parentNode: '2',
|
||||
},
|
||||
{ id: '3', data: { label: 'Node 3' }, position: { x: 320, y: 100 }, className: 'light' },
|
||||
{
|
||||
id: '3',
|
||||
data: { label: 'Node 3' },
|
||||
position: { x: 320, y: 100 },
|
||||
className: 'light',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
data: { label: 'Node 4' },
|
||||
@@ -53,7 +63,11 @@ const initialNodes: Node[] = [
|
||||
data: { label: 'Node 4b' },
|
||||
position: { x: 15, y: 120 },
|
||||
className: 'light',
|
||||
style: { backgroundColor: 'rgba(255, 0, 255, 0.7)', height: 150, width: 270 },
|
||||
style: {
|
||||
backgroundColor: 'rgba(255, 0, 255, 0.7)',
|
||||
height: 150,
|
||||
width: 270,
|
||||
},
|
||||
parentNode: '4',
|
||||
},
|
||||
{
|
||||
@@ -88,9 +102,12 @@ const NestedFlow = () => {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
|
||||
const onConnect = useCallback((connection: Connection) => {
|
||||
setEdges((eds) => addEdge(connection, eds));
|
||||
}, []);
|
||||
const onConnect = useCallback(
|
||||
(connection: Connection) => {
|
||||
setEdges((eds) => addEdge(connection, eds));
|
||||
},
|
||||
[setEdges]
|
||||
);
|
||||
const onInit = useCallback((reactFlowInstance: ReactFlowInstance) => setRfInstance(reactFlowInstance), []);
|
||||
|
||||
const updatePos = () => {
|
||||
@@ -139,7 +156,6 @@ const NestedFlow = () => {
|
||||
onConnect={onConnect}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
className="react-flow-basic-example"
|
||||
defaultZoom={1.5}
|
||||
minZoom={0.2}
|
||||
maxZoom={4}
|
||||
onlyRenderVisibleElements={false}
|
||||
+9
-12
@@ -1,14 +1,6 @@
|
||||
import { CSSProperties } from 'react';
|
||||
import React, { CSSProperties, useCallback } from 'react';
|
||||
|
||||
import ReactFlow, {
|
||||
addEdge,
|
||||
Node,
|
||||
Position,
|
||||
Connection,
|
||||
Edge,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
} from 'react-flow-renderer';
|
||||
import ReactFlow, { addEdge, Node, Position, Connection, Edge, useNodesState, useEdgesState } from 'reactflow';
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{
|
||||
@@ -30,12 +22,17 @@ const initialNodes: Node[] = [
|
||||
|
||||
const initialEdges: Edge[] = [{ id: 'e1-2', source: '1', type: 'smoothstep', target: '2', animated: true }];
|
||||
|
||||
const buttonStyle: CSSProperties = { position: 'absolute', right: 10, top: 30, zIndex: 4 };
|
||||
const buttonStyle: CSSProperties = {
|
||||
position: 'absolute',
|
||||
right: 10,
|
||||
top: 30,
|
||||
zIndex: 4,
|
||||
};
|
||||
|
||||
const NodeTypeChangeFlow = () => {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
const onConnect = (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds));
|
||||
const onConnect = useCallback((params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)), [setEdges]);
|
||||
const changeType = () => {
|
||||
setNodes((nds) =>
|
||||
nds.map((node) => {
|
||||
+14
-7
@@ -1,5 +1,4 @@
|
||||
import { useState, CSSProperties, FC } from 'react';
|
||||
|
||||
import React, { useState, CSSProperties, FC, useCallback } from 'react';
|
||||
import ReactFlow, {
|
||||
addEdge,
|
||||
Node,
|
||||
@@ -10,7 +9,7 @@ import ReactFlow, {
|
||||
NodeTypes,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
} from 'react-flow-renderer';
|
||||
} from 'reactflow';
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{
|
||||
@@ -30,9 +29,17 @@ const initialNodes: Node[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const buttonStyle: CSSProperties = { position: 'absolute', right: 10, top: 30, zIndex: 4 };
|
||||
const buttonStyle: CSSProperties = {
|
||||
position: 'absolute',
|
||||
right: 10,
|
||||
top: 30,
|
||||
zIndex: 4,
|
||||
};
|
||||
|
||||
const nodeStyles: CSSProperties = { padding: '10px 15px', border: '1px solid #ddd' };
|
||||
const nodeStyles: CSSProperties = {
|
||||
padding: '10px 15px',
|
||||
border: '1px solid #ddd',
|
||||
};
|
||||
|
||||
const NodeA: FC<NodeProps> = () => {
|
||||
return <div style={nodeStyles}>A</div>;
|
||||
@@ -57,9 +64,9 @@ const nodeTypesObjects: NodeTypesObject = {
|
||||
|
||||
const NodeTypeChangeFlow = () => {
|
||||
const [nodeTypesId, setNodeTypesId] = useState<string>('a');
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
const [nodes, , onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||
const onConnect = (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds));
|
||||
const onConnect = useCallback((params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)), [setEdges]);
|
||||
const changeType = () => setNodeTypesId((nt) => (nt === 'a' ? 'b' : 'a'));
|
||||
|
||||
return (
|
||||
+36
-10
@@ -1,9 +1,6 @@
|
||||
import { MouseEvent as ReactMouseEvent, CSSProperties, useCallback } from 'react';
|
||||
import React, { MouseEvent as ReactMouseEvent, CSSProperties, useCallback } from 'react';
|
||||
import ReactFlow, {
|
||||
addEdge,
|
||||
MiniMap,
|
||||
Controls,
|
||||
Background,
|
||||
Node,
|
||||
Viewport,
|
||||
SnapGrid,
|
||||
@@ -13,7 +10,10 @@ import ReactFlow, {
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
OnSelectionChangeParams,
|
||||
} from 'react-flow-renderer';
|
||||
Controls,
|
||||
Background,
|
||||
MiniMap,
|
||||
} from 'reactflow';
|
||||
|
||||
const onNodeDragStart = (_: ReactMouseEvent, node: Node, nodes: Node[]) => console.log('drag start', node, nodes);
|
||||
const onNodeDrag = (_: ReactMouseEvent, node: Node, nodes: Node[]) => console.log('drag', node, nodes);
|
||||
@@ -45,6 +45,7 @@ const onEdgeMouseLeave = (_: ReactMouseEvent, edge: Edge) => console.log('edge m
|
||||
const onEdgeDoubleClick = (_: ReactMouseEvent, edge: Edge) => console.log('edge double click', edge);
|
||||
const onNodesDelete = (nodes: Node[]) => console.log('nodes delete', nodes);
|
||||
const onEdgesDelete = (edges: Edge[]) => console.log('edges delete', edges);
|
||||
const onPaneMouseMove = (e: ReactMouseEvent) => console.log('pane move', e.clientX, e.clientY);
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{
|
||||
@@ -80,7 +81,12 @@ const initialNodes: Node[] = [
|
||||
),
|
||||
},
|
||||
position: { x: 400, y: 100 },
|
||||
style: { background: '#D6D5E6', color: '#333', border: '1px solid #222138', width: 180 },
|
||||
style: {
|
||||
background: '#D6D5E6',
|
||||
color: '#333',
|
||||
border: '1px solid #222138',
|
||||
width: 180,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
@@ -113,21 +119,40 @@ const initialNodes: Node[] = [
|
||||
data: {
|
||||
label: (
|
||||
<>
|
||||
An <strong>output node</strong>
|
||||
An <strong>output node (not deletable)</strong>
|
||||
</>
|
||||
),
|
||||
},
|
||||
position: { x: 100, y: 480 },
|
||||
deletable: false,
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
type: 'output',
|
||||
data: { label: 'Another output node' },
|
||||
position: { x: 400, y: 450 },
|
||||
},
|
||||
{ id: '7', type: 'output', data: { label: 'Another output node' }, position: { x: 400, y: 450 } },
|
||||
];
|
||||
|
||||
const initialEdges: Edge[] = [
|
||||
{ id: 'e1-2', source: '1', target: '2', label: 'this is an edge label' },
|
||||
{ id: 'e1-3', source: '1', target: '3' },
|
||||
{ id: 'e3-4', source: '3', target: '4', animated: true, label: 'animated edge' },
|
||||
{
|
||||
id: 'e3-4',
|
||||
source: '3',
|
||||
target: '4',
|
||||
animated: true,
|
||||
label: 'animated edge',
|
||||
},
|
||||
{ id: 'e4-5', source: '4', target: '5', label: 'edge with arrow head' },
|
||||
{ id: 'e5-6', source: '5', target: '6', type: 'smoothstep', label: 'smooth step edge' },
|
||||
{
|
||||
id: 'e5-6',
|
||||
source: '5',
|
||||
target: '6',
|
||||
type: 'smoothstep',
|
||||
deletable: false,
|
||||
label: 'smooth step edge (not deletable)',
|
||||
},
|
||||
{
|
||||
id: 'e5-7',
|
||||
source: '5',
|
||||
@@ -200,6 +225,7 @@ const OverviewFlow = () => {
|
||||
maxZoom={Infinity}
|
||||
onNodesDelete={onNodesDelete}
|
||||
onEdgesDelete={onEdgesDelete}
|
||||
onPaneMouseMove={onPaneMouseMove}
|
||||
>
|
||||
<MiniMap nodeStrokeColor={nodeStrokeColor} nodeColor={nodeColor} nodeBorderRadius={2} />
|
||||
<Controls />
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user