@@ -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
|
stats.html
|
||||||
.eslintcache
|
.eslintcache
|
||||||
.idea
|
.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/"
|
registry="https://registry.npmjs.com/"
|
||||||
legacy-peer-deps = true
|
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 :)
|
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.
|
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`
|
1. Install dependencies `pnpm install`
|
||||||
2. Run library start task `npm start` and example start task `cd example && npm start`
|
2. Start dev server `pnpm dev`
|
||||||
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
|
3. Test your changes with the existing examples or 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
|
4. Run tests `pnpm test` and add new new tests if you are introducing a new feature
|
||||||
|
|||||||
@@ -3,19 +3,18 @@
|
|||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
### Wire Your Ideas With React Flow!
|
|
||||||
A highly customizable React component for building interactive graphs and node-based editors.
|
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)
|
[🚀 Getting Started](https://reactflow.dev/docs/quickstart/) | [📖 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)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
## Key Features
|
## Key Features
|
||||||
|
|
||||||
@@ -26,28 +25,52 @@ A highly customizable React component for building interactive graphs and node-b
|
|||||||
- **Plugin Components:** [Background](https://reactflow.dev/docs/api/plugin-components/background), [MiniMap](https://reactflow.dev/docs/api/plugin-components/minimap) and [Controls](https://reactflow.dev/docs/api/plugin-components/controls)
|
- **Plugin Components:** [Background](https://reactflow.dev/docs/api/plugin-components/background), [MiniMap](https://reactflow.dev/docs/api/plugin-components/minimap) and [Controls](https://reactflow.dev/docs/api/plugin-components/controls)
|
||||||
- **Reliable**: Written in [Typescript](https://www.typescriptlang.org/) and tested with [cypress](https://www.cypress.io/)
|
- **Reliable**: Written in [Typescript](https://www.typescriptlang.org/) and tested with [cypress](https://www.cypress.io/)
|
||||||
|
|
||||||
## Commercial Usage / Attribution
|
## Commercial Usage
|
||||||
|
|
||||||
React Flow includes a small attribution that links to the React Flow website. **We expect companies who are using React Flow commercially to subscribe to [React Flow Pro](https://pro.reactflow.dev/pricing) if they want to remove the attribution.** By subscribing you get access to other exclusive services like advanced examples, individual support or prioritized bug reports. In non-commercial applications you may hide the attribution without subscribing but are welcome to [sponsor us on Github](https://github.com/sponsors/wbkd).
|
**Are you using React Flow for a personal project?** Great! No sponsorship needed, you can support us by reporting any bugs you find, sending us screenshots of your projects, and starring us on Github 🌟
|
||||||
|
|
||||||
You can find more information in our [React Flow Pro FAQs](https://pro.reactflow.dev/faq).
|
**Are you using React Flow at your organization and making money from it?** Awesome! We rely on your support to keep React Flow developed and maintained under an MIT License, just how we like it. You can do that on the [React Flow Pro website](https://pro.reactflow.dev) or through [Github Sponsors](https://github.com/sponsors/wbkd).
|
||||||
|
|
||||||
|
You can find more information in our [React Flow Pro FAQs](https://pro.reactflow.dev/info).
|
||||||
|
|
||||||
## Installation
|
## 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
|
```bash
|
||||||
npm install react-flow-renderer
|
npm install reactflow
|
||||||
```
|
```
|
||||||
|
|
||||||
## Quick Start
|
## Quickstart
|
||||||
|
|
||||||
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).
|
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 the full [API reference](https://reactflow.dev/docs/api/react-flow-props).
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
import ReactFlow, { MiniMap, Controls } from 'react-flow-renderer';
|
import { useCallback } from 'react';
|
||||||
|
import ReactFlow, {
|
||||||
|
MiniMap,
|
||||||
|
Controls,
|
||||||
|
Background,
|
||||||
|
useNodesState,
|
||||||
|
useEdgesState,
|
||||||
|
addEdge,
|
||||||
|
} from 'reactflow';
|
||||||
|
|
||||||
|
import 'reactflow/dist/style.css';
|
||||||
|
|
||||||
|
const initialNodes = [
|
||||||
|
{ id: '1', position: { x: 0, y: 0 }, data: { label: '1' } },
|
||||||
|
{ id: '2', position: { x: 0, y: 100 }, data: { label: '2' } },
|
||||||
|
];
|
||||||
|
|
||||||
|
const initialEdges = [{ id: 'e1-2', source: '1', target: '2' }];
|
||||||
|
|
||||||
|
function Flow() {
|
||||||
|
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||||
|
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||||
|
|
||||||
|
const onConnect = useCallback((params) => setEdges((eds) => addEdge(params, eds)), [setEdges]);
|
||||||
|
|
||||||
function Flow({ nodes, edges, onNodesChange, onEdgesChange, onConnect }) {
|
|
||||||
return (
|
return (
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
@@ -58,6 +81,7 @@ function Flow({ nodes, edges, onNodesChange, onEdgesChange, onConnect }) {
|
|||||||
>
|
>
|
||||||
<MiniMap />
|
<MiniMap />
|
||||||
<Controls />
|
<Controls />
|
||||||
|
<Background />
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -65,52 +89,40 @@ function Flow({ nodes, edges, onNodesChange, onEdgesChange, onConnect }) {
|
|||||||
|
|
||||||
## Development
|
## 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:
|
For local development, you can use `pnpm dev`.
|
||||||
|
|
||||||
```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.
|
|
||||||
|
|
||||||
## Testing
|
## 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
|
```sh
|
||||||
npm run test
|
pnpm test
|
||||||
```
|
```
|
||||||
|
|
||||||
## Maintainers
|
## 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:
|
React Flow is the full-time project of Moritz and Christopher of [webkid](https://webkid.io/), based in Berlin. If you need help or want to talk to us about a collaboration, reach out through our [contact form](https://pro.reactflow.dev/contact) or by joining the [React Flow Discord Server](https://discord.gg/Bqt6xrs).
|
||||||
|
|
||||||
* Moritz Klack • [Twitter](https://twitter.com/moklick) • [Github](https://github.com/moklick)
|
- 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)
|
- 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).
|
Any support you provide goes directly towards the development and maintenance of React Flow, allowing us to continue to operate as an independent company, working on what we think is best for React Flow as an open-source library.
|
||||||
|
|
||||||
## Community Packages
|
## Community Packages
|
||||||
|
|
||||||
* [useUndoable](https://github.com/Infinium8/useUndoable) - Hook for undo/redo functionality with an explicit React Flow example
|
- [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
|
- [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
|
- [Feliz.ReactFlow](https://github.com/tforkmann/Feliz.ReactFlow) - Feliz React Bindings for React Flow
|
||||||
|
|
||||||
## Credits
|
## 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-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
|
- [d3-drag](https://github.com/d3/d3-drag) - used for making the nodes draggable
|
||||||
* [zustand](https://github.com/pmndrs/zustand) - internal state management
|
- [zustand](https://github.com/pmndrs/zustand) - internal state management
|
||||||
|
|
||||||
## License
|
## 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 = [
|
const nodes: 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: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 } },
|
||||||
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 } },
|
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 } },
|
||||||
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 } },
|
{ id: '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-2', source: '1', target: '2', animated: true },
|
||||||
{ id: 'e1-3', source: '1', target: '3' },
|
{ id: 'e1-3', source: '1', target: '3' },
|
||||||
{ id: 'e2-3', source: '2', target: '3' },
|
{ id: 'e2-3', source: '2', target: '3' },
|
||||||
@@ -43,25 +48,26 @@ describe('Graph Utils Testing', () => {
|
|||||||
|
|
||||||
describe('tests addEdge function', () => {
|
describe('tests addEdge function', () => {
|
||||||
it('adds edge', () => {
|
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);
|
const nextEdges = addEdge(newEdge, edges);
|
||||||
|
|
||||||
expect(nextEdges.length).to.be.equal(edges.length + 1);
|
expect(nextEdges.length).to.be.equal(edges.length + 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('tries to add existing edge', () => {
|
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);
|
const nextEdges = addEdge(newEdge, edges);
|
||||||
|
|
||||||
expect(nextEdges.length).to.be.equal(edges.length);
|
expect(nextEdges.length).to.be.equal(edges.length);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('tries to add invalid edge', () => {
|
it('tries to add invalid edge', () => {
|
||||||
const newEdge = { nosource: '1', notarget: '3' };
|
// @ts-ignore
|
||||||
|
const newEdge: Edge = { nosource: '1', notarget: '3' };
|
||||||
|
|
||||||
try {
|
try {
|
||||||
addEdge(newEdge, edges);
|
addEdge(newEdge, edges);
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
console.log(e.message);
|
console.log(e.message);
|
||||||
|
|
||||||
expect(e.message).to.be.equal("Can't create edge. An edge needs a source and a target.");
|
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', () => {
|
describe('Basic Flow Rendering', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.visit('/basic');
|
cy.visit('/');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders a flow with three nodes', () => {
|
it('renders a flow with three nodes', () => {
|
||||||
@@ -95,7 +95,7 @@ describe('Basic Flow Rendering', () => {
|
|||||||
it('drags a node', () => {
|
it('drags a node', () => {
|
||||||
const styleBeforeDrag = Cypress.$('.react-flow__node:first').css('transform');
|
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');
|
const styleAfterDrag = $el.css('transform');
|
||||||
expect(styleBeforeDrag).to.not.equal(styleAfterDrag);
|
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', () => {
|
describe('DragHandle Flow Rendering', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.visit('/draghandle');
|
cy.visit('/DragHandle');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders a flow with a node', () => {
|
it('renders a flow with a node', () => {
|
||||||
@@ -28,3 +28,5 @@ describe('DragHandle Flow Rendering', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export {};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
describe('Empty Flow Rendering', () => {
|
describe('Empty Flow Rendering', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.visit('/empty');
|
cy.visit('/Empty');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders an empty flow', () => {
|
it('renders an empty flow', () => {
|
||||||
@@ -10,12 +10,15 @@ describe('Empty Flow Rendering', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('renders empty selection', () => {
|
it('renders empty selection', () => {
|
||||||
|
cy.get('.react-flow__renderer').click();
|
||||||
cy.get('body')
|
cy.get('body')
|
||||||
.type('{shift}', { release: false })
|
.type('{shift}', { release: false })
|
||||||
|
.wait(50)
|
||||||
.get('.react-flow__selectionpane')
|
.get('.react-flow__selectionpane')
|
||||||
.trigger('mousedown', 'topLeft', { which: 1, force: true })
|
.trigger('mousedown', 400, 50, { which: 1, force: true })
|
||||||
.trigger('mousemove', 'bottomLeft', { which: 1 })
|
.trigger('mousemove', 200, 200, { which: 1 })
|
||||||
.trigger('mouseup', 'bottomLeft', { force: true });
|
.wait(50)
|
||||||
|
.trigger('mouseup', 200, 200, { force: true });
|
||||||
|
|
||||||
cy.get('body').type('{shift}', { release: 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);
|
cy.get('.react-flow__edge').should('have.length', 1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export {};
|
||||||
+3
-1
@@ -1,6 +1,6 @@
|
|||||||
describe('Hidden Flow Rendering', () => {
|
describe('Hidden Flow Rendering', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.visit('/hidden');
|
cy.visit('/Hidden');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders empty flow', () => {
|
it('renders empty flow', () => {
|
||||||
@@ -30,3 +30,5 @@ describe('Hidden Flow Rendering', () => {
|
|||||||
cy.get('.react-flow__minimap-node').should('not.exist');
|
cy.get('.react-flow__minimap-node').should('not.exist');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export {};
|
||||||
+3
-1
@@ -1,6 +1,6 @@
|
|||||||
describe('Interaction Flow Rendering', () => {
|
describe('Interaction Flow Rendering', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.visit('/interaction');
|
cy.visit('/Interaction');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders initial flow', () => {
|
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', () => {
|
it('has same number of nodes as the pane', () => {
|
||||||
const paneNodes = Cypress.$('.react-flow__node').length;
|
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);
|
expect(paneNodes).equal(minimapNodes);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('changes zoom level', () => {
|
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');
|
const maskPathBeforeZoom = Cypress.$('.react-flow__minimap-mask').attr('d');
|
||||||
|
|
||||||
cy.get('.react-flow__pane')
|
cy.get('.react-flow__pane')
|
||||||
.trigger('wheel', 'topLeft', { deltaY: -200 })
|
.trigger('wheel', 'topLeft', { deltaY: -200 })
|
||||||
.wait(50)
|
.wait(50)
|
||||||
.then(() => {
|
.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');
|
const maskPathAfterZoom = Cypress.$('.react-flow__minimap-mask').attr('d');
|
||||||
|
|
||||||
expect(viewBoxBeforeZoom).to.not.equal(viewBoxAfterZoom);
|
expect(viewBoxBeforeZoom).to.not.equal(viewBoxAfterZoom);
|
||||||
@@ -49,7 +50,7 @@ describe('Minimap Testing', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('changes node positions via pane drag', () => {
|
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');
|
const maskPathBeforeDrag = Cypress.$('.react-flow__minimap-mask').attr('d');
|
||||||
|
|
||||||
// for d3 we have to pass the window to the event
|
// for d3 we have to pass the window to the event
|
||||||
@@ -61,7 +62,7 @@ describe('Minimap Testing', () => {
|
|||||||
.wait(50)
|
.wait(50)
|
||||||
.trigger('mouseup', { force: true, view: win })
|
.trigger('mouseup', { force: true, view: win })
|
||||||
.then(() => {
|
.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');
|
const maskPathAfterDrag = Cypress.$('.react-flow__minimap-mask').attr('d');
|
||||||
|
|
||||||
expect(viewBoxBeforeDrag).to.not.equal(viewBoxAfterDrag);
|
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,34 @@
|
|||||||
|
{
|
||||||
|
"name": "@reactflow/examples",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite --port 3000 --open",
|
||||||
|
"serve": "vite serve --port 3000",
|
||||||
|
"test:dev": "cypress open",
|
||||||
|
"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.8.3",
|
||||||
|
"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 { ChangeEvent } from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
|
||||||
import { BrowserRouter, Route, Routes, useLocation, useNavigate } from 'react-router-dom';
|
import { BrowserRouter, Route, Routes, useLocation, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import Basic from './Basic';
|
import Basic from '../examples/Basic';
|
||||||
import ControlledUncontrolled from './ControlledUncontrolled';
|
import Backgrounds from '../examples/Backgrounds';
|
||||||
import CustomConnectionLine from './CustomConnectionLine';
|
import ControlledUncontrolled from '../examples/ControlledUncontrolled';
|
||||||
import CustomNode from './CustomNode';
|
import CustomConnectionLine from '../examples/CustomConnectionLine';
|
||||||
import DefaultNodes from './DefaultNodes';
|
import CustomNode from '../examples/CustomNode';
|
||||||
import DragHandle from './DragHandle';
|
import DefaultNodes from '../examples/DefaultNodes';
|
||||||
import DragNDrop from './DragNDrop';
|
import DragHandle from '../examples/DragHandle';
|
||||||
import Edges from './Edges';
|
import DragNDrop from '../examples/DragNDrop';
|
||||||
import EdgeTypes from './EdgeTypes';
|
import Edges from '../examples/Edges';
|
||||||
import Empty from './Empty';
|
import EdgeTypes from '../examples/EdgeTypes';
|
||||||
import FloatingEdges from './FloatingEdges';
|
import Empty from '../examples/Empty';
|
||||||
import Hidden from './Hidden';
|
import FloatingEdges from '../examples/FloatingEdges';
|
||||||
import './index.css';
|
import Hidden from '../examples/Hidden';
|
||||||
import Interaction from './Interaction';
|
import Interaction from '../examples/Interaction';
|
||||||
import Layouting from './Layouting';
|
import Layouting from '../examples/Layouting';
|
||||||
import MultiFlows from './MultiFlows';
|
import MultiFlows from '../examples/MultiFlows';
|
||||||
import NestedNodes from './NestedNodes';
|
import NestedNodes from '../examples/NestedNodes';
|
||||||
import NodeTypeChange from './NodeTypeChange';
|
import NodeTypeChange from '../examples/NodeTypeChange';
|
||||||
import NodeTypesObjectChange from './NodeTypesObjectChange';
|
import NodeTypesObjectChange from '../examples/NodeTypesObjectChange';
|
||||||
import Overview from './Overview';
|
import Overview from '../examples/Overview';
|
||||||
import Provider from './Provider';
|
import Provider from '../examples/Provider';
|
||||||
import SaveRestore from './SaveRestore';
|
import SaveRestore from '../examples/SaveRestore';
|
||||||
import Stress from './Stress';
|
import Stress from '../examples/Stress';
|
||||||
import Subflow from './Subflow';
|
import Subflow from '../examples/Subflow';
|
||||||
import SwitchFlow from './Switch';
|
import SwitchFlow from '../examples/Switch';
|
||||||
import TouchDevice from './TouchDevice';
|
import TouchDevice from '../examples/TouchDevice';
|
||||||
import Undirectional from './Undirectional';
|
import Undirectional from '../examples/Undirectional';
|
||||||
import UpdatableEdge from './UpdatableEdge';
|
import UpdatableEdge from '../examples/UpdatableEdge';
|
||||||
import UpdateNode from './UpdateNode';
|
import UpdateNode from '../examples/UpdateNode';
|
||||||
import UseUpdateNodeInternals from './UseUpdateNodeInternals';
|
import UseUpdateNodeInternals from '../examples/UseUpdateNodeInternals';
|
||||||
import UseReactFlow from './UseReactFlow';
|
import UseReactFlow from '../examples/UseReactFlow';
|
||||||
import Validation from './Validation';
|
import Validation from '../examples/Validation';
|
||||||
import UseKeyPress from './UseKeyPress';
|
import UseKeyPress from '../examples/UseKeyPress';
|
||||||
|
import EdgeRouting from '../examples/EdgeRouting';
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
component: Overview,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/basic',
|
|
||||||
component: Basic,
|
component: Basic,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/default-nodes',
|
path: '/backgrounds',
|
||||||
component: DefaultNodes,
|
component: Backgrounds,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/controlled-uncontrolled',
|
||||||
|
component: ControlledUncontrolled,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/custom-connectionline',
|
path: '/custom-connectionline',
|
||||||
@@ -57,6 +57,10 @@ const routes = [
|
|||||||
path: '/custom-node',
|
path: '/custom-node',
|
||||||
component: CustomNode,
|
component: CustomNode,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/default-nodes',
|
||||||
|
component: DefaultNodes,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/draghandle',
|
path: '/draghandle',
|
||||||
component: DragHandle,
|
component: DragHandle,
|
||||||
@@ -73,6 +77,10 @@ const routes = [
|
|||||||
path: '/edge-types',
|
path: '/edge-types',
|
||||||
component: EdgeTypes,
|
component: EdgeTypes,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/edge-routing',
|
||||||
|
component: EdgeRouting,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/empty',
|
path: '/empty',
|
||||||
component: Empty,
|
component: Empty,
|
||||||
@@ -109,6 +117,10 @@ const routes = [
|
|||||||
path: '/nodetypesobject-change',
|
path: '/nodetypesobject-change',
|
||||||
component: NodeTypesObjectChange,
|
component: NodeTypesObjectChange,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/overview',
|
||||||
|
component: Overview,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/provider',
|
path: '/provider',
|
||||||
component: Provider,
|
component: Provider,
|
||||||
@@ -157,10 +169,6 @@ const routes = [
|
|||||||
path: '/validation',
|
path: '/validation',
|
||||||
component: Validation,
|
component: Validation,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/controlled-uncontrolled',
|
|
||||||
component: ControlledUncontrolled,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/use-key-press',
|
path: '/use-key-press',
|
||||||
component: UseKeyPress,
|
component: UseKeyPress,
|
||||||
@@ -180,7 +188,7 @@ const Header = () => {
|
|||||||
<select defaultValue={location.pathname} onChange={onChange}>
|
<select defaultValue={location.pathname} onChange={onChange}>
|
||||||
{routes.map((route) => (
|
{routes.map((route) => (
|
||||||
<option value={route.path} key={route.path}>
|
<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>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
@@ -188,10 +196,7 @@ const Header = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const container = document.getElementById('root');
|
export default () => (
|
||||||
const root = createRoot(container!);
|
|
||||||
|
|
||||||
root.render(
|
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Header />
|
<Header />
|
||||||
<Routes>
|
<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, {
|
import ReactFlow, {
|
||||||
ReactFlowProvider,
|
MiniMap,
|
||||||
Background,
|
Background,
|
||||||
BackgroundVariant,
|
BackgroundVariant,
|
||||||
|
Controls,
|
||||||
|
ReactFlowProvider,
|
||||||
Node,
|
Node,
|
||||||
Edge,
|
Edge,
|
||||||
useReactFlow,
|
useReactFlow,
|
||||||
} from 'react-flow-renderer';
|
} from 'reactflow';
|
||||||
|
|
||||||
const onNodeDrag = (_: MouseEvent, node: Node) => console.log('drag', node);
|
const onNodeDrag = (_: MouseEvent, node: Node) => console.log('drag', node);
|
||||||
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
|
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
|
||||||
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
||||||
|
|
||||||
const initialNodes: 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: '1',
|
||||||
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 }, className: 'light' },
|
type: 'input',
|
||||||
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 }, className: 'light' },
|
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[] = [
|
const initialEdges: Edge[] = [
|
||||||
@@ -71,6 +93,8 @@ const BasicFlow = () => {
|
|||||||
selectNodesOnDrag={false}
|
selectNodesOnDrag={false}
|
||||||
>
|
>
|
||||||
<Background variant={BackgroundVariant.Lines} />
|
<Background variant={BackgroundVariant.Lines} />
|
||||||
|
<MiniMap />
|
||||||
|
<Controls />
|
||||||
|
|
||||||
<div style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}>
|
<div style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}>
|
||||||
<button onClick={resetTransform} style={{ marginRight: 5 }}>
|
<button onClick={resetTransform} style={{ marginRight: 5 }}>
|
||||||
+29
-7
@@ -1,19 +1,41 @@
|
|||||||
|
import React from 'react';
|
||||||
import ReactFlow, {
|
import ReactFlow, {
|
||||||
useReactFlow,
|
useReactFlow,
|
||||||
Background,
|
|
||||||
BackgroundVariant,
|
|
||||||
Node,
|
Node,
|
||||||
Edge,
|
Edge,
|
||||||
ReactFlowProvider,
|
ReactFlowProvider,
|
||||||
useNodesState,
|
useNodesState,
|
||||||
useEdgesState,
|
useEdgesState,
|
||||||
} from 'react-flow-renderer';
|
Background,
|
||||||
|
BackgroundVariant,
|
||||||
|
} from 'reactflow';
|
||||||
|
|
||||||
const defaultNodes: Node[] = [
|
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: '1',
|
||||||
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 }, className: 'light' },
|
type: 'input',
|
||||||
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 }, className: 'light' },
|
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[] = [
|
const defaultEdges: Edge[] = [
|
||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
import React, { FC } from 'react';
|
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 (
|
return (
|
||||||
<g>
|
<g>
|
||||||
<path
|
<path
|
||||||
@@ -9,7 +9,7 @@ const ConnectionLine: FC<ConnectionLineComponentProps> = ({ sourceX, sourceY, ta
|
|||||||
stroke="#222"
|
stroke="#222"
|
||||||
strokeWidth={1.5}
|
strokeWidth={1.5}
|
||||||
className="animated"
|
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} />
|
<circle cx={toX} cy={toY} fill="#fff" r={3} stroke="#222" strokeWidth={1.5} />
|
||||||
</g>
|
</g>
|
||||||
+12
-5
@@ -1,18 +1,25 @@
|
|||||||
import { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import ReactFlow, {
|
import ReactFlow, {
|
||||||
Node,
|
Node,
|
||||||
addEdge,
|
addEdge,
|
||||||
Background,
|
|
||||||
BackgroundVariant,
|
|
||||||
Connection,
|
Connection,
|
||||||
Edge,
|
Edge,
|
||||||
useNodesState,
|
useNodesState,
|
||||||
useEdgesState,
|
useEdgesState,
|
||||||
} from 'react-flow-renderer';
|
Background,
|
||||||
|
BackgroundVariant,
|
||||||
|
} from 'reactflow';
|
||||||
|
|
||||||
import ConnectionLine from './ConnectionLine';
|
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 initialEdges: Edge[] = [];
|
||||||
|
|
||||||
const ConnectionLineFlow = () => {
|
const ConnectionLineFlow = () => {
|
||||||
+17
-4
@@ -1,14 +1,27 @@
|
|||||||
import React, { memo, FC, CSSProperties } from 'react';
|
import React, { memo, FC, CSSProperties, useCallback } from 'react';
|
||||||
|
import { Handle, Position, NodeProps, Connection, Edge, useOnViewportChange, Viewport } from 'reactflow';
|
||||||
import { Handle, Position, NodeProps, Connection, Edge } from 'react-flow-renderer';
|
|
||||||
|
|
||||||
const targetHandleStyle: CSSProperties = { background: '#555' };
|
const targetHandleStyle: CSSProperties = { background: '#555' };
|
||||||
const sourceHandleStyleA: CSSProperties = { ...targetHandleStyle, top: 10 };
|
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 onConnect = (params: Connection | Edge) => console.log('handle onConnect', params);
|
||||||
|
|
||||||
const ColorSelectorNode: FC<NodeProps> = ({ data, isConnectable }) => {
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<Handle type="target" position={Position.Left} style={targetHandleStyle} onConnect={onConnect} />
|
<Handle type="target" position={Position.Left} style={targetHandleStyle} onConnect={onConnect} />
|
||||||
+31
-10
@@ -1,10 +1,8 @@
|
|||||||
import { useState, useEffect, MouseEvent } from 'react';
|
import React, { useState, useEffect, MouseEvent, ChangeEvent, useCallback } from 'react';
|
||||||
import { ChangeEvent } from 'react';
|
|
||||||
|
|
||||||
import ReactFlow, {
|
import ReactFlow, {
|
||||||
addEdge,
|
|
||||||
MiniMap,
|
MiniMap,
|
||||||
Controls,
|
Controls,
|
||||||
|
addEdge,
|
||||||
Node,
|
Node,
|
||||||
ReactFlowInstance,
|
ReactFlowInstance,
|
||||||
Position,
|
Position,
|
||||||
@@ -12,7 +10,7 @@ import ReactFlow, {
|
|||||||
Connection,
|
Connection,
|
||||||
useNodesState,
|
useNodesState,
|
||||||
useEdgesState,
|
useEdgesState,
|
||||||
} from 'react-flow-renderer';
|
} from 'reactflow';
|
||||||
|
|
||||||
import ColorSelectorNode from './ColorSelectorNode';
|
import ColorSelectorNode from './ColorSelectorNode';
|
||||||
|
|
||||||
@@ -93,14 +91,37 @@ const CustomNodeFlow = () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
setEdges([
|
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: 'e1-2',
|
||||||
{ id: 'e2b-4', source: '2', sourceHandle: 'b', target: '4', animated: true, style: { stroke: '#fff' } },
|
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) =>
|
const onConnect = useCallback(
|
||||||
setEdges((eds) => addEdge({ ...connection, animated: true, style: { stroke: '#fff' } }, eds));
|
(connection: Connection) =>
|
||||||
|
setEdges((eds) => addEdge({ ...connection, animated: true, style: { stroke: '#fff' } }, eds)),
|
||||||
|
[setEdges]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
+27
-12
@@ -1,17 +1,32 @@
|
|||||||
import ReactFlow, {
|
import React from 'react';
|
||||||
useReactFlow,
|
import ReactFlow, { useReactFlow, Node, Edge, ReactFlowProvider, Background, BackgroundVariant } from 'reactflow';
|
||||||
Background,
|
|
||||||
BackgroundVariant,
|
|
||||||
Node,
|
|
||||||
Edge,
|
|
||||||
ReactFlowProvider,
|
|
||||||
} from 'react-flow-renderer';
|
|
||||||
|
|
||||||
const defaultNodes: Node[] = [
|
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: '1',
|
||||||
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 }, className: 'light' },
|
type: 'input',
|
||||||
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 }, className: 'light' },
|
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[] = [
|
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);
|
const onConnect = (params: Connection | Edge) => console.log('handle onConnect', params);
|
||||||
|
|
||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
import { MouseEvent } from 'react';
|
import React, { MouseEvent } from 'react';
|
||||||
import ReactFlow, { Node, Edge, useNodesState, useEdgesState } from 'react-flow-renderer';
|
import ReactFlow, { Node, Edge, useNodesState, useEdgesState } from 'reactflow';
|
||||||
|
|
||||||
import DragHandleNode from './DragHandleNode';
|
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%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dndflow aside {
|
.aside {
|
||||||
border-right: 1px solid #eee;
|
border-right: 1px solid #eee;
|
||||||
padding: 15px 10px;
|
padding: 15px 10px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
background: #fcfcfc;
|
background: #fcfcfc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dndflow aside > * {
|
.aside > * {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dndflow aside .description {
|
.description {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dndflow .reactflow-wrapper {
|
.wrapper {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
@media screen and (min-width: 768px) {
|
||||||
.dndflow {
|
.aside {
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dndflow aside {
|
|
||||||
width: 20%;
|
width: 20%;
|
||||||
max-width: 180px;
|
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, {
|
import ReactFlow, {
|
||||||
ReactFlowProvider,
|
ReactFlowProvider,
|
||||||
addEdge,
|
addEdge,
|
||||||
Controls,
|
|
||||||
ReactFlowInstance,
|
ReactFlowInstance,
|
||||||
Connection,
|
Connection,
|
||||||
Edge,
|
Edge,
|
||||||
Node,
|
Node,
|
||||||
useNodesState,
|
useNodesState,
|
||||||
useEdgesState,
|
useEdgesState,
|
||||||
} from 'react-flow-renderer';
|
Controls,
|
||||||
|
NodeOrigin,
|
||||||
|
} from 'reactflow';
|
||||||
|
|
||||||
import Sidebar from './Sidebar';
|
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) => {
|
const onDragOver = (event: DragEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -25,6 +33,8 @@ const onDragOver = (event: DragEvent) => {
|
|||||||
let id = 0;
|
let id = 0;
|
||||||
const getId = () => `dndnode_${id++}`;
|
const getId = () => `dndnode_${id++}`;
|
||||||
|
|
||||||
|
const nodeOrigin: NodeOrigin = [0.5, 0.5];
|
||||||
|
|
||||||
const DnDFlow = () => {
|
const DnDFlow = () => {
|
||||||
const [reactFlowInstance, setReactFlowInstance] = useState<ReactFlowInstance>();
|
const [reactFlowInstance, setReactFlowInstance] = useState<ReactFlowInstance>();
|
||||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||||
@@ -38,7 +48,10 @@ const DnDFlow = () => {
|
|||||||
|
|
||||||
if (reactFlowInstance) {
|
if (reactFlowInstance) {
|
||||||
const type = event.dataTransfer.getData('application/reactflow');
|
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 = {
|
const newNode: Node = {
|
||||||
id: getId(),
|
id: getId(),
|
||||||
type,
|
type,
|
||||||
@@ -51,9 +64,9 @@ const DnDFlow = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dndflow">
|
<div className={styles.dndflow}>
|
||||||
<ReactFlowProvider>
|
<ReactFlowProvider>
|
||||||
<div className="reactflow-wrapper">
|
<div className={styles.wrapper}>
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
edges={edges}
|
edges={edges}
|
||||||
@@ -63,6 +76,7 @@ const DnDFlow = () => {
|
|||||||
onInit={onInit}
|
onInit={onInit}
|
||||||
onDrop={onDrop}
|
onDrop={onDrop}
|
||||||
onDragOver={onDragOver}
|
onDragOver={onDragOver}
|
||||||
|
nodeOrigin={nodeOrigin}
|
||||||
>
|
>
|
||||||
<Controls />
|
<Controls />
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
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',
|
||||||
|
pathOptions: {
|
||||||
|
offset: 30,
|
||||||
|
},
|
||||||
|
interactionWidth: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'e3-4',
|
||||||
|
source: '3',
|
||||||
|
target: '4',
|
||||||
|
pathOptions: {
|
||||||
|
borderRadius: 2,
|
||||||
|
},
|
||||||
|
interactionWidth: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: 'e4-5',
|
||||||
|
source: '5',
|
||||||
|
target: '6',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: 'e7-8',
|
||||||
|
source: '7',
|
||||||
|
target: '8',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const defaultEdgeOptions = {
|
||||||
|
label: 'Edge Label',
|
||||||
|
type: 'smoothstep',
|
||||||
|
markerEnd: {
|
||||||
|
type: MarkerType.ArrowClosed,
|
||||||
|
},
|
||||||
|
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
|
* Example for checking the different edge types and source and target positions
|
||||||
*/
|
*/
|
||||||
|
import React, { useCallback } from 'react';
|
||||||
|
|
||||||
import ReactFlow, {
|
import ReactFlow, {
|
||||||
addEdge,
|
|
||||||
MiniMap,
|
|
||||||
Controls,
|
|
||||||
Background,
|
Background,
|
||||||
|
Controls,
|
||||||
|
MiniMap,
|
||||||
|
addEdge,
|
||||||
ReactFlowInstance,
|
ReactFlowInstance,
|
||||||
Connection,
|
Connection,
|
||||||
Edge,
|
Edge,
|
||||||
useNodesState,
|
useNodesState,
|
||||||
useEdgesState,
|
useEdgesState,
|
||||||
} from 'react-flow-renderer';
|
} from 'reactflow';
|
||||||
|
|
||||||
import { getElements } from './utils';
|
import { getElements } from './utils';
|
||||||
|
|
||||||
const onInit = (reactFlowInstance: ReactFlowInstance) => {
|
const onInit = (reactFlowInstance: ReactFlowInstance) => {
|
||||||
@@ -28,7 +30,7 @@ const deleteKeyCode = ['AltLeft+KeyD', 'Backspace'];
|
|||||||
const EdgeTypesFlow = () => {
|
const EdgeTypesFlow = () => {
|
||||||
const [nodes, , onNodesChange] = useNodesState(initialNodes);
|
const [nodes, , onNodesChange] = useNodesState(initialNodes);
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
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 (
|
return (
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
@@ -39,7 +41,6 @@ const EdgeTypesFlow = () => {
|
|||||||
onInit={onInit}
|
onInit={onInit}
|
||||||
onConnect={onConnect}
|
onConnect={onConnect}
|
||||||
minZoom={0.2}
|
minZoom={0.2}
|
||||||
zoomOnScroll={false}
|
|
||||||
selectionKeyCode="a+s"
|
selectionKeyCode="a+s"
|
||||||
multiSelectionKeyCode={multiSelectionKeyCode}
|
multiSelectionKeyCode={multiSelectionKeyCode}
|
||||||
deleteKeyCode={deleteKeyCode}
|
deleteKeyCode={deleteKeyCode}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Edge, Node, Position } from 'react-flow-renderer';
|
import { Edge, Node, Position } from 'reactflow';
|
||||||
|
|
||||||
const nodeWidth = 80;
|
const nodeWidth = 80;
|
||||||
const nodeGapWidth = nodeWidth * 2;
|
const nodeGapWidth = nodeWidth * 2;
|
||||||
@@ -63,7 +63,10 @@ export function getElements(): { nodes: Node[]; edges: Edge[] } {
|
|||||||
for (let offsetIndex = 0; offsetIndex < offsets.length; offsetIndex++) {
|
for (let offsetIndex = 0; offsetIndex < offsets.length; offsetIndex++) {
|
||||||
const currOffset = offsets[offsetIndex];
|
const currOffset = offsets[offsetIndex];
|
||||||
|
|
||||||
const style = { ...nodeStyle, background: nodeColors[sourceTargetIndex][edgeTypeIndex] };
|
const style = {
|
||||||
|
...nodeStyle,
|
||||||
|
background: nodeColors[sourceTargetIndex][edgeTypeIndex],
|
||||||
|
};
|
||||||
const sourcePosition = {
|
const sourcePosition = {
|
||||||
x: offsetIndex * nodeWidth * 4,
|
x: offsetIndex * nodeWidth * 4,
|
||||||
y: edgeTypeIndex * 300 + sourceTargetIndex * edgeTypes.length * 300,
|
y: edgeTypeIndex * 300 + sourceTargetIndex * edgeTypes.length * 300,
|
||||||
+9
-2
@@ -1,5 +1,5 @@
|
|||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import { EdgeProps, getBezierPath } from 'react-flow-renderer';
|
import { EdgeProps, getBezierPath } from 'reactflow';
|
||||||
|
|
||||||
const CustomEdge: FC<EdgeProps> = ({
|
const CustomEdge: FC<EdgeProps> = ({
|
||||||
id,
|
id,
|
||||||
@@ -11,7 +11,14 @@ const CustomEdge: FC<EdgeProps> = ({
|
|||||||
targetPosition,
|
targetPosition,
|
||||||
data,
|
data,
|
||||||
}) => {
|
}) => {
|
||||||
const edgePath = getBezierPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition });
|
const [edgePath] = getBezierPath({
|
||||||
|
sourceX,
|
||||||
|
sourceY,
|
||||||
|
sourcePosition,
|
||||||
|
targetX,
|
||||||
|
targetY,
|
||||||
|
targetPosition,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
+6
-5
@@ -1,5 +1,5 @@
|
|||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import { EdgeProps, getBezierPath, EdgeText, getBezierEdgeCenter } from 'react-flow-renderer';
|
import { EdgeProps, getBezierPath, EdgeText } from 'reactflow';
|
||||||
|
|
||||||
const CustomEdge: FC<EdgeProps> = ({
|
const CustomEdge: FC<EdgeProps> = ({
|
||||||
id,
|
id,
|
||||||
@@ -11,20 +11,21 @@ const CustomEdge: FC<EdgeProps> = ({
|
|||||||
targetPosition,
|
targetPosition,
|
||||||
data,
|
data,
|
||||||
}) => {
|
}) => {
|
||||||
const edgePath = getBezierPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition });
|
const [edgePath, labelX, labelY] = getBezierPath({
|
||||||
const [centerX, centerY] = getBezierEdgeCenter({
|
|
||||||
sourceX,
|
sourceX,
|
||||||
sourceY,
|
sourceY,
|
||||||
|
sourcePosition,
|
||||||
targetX,
|
targetX,
|
||||||
targetY,
|
targetY,
|
||||||
|
targetPosition,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<path id={id} className="react-flow__edge-path" d={edgePath} />
|
<path id={id} className="react-flow__edge-path" d={edgePath} />
|
||||||
<EdgeText
|
<EdgeText
|
||||||
x={centerX}
|
x={labelX}
|
||||||
y={centerY}
|
y={labelY}
|
||||||
label={data.text}
|
label={data.text}
|
||||||
labelStyle={{ fill: 'white' }}
|
labelStyle={{ fill: 'white' }}
|
||||||
labelShowBg
|
labelShowBg
|
||||||
@@ -1,22 +1,21 @@
|
|||||||
import { MouseEvent } from 'react';
|
import React, { MouseEvent, useCallback } from 'react';
|
||||||
import ReactFlow, {
|
import ReactFlow, {
|
||||||
addEdge,
|
|
||||||
Background,
|
|
||||||
Connection,
|
|
||||||
Controls,
|
Controls,
|
||||||
|
Background,
|
||||||
|
MiniMap,
|
||||||
|
addEdge,
|
||||||
|
Connection,
|
||||||
Edge,
|
Edge,
|
||||||
EdgeTypes,
|
EdgeTypes,
|
||||||
MarkerType,
|
MarkerType,
|
||||||
MiniMap,
|
|
||||||
Node,
|
Node,
|
||||||
ReactFlowInstance,
|
|
||||||
useEdgesState,
|
useEdgesState,
|
||||||
useNodesState,
|
useNodesState,
|
||||||
} from 'react-flow-renderer';
|
} from 'reactflow';
|
||||||
|
|
||||||
import CustomEdge from './CustomEdge';
|
import CustomEdge from './CustomEdge';
|
||||||
import CustomEdge2 from './CustomEdge2';
|
import CustomEdge2 from './CustomEdge2';
|
||||||
|
|
||||||
const onInit = (reactFlowInstance: ReactFlowInstance) => reactFlowInstance.fitView();
|
|
||||||
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
|
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
|
||||||
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
||||||
const onEdgeClick = (_: MouseEvent, edge: Edge) => console.log('click', edge);
|
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 onEdgeMouseLeave = (_: MouseEvent, edge: Edge) => console.log('leave', edge);
|
||||||
|
|
||||||
const initialNodes: Node[] = [
|
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: '2', data: { label: 'Node 2' }, position: { x: 150, y: 100 } },
|
||||||
{ id: '2a', data: { label: 'Node 2a' }, position: { x: 0, y: 180 } },
|
{ id: '2a', data: { label: 'Node 2a' }, position: { x: 0, y: 180 } },
|
||||||
{ id: '2b', data: { label: 'Node 2b' }, position: { x: -40, y: 300 } },
|
{ 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: '4', data: { label: 'Node 4' }, position: { x: 400, y: 300 } },
|
||||||
{ id: '3a', data: { label: 'Node 3a' }, position: { x: 150, 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: '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: '6',
|
||||||
{ id: '8', type: 'output', data: { label: 'Output 8' }, position: { x: 525, y: 600 } },
|
type: 'output',
|
||||||
{ id: '9', type: 'output', data: { label: 'Output 9' }, position: { x: 675, y: 500 } },
|
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[] = [
|
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',
|
id: 'e2a-2b',
|
||||||
source: '2a',
|
source: '2a',
|
||||||
@@ -51,9 +87,29 @@ const initialEdges: Edge[] = [
|
|||||||
label: 'simple bezier edge',
|
label: 'simple bezier edge',
|
||||||
},
|
},
|
||||||
{ id: 'e2-3', source: '2', target: '3', type: 'step', label: 'step 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-4',
|
||||||
{ id: 'e3-5', source: '4', target: '5', animated: true, label: 'animated styled edge', style: { stroke: 'red' } },
|
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',
|
id: 'e5-7',
|
||||||
source: '5',
|
source: '5',
|
||||||
@@ -121,7 +177,7 @@ const edgeTypes: EdgeTypes = {
|
|||||||
const EdgesFlow = () => {
|
const EdgesFlow = () => {
|
||||||
const [nodes, , onNodesChange] = useNodesState(initialNodes);
|
const [nodes, , onNodesChange] = useNodesState(initialNodes);
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
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 (
|
return (
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
@@ -132,7 +188,6 @@ const EdgesFlow = () => {
|
|||||||
onNodeClick={onNodeClick}
|
onNodeClick={onNodeClick}
|
||||||
onConnect={onConnect}
|
onConnect={onConnect}
|
||||||
onNodeDragStop={onNodeDragStop}
|
onNodeDragStop={onNodeDragStop}
|
||||||
onInit={onInit}
|
|
||||||
snapToGrid={true}
|
snapToGrid={true}
|
||||||
edgeTypes={edgeTypes}
|
edgeTypes={edgeTypes}
|
||||||
onEdgeClick={onEdgeClick}
|
onEdgeClick={onEdgeClick}
|
||||||
@@ -1,35 +1,43 @@
|
|||||||
import { MouseEvent, CSSProperties } from 'react';
|
import React, { MouseEvent, CSSProperties, useCallback } from 'react';
|
||||||
|
|
||||||
import ReactFlow, {
|
import ReactFlow, {
|
||||||
addEdge,
|
|
||||||
Controls,
|
|
||||||
Background,
|
Background,
|
||||||
Node,
|
|
||||||
BackgroundVariant,
|
BackgroundVariant,
|
||||||
|
Controls,
|
||||||
|
addEdge,
|
||||||
|
Node,
|
||||||
Connection,
|
Connection,
|
||||||
Edge,
|
Edge,
|
||||||
useNodesState,
|
useNodesState,
|
||||||
useEdgesState,
|
useEdgesState,
|
||||||
ReactFlowInstance,
|
ReactFlowInstance,
|
||||||
} from 'react-flow-renderer';
|
} from 'reactflow';
|
||||||
|
|
||||||
const onInit = (reactFlowInstance: ReactFlowInstance) => console.log('flow loaded:', reactFlowInstance);
|
const onInit = (reactFlowInstance: ReactFlowInstance) => console.log('flow loaded:', reactFlowInstance);
|
||||||
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
||||||
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', 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 EmptyFlow = () => {
|
||||||
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
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 addRandomNode = () => {
|
||||||
const nodeId = (nodes.length + 1).toString();
|
const nodeId = (nodes.length + 1).toString();
|
||||||
const newNode: Node = {
|
const newNode: Node = {
|
||||||
id: nodeId,
|
id: nodeId,
|
||||||
data: { label: `Node: ${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));
|
setNodes((nds) => nds.concat(newNode));
|
||||||
};
|
};
|
||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
import { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { getBezierPath, ConnectionLineComponentProps, Node } from 'react-flow-renderer';
|
import { getBezierPath, ConnectionLineComponentProps, Node } from 'reactflow';
|
||||||
|
|
||||||
import { getEdgeParams } from './utils';
|
import { getEdgeParams } from './utils';
|
||||||
|
|
||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
import { FC, useMemo, CSSProperties } from 'react';
|
import React, { FC, useMemo, CSSProperties } from 'react';
|
||||||
import { EdgeProps, useStore, getBezierPath, ReactFlowState } from 'react-flow-renderer';
|
import { EdgeProps, useStore, getBezierPath, ReactFlowState } from 'reactflow';
|
||||||
|
|
||||||
import { getEdgeParams } from './utils';
|
import { getEdgeParams } from './utils';
|
||||||
|
|
||||||
+8
-8
@@ -1,25 +1,25 @@
|
|||||||
import { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
|
|
||||||
import ReactFlow, {
|
import ReactFlow, {
|
||||||
addEdge,
|
|
||||||
Background,
|
Background,
|
||||||
|
addEdge,
|
||||||
ReactFlowInstance,
|
ReactFlowInstance,
|
||||||
EdgeTypes,
|
EdgeTypes,
|
||||||
Connection,
|
Connection,
|
||||||
useNodesState,
|
useNodesState,
|
||||||
useEdgesState,
|
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 FloatingConnectionLine from './FloatingConnectionLine';
|
||||||
|
import FloatingEdge from './FloatingEdge';
|
||||||
import { createElements } from './utils';
|
import { createElements } from './utils';
|
||||||
|
|
||||||
const onInit = (reactFlowInstance: ReactFlowInstance) => reactFlowInstance.fitView();
|
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 = {
|
const edgeTypes: EdgeTypes = {
|
||||||
floating: FloatingEdge,
|
floating: FloatingEdge,
|
||||||
};
|
};
|
||||||
@@ -33,7 +33,7 @@ const FloatingEdges = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="floatingedges">
|
<div className={styles.floatingedges}>
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
edges={edges}
|
edges={edges}
|
||||||
+1
-1
@@ -4,6 +4,6 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.floatingedges .react-flow__handle {
|
.floatingedges :global .react-flow__handle {
|
||||||
opacity: 0;
|
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
|
// this helper function returns the intersection point
|
||||||
// of the line between the center of the intersectionNode and the target node
|
// 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, Connection, Edge, Node, useNodesState, useEdgesState, MiniMap, Controls } from 'reactflow';
|
||||||
import ReactFlow, {
|
|
||||||
addEdge,
|
|
||||||
MiniMap,
|
|
||||||
Controls,
|
|
||||||
Connection,
|
|
||||||
Edge,
|
|
||||||
Node,
|
|
||||||
useNodesState,
|
|
||||||
useEdgesState,
|
|
||||||
} from 'react-flow-renderer';
|
|
||||||
|
|
||||||
const initialNodes: Node[] = [
|
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: '1',
|
||||||
{ id: '3', hidden: true, data: { label: 'Node 3' }, position: { x: 400, y: 100 } },
|
type: 'input',
|
||||||
{ id: '4', hidden: true, data: { label: 'Node 4' }, position: { x: 400, y: 200 } },
|
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[] = [
|
const initialEdges: Edge[] = [
|
||||||
@@ -49,8 +60,6 @@ const HiddenFlow = () => {
|
|||||||
setEdges(setHidden(isHidden));
|
setEdges(setHidden(isHidden));
|
||||||
}, [isHidden, setEdges, setNodes]);
|
}, [isHidden, setEdges, setNodes]);
|
||||||
|
|
||||||
console.log(nodes);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
nodes={nodes}
|
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, {
|
import ReactFlow, {
|
||||||
addEdge,
|
addEdge,
|
||||||
MiniMap,
|
|
||||||
Controls,
|
|
||||||
Node,
|
Node,
|
||||||
Connection,
|
Connection,
|
||||||
Edge,
|
Edge,
|
||||||
@@ -10,10 +8,17 @@ import ReactFlow, {
|
|||||||
Viewport,
|
Viewport,
|
||||||
useNodesState,
|
useNodesState,
|
||||||
useEdgesState,
|
useEdgesState,
|
||||||
} from 'react-flow-renderer';
|
Controls,
|
||||||
|
MiniMap,
|
||||||
|
} from 'reactflow';
|
||||||
|
|
||||||
const initialNodes: Node[] = [
|
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: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 } },
|
||||||
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 } },
|
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 } },
|
||||||
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 } },
|
{ id: '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, {
|
import ReactFlow, {
|
||||||
|
Controls,
|
||||||
ReactFlowProvider,
|
ReactFlowProvider,
|
||||||
addEdge,
|
addEdge,
|
||||||
Controls,
|
|
||||||
Connection,
|
Connection,
|
||||||
CoordinateExtent,
|
CoordinateExtent,
|
||||||
Position,
|
Position,
|
||||||
@@ -10,12 +10,13 @@ import ReactFlow, {
|
|||||||
useEdgesState,
|
useEdgesState,
|
||||||
MarkerType,
|
MarkerType,
|
||||||
EdgeMarker,
|
EdgeMarker,
|
||||||
} from 'react-flow-renderer';
|
} from 'reactflow';
|
||||||
|
|
||||||
import dagre from 'dagre';
|
import dagre from 'dagre';
|
||||||
|
|
||||||
import initialItems from './initial-elements';
|
import initialItems from './initial-elements';
|
||||||
|
|
||||||
import './layouting.css';
|
import styles from './layouting.module.css';
|
||||||
|
|
||||||
const dagreGraph = new dagre.graphlib.Graph();
|
const dagreGraph = new dagre.graphlib.Graph();
|
||||||
dagreGraph.setDefaultEdgeLabel(() => ({}));
|
dagreGraph.setDefaultEdgeLabel(() => ({}));
|
||||||
@@ -56,7 +57,10 @@ const LayoutFlow = () => {
|
|||||||
node.sourcePosition = isHorizontal ? Position.Right : Position.Bottom;
|
node.sourcePosition = isHorizontal ? Position.Right : Position.Bottom;
|
||||||
// we need to pass a slightly different position in order to notify react flow about the change
|
// 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?
|
// @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;
|
return node;
|
||||||
});
|
});
|
||||||
@@ -80,7 +84,7 @@ const LayoutFlow = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="layoutflow">
|
<div className={styles.layoutflow}>
|
||||||
<ReactFlowProvider>
|
<ReactFlowProvider>
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
@@ -93,7 +97,7 @@ const LayoutFlow = () => {
|
|||||||
>
|
>
|
||||||
<Controls />
|
<Controls />
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
<div className="controls">
|
<div className={styles.controls}>
|
||||||
<button onClick={() => onLayout('TB')} style={{ marginRight: 10 }}>
|
<button onClick={() => onLayout('TB')} style={{ marginRight: 10 }}>
|
||||||
vertical layout
|
vertical layout
|
||||||
</button>
|
</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;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layoutflow .controls {
|
.controls {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
+39
-10
@@ -1,8 +1,8 @@
|
|||||||
import { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
|
|
||||||
import ReactFlow, {
|
import ReactFlow, {
|
||||||
addEdge,
|
|
||||||
Background,
|
Background,
|
||||||
|
addEdge,
|
||||||
Node,
|
Node,
|
||||||
Edge,
|
Edge,
|
||||||
Connection,
|
Connection,
|
||||||
@@ -10,19 +10,47 @@ import ReactFlow, {
|
|||||||
useNodesState,
|
useNodesState,
|
||||||
useEdgesState,
|
useEdgesState,
|
||||||
MarkerType,
|
MarkerType,
|
||||||
} from 'react-flow-renderer';
|
MiniMap,
|
||||||
|
} from 'reactflow';
|
||||||
|
|
||||||
import './multiflows.css';
|
import styles from './multiflows.module.css';
|
||||||
|
|
||||||
const initialNodes: 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: '1',
|
||||||
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 }, className: 'light' },
|
type: 'input',
|
||||||
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 }, className: 'light' },
|
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[] = [
|
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' },
|
{ id: 'e1-3', source: '1', target: '3' },
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -43,13 +71,14 @@ const Flow: FC<{ id: string }> = ({ id }) => {
|
|||||||
id={id}
|
id={id}
|
||||||
>
|
>
|
||||||
<Background />
|
<Background />
|
||||||
|
<MiniMap />
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
</ReactFlowProvider>
|
</ReactFlowProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const MultiFlows: FC = () => (
|
const MultiFlows: FC = () => (
|
||||||
<div className="react-flow__example-multiflows">
|
<div className={styles.multiflows}>
|
||||||
<Flow id="flow-a" />
|
<Flow id="flow-a" />
|
||||||
<Flow id="flow-b" />
|
<Flow id="flow-b" />
|
||||||
</div>
|
</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, {
|
import ReactFlow, {
|
||||||
addEdge,
|
Controls,
|
||||||
|
MiniMap,
|
||||||
Background,
|
Background,
|
||||||
|
addEdge,
|
||||||
useNodesState,
|
useNodesState,
|
||||||
useEdgesState,
|
useEdgesState,
|
||||||
MiniMap,
|
|
||||||
Controls,
|
|
||||||
Node,
|
Node,
|
||||||
Edge,
|
Edge,
|
||||||
ReactFlowInstance,
|
ReactFlowInstance,
|
||||||
Connection,
|
Connection,
|
||||||
} from 'react-flow-renderer';
|
} from 'reactflow';
|
||||||
|
|
||||||
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
|
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
|
||||||
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
||||||
const onEdgeClick = (_: MouseEvent, edge: Edge) => console.log('click', edge);
|
const onEdgeClick = (_: MouseEvent, edge: Edge) => console.log('click', edge);
|
||||||
|
|
||||||
const initialNodes: Node[] = [
|
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',
|
id: '2',
|
||||||
data: { label: 'Node 2' },
|
data: { label: 'Node 2' },
|
||||||
@@ -32,7 +37,12 @@ const initialNodes: Node[] = [
|
|||||||
position: { x: 10, y: 50 },
|
position: { x: 10, y: 50 },
|
||||||
parentNode: '2',
|
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',
|
id: '4',
|
||||||
data: { label: 'Node 4' },
|
data: { label: 'Node 4' },
|
||||||
@@ -53,7 +63,11 @@ const initialNodes: Node[] = [
|
|||||||
data: { label: 'Node 4b' },
|
data: { label: 'Node 4b' },
|
||||||
position: { x: 15, y: 120 },
|
position: { x: 15, y: 120 },
|
||||||
className: 'light',
|
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',
|
parentNode: '4',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -88,9 +102,12 @@ const NestedFlow = () => {
|
|||||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||||
|
|
||||||
const onConnect = useCallback((connection: Connection) => {
|
const onConnect = useCallback(
|
||||||
setEdges((eds) => addEdge(connection, eds));
|
(connection: Connection) => {
|
||||||
}, []);
|
setEdges((eds) => addEdge(connection, eds));
|
||||||
|
},
|
||||||
|
[setEdges]
|
||||||
|
);
|
||||||
const onInit = useCallback((reactFlowInstance: ReactFlowInstance) => setRfInstance(reactFlowInstance), []);
|
const onInit = useCallback((reactFlowInstance: ReactFlowInstance) => setRfInstance(reactFlowInstance), []);
|
||||||
|
|
||||||
const updatePos = () => {
|
const updatePos = () => {
|
||||||
@@ -139,7 +156,6 @@ const NestedFlow = () => {
|
|||||||
onConnect={onConnect}
|
onConnect={onConnect}
|
||||||
onNodeDragStop={onNodeDragStop}
|
onNodeDragStop={onNodeDragStop}
|
||||||
className="react-flow-basic-example"
|
className="react-flow-basic-example"
|
||||||
defaultZoom={1.5}
|
|
||||||
minZoom={0.2}
|
minZoom={0.2}
|
||||||
maxZoom={4}
|
maxZoom={4}
|
||||||
onlyRenderVisibleElements={false}
|
onlyRenderVisibleElements={false}
|
||||||
+9
-12
@@ -1,14 +1,6 @@
|
|||||||
import { CSSProperties } from 'react';
|
import React, { CSSProperties, useCallback } from 'react';
|
||||||
|
|
||||||
import ReactFlow, {
|
import ReactFlow, { addEdge, Node, Position, Connection, Edge, useNodesState, useEdgesState } from 'reactflow';
|
||||||
addEdge,
|
|
||||||
Node,
|
|
||||||
Position,
|
|
||||||
Connection,
|
|
||||||
Edge,
|
|
||||||
useNodesState,
|
|
||||||
useEdgesState,
|
|
||||||
} from 'react-flow-renderer';
|
|
||||||
|
|
||||||
const initialNodes: Node[] = [
|
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 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 NodeTypeChangeFlow = () => {
|
||||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
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 = () => {
|
const changeType = () => {
|
||||||
setNodes((nds) =>
|
setNodes((nds) =>
|
||||||
nds.map((node) => {
|
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, {
|
import ReactFlow, {
|
||||||
addEdge,
|
addEdge,
|
||||||
Node,
|
Node,
|
||||||
@@ -10,7 +9,7 @@ import ReactFlow, {
|
|||||||
NodeTypes,
|
NodeTypes,
|
||||||
useNodesState,
|
useNodesState,
|
||||||
useEdgesState,
|
useEdgesState,
|
||||||
} from 'react-flow-renderer';
|
} from 'reactflow';
|
||||||
|
|
||||||
const initialNodes: Node[] = [
|
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> = () => {
|
const NodeA: FC<NodeProps> = () => {
|
||||||
return <div style={nodeStyles}>A</div>;
|
return <div style={nodeStyles}>A</div>;
|
||||||
@@ -57,9 +64,9 @@ const nodeTypesObjects: NodeTypesObject = {
|
|||||||
|
|
||||||
const NodeTypeChangeFlow = () => {
|
const NodeTypeChangeFlow = () => {
|
||||||
const [nodeTypesId, setNodeTypesId] = useState<string>('a');
|
const [nodeTypesId, setNodeTypesId] = useState<string>('a');
|
||||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
const [nodes, , onNodesChange] = useNodesState(initialNodes);
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
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'));
|
const changeType = () => setNodeTypesId((nt) => (nt === 'a' ? 'b' : 'a'));
|
||||||
|
|
||||||
return (
|
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, {
|
import ReactFlow, {
|
||||||
addEdge,
|
addEdge,
|
||||||
MiniMap,
|
|
||||||
Controls,
|
|
||||||
Background,
|
|
||||||
Node,
|
Node,
|
||||||
Viewport,
|
Viewport,
|
||||||
SnapGrid,
|
SnapGrid,
|
||||||
@@ -13,7 +10,10 @@ import ReactFlow, {
|
|||||||
useNodesState,
|
useNodesState,
|
||||||
useEdgesState,
|
useEdgesState,
|
||||||
OnSelectionChangeParams,
|
OnSelectionChangeParams,
|
||||||
} from 'react-flow-renderer';
|
Controls,
|
||||||
|
Background,
|
||||||
|
MiniMap,
|
||||||
|
} from 'reactflow';
|
||||||
|
|
||||||
const onNodeDragStart = (_: ReactMouseEvent, node: Node, nodes: Node[]) => console.log('drag start', node, nodes);
|
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);
|
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 onEdgeDoubleClick = (_: ReactMouseEvent, edge: Edge) => console.log('edge double click', edge);
|
||||||
const onNodesDelete = (nodes: Node[]) => console.log('nodes delete', nodes);
|
const onNodesDelete = (nodes: Node[]) => console.log('nodes delete', nodes);
|
||||||
const onEdgesDelete = (edges: Edge[]) => console.log('edges delete', edges);
|
const onEdgesDelete = (edges: Edge[]) => console.log('edges delete', edges);
|
||||||
|
const onPaneMouseMove = (e: ReactMouseEvent) => console.log('pane move', e.clientX, e.clientY);
|
||||||
|
|
||||||
const initialNodes: Node[] = [
|
const initialNodes: Node[] = [
|
||||||
{
|
{
|
||||||
@@ -80,7 +81,12 @@ const initialNodes: Node[] = [
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
position: { x: 400, y: 100 },
|
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',
|
id: '4',
|
||||||
@@ -113,21 +119,40 @@ const initialNodes: Node[] = [
|
|||||||
data: {
|
data: {
|
||||||
label: (
|
label: (
|
||||||
<>
|
<>
|
||||||
An <strong>output node</strong>
|
An <strong>output node (not deletable)</strong>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
position: { x: 100, y: 480 },
|
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[] = [
|
const initialEdges: Edge[] = [
|
||||||
{ id: 'e1-2', source: '1', target: '2', label: 'this is an edge label' },
|
{ id: 'e1-2', source: '1', target: '2', label: 'this is an edge label' },
|
||||||
{ id: 'e1-3', source: '1', target: '3' },
|
{ 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: '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',
|
id: 'e5-7',
|
||||||
source: '5',
|
source: '5',
|
||||||
@@ -200,6 +225,7 @@ const OverviewFlow = () => {
|
|||||||
maxZoom={Infinity}
|
maxZoom={Infinity}
|
||||||
onNodesDelete={onNodesDelete}
|
onNodesDelete={onNodesDelete}
|
||||||
onEdgesDelete={onEdgesDelete}
|
onEdgesDelete={onEdgesDelete}
|
||||||
|
onPaneMouseMove={onPaneMouseMove}
|
||||||
>
|
>
|
||||||
<MiniMap nodeStrokeColor={nodeStrokeColor} nodeColor={nodeColor} nodeBorderRadius={2} />
|
<MiniMap nodeStrokeColor={nodeStrokeColor} nodeColor={nodeColor} nodeBorderRadius={2} />
|
||||||
<Controls />
|
<Controls />
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user