├── .dumi
├── favicon.png
└── global.css
├── .dumirc.ts
├── .editorconfig
├── .gitignore
├── .husky
├── commit-msg
└── pre-commit
├── .prettierignore
├── .prettierrc.js
├── LICENSE
├── README.md
├── docs
├── guide
│ ├── case.md
│ ├── core.md
│ ├── custom.md
│ ├── demo
│ │ ├── components
│ │ │ ├── Edges.tsx
│ │ │ ├── Nodes.tsx
│ │ │ ├── data.ts
│ │ │ └── index.less
│ │ ├── custom.tsx
│ │ ├── dooring.tsx
│ │ ├── index.less
│ │ ├── layout.tsx
│ │ └── multihandle.tsx
│ ├── edge.md
│ ├── edgePro.md
│ ├── flow.md
│ ├── index.md
│ ├── intro.md
│ ├── layout.md
│ ├── node.md
│ ├── plugin.md
│ ├── subflow.md
│ ├── terms.md
│ ├── theme.md
│ └── viewport.md
└── index.md
├── package.json
├── pnpm-lock.yaml
├── public
├── bg.svg
├── d3.png
├── edge.webp
├── elk.png
├── favicon.png
├── force.png
├── library.png
└── overview.webp
└── tsconfig.json
/.dumi/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MrXujiang/react-flow/0c6e9995b838a3deb08a074a149b06b3464f4ee4/.dumi/favicon.png
--------------------------------------------------------------------------------
/.dumi/global.css:
--------------------------------------------------------------------------------
1 | .dumi-default-navbar {
2 | padding-left: 60px !important;
3 | }
4 |
5 | .react-flow__panel.react-flow__attribution {
6 | display: none !important;
7 | }
8 |
--------------------------------------------------------------------------------
/.dumirc.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'dumi';
2 |
3 | export default defineConfig({
4 | title: 'React-Flow 中文文档',
5 | themeConfig: {
6 | name: 'React-Flow',
7 | favicons: [
8 | '/favicon.png'
9 | ],
10 | logo: '/favicon.png',
11 | nav: [
12 | { title: '指南', link: '/guide' },
13 | { title: 'Flow工作流案例', link: '/guide/case' },
14 | { title: '页面制作', link: 'https://dooring.vip' },
15 | { title: '笔记反馈', link: 'http://doc.dooring.vip/design/doc?id=d1722395646623&uid=wep_251711700015023' },
16 | ],
17 | socialLinks: {
18 | github: 'https://github.com/MrXujiang/react-flow',
19 | zhihu: 'https://www.zhihu.com/people/build800'
20 | },
21 | footer: '中文版由 徐小夕 提供技术支持'
22 | },
23 | });
24 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | /dist
3 | .dumi/tmp
4 | .dumi/tmp-production
5 | .DS_Store
6 | deploy.sh
7 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | npx commitlint --edit "${1}"
5 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | npx lint-staged
5 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .dumi/tmp
2 | .dumi/tmp-production
3 | *.yaml
4 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | printWidth: 80,
3 | proseWrap: 'never',
4 | singleQuote: true,
5 | trailingComma: 'all',
6 | overrides: [
7 | {
8 | files: '*.md',
9 | options: {
10 | proseWrap: 'preserve',
11 | },
12 | },
13 | ],
14 | };
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 徐小夕
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-flow
2 |
3 | 
4 |
5 | 中文文档: http://react-flow.com
6 |
7 | 文档搭建引擎: http://flowmix.turntip.cn/docx
8 |
9 | 开源版工作流编辑器: https://github.com/MrXujiang/flowmix-flow
10 |
11 | 工作流编辑器demo: http://flowmix.turntip.cn/flow-v0
12 |
13 | 如果大家觉得有帮助, 欢迎点个小小的 `star`, 后续还会持续更新!
14 |
15 | ## Development
16 |
17 | ```bash
18 | # install dependencies
19 | $ pnpm install
20 |
21 | # start dev server
22 | $ pnpm start
23 |
24 | # build docs
25 | $ pnpm run build
26 | ```
27 |
28 | ## 更多优质项目
29 |
30 | | 名称 | 描述 |
31 | | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
32 | | [H5-Dooring](https://github.com/MrXujiang/h5-Dooring) | 让 H5 制作像搭积木一样简单, 轻松搭建 H5 页面, H5 网站, PC 端网站, LowCode 平台. |
33 | | [V6.Dooring](https://github.com/MrXujiang/v6.dooring.public) | 可视化大屏解决方案, 提供一套可视化编辑引擎, 助力个人或企业轻松定制自己的可视化大屏应用. |
34 | | [dooring-electron-lowcode](https://github.com/MrXujiang/dooring-electron-lowcode) | 基于 electron 的 H5-Dooring 编辑器桌面端. |
35 | | [DooringX](https://github.com/H5-Dooring/dooringx) | 快速高效搭建可视化拖拽平台. |
36 | | [Mitu](https://github.com/H5-Dooring/mitu-editor) | 一款轻量级且可扩展的图片/图形编辑器解决方案. |
37 | | [xijs](https://github.com/MrXujiang/xijs) | 一个开箱即用的面向业务的javascript工具库 |
38 |
39 | ## 贡献
40 |
41 | 欢迎对next感兴趣的朋友一起共建:
42 |
43 | - 提交 [issues](https://github.com/MrXujiang/next-admin/issues) 来报告问题和优化建议.
44 | - 主动 [pull requests](https://github.com/MrXujiang/next-admin/pulls) 来优化代码.
45 |
46 | ## 联系
47 |
48 | 微信: `cxzk_168`
49 |
50 | ## LICENSE
51 |
52 | MIT
53 |
--------------------------------------------------------------------------------
/docs/guide/case.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav: 指南
3 | title: 脑图应用
4 | order: 1
5 | group:
6 | title: 应用案例
7 | order: 4
8 | ---
9 |
10 | ## 案例一: Dooring可视化搭建平台系统架构
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/docs/guide/core.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav: 指南
3 | title: 核心概念
4 | order: 2
5 | group:
6 | title: 概念&原理
7 | order: 1
8 | ---
9 |
10 | ## 核心概念
11 |
12 | 在下面的部分中,我将介绍 `React Flow` 的核心概念,并解释如何创建交互式流程。
13 |
14 | 流由节点和边(或仅节点)组成。我们可以将节点和边数组作为 `props` 传递给 `ReactFlow` 组件。因此,所有节点和边缘 ID 都必须是唯一的。节点需要一个位置和一个标签(如果您使用自定义节点,这可能会有所不同),而边缘需要源(节点 id)和目标(节点 id)。我们可以在节点选项和边缘选项部分中阅读有关选项的更多信息。
15 |
16 | ### 受控与非受控
17 |
18 | 使用 `React Flow`,我们有两种设置流程的方法。我们可以创建受控或非受控的。建议使用受控流,但对于更简单的用例,我们也可以设置不受控流。在接下来的部分中,我们将设置受控流。让我们首先向 `ReactFlow` 组件添加一些节点和边:
19 |
20 | ```tsx
21 | import { useState } from 'react';
22 | import { ReactFlow } from '@xyflow/react';
23 |
24 | const initialNodes = [
25 | {
26 | id: '1',
27 | type: 'input',
28 | data: { label: 'Input Node' },
29 | position: { x: 250, y: 25 },
30 | },
31 |
32 | {
33 | id: '2',
34 | // you can also pass a React component as a label
35 | data: { label:
Dooring Node
},
36 | position: { x: 100, y: 125 },
37 | },
38 | {
39 | id: '3',
40 | type: 'output',
41 | data: { label: 'Output Node' },
42 | position: { x: 250, y: 250 },
43 | },
44 | ];
45 |
46 | const initialEdges = [
47 | { id: 'e1-2', source: '1', target: '2' },
48 | { id: 'e2-3', source: '2', target: '3', animated: true },
49 | ];
50 |
51 | function Flow() {
52 | const [nodes, setNodes] = useState(initialNodes);
53 | const [edges, setEdges] = useState(initialEdges);
54 |
55 | return
;
56 | }
57 |
58 | export default Flow;
59 | ```
60 |
61 | ### 基础能力
62 |
63 | 默认情况下,除了在设置受控流时处理视口之外,`React Flow` 不会执行任何内部状态更新。与
64 | 组件一样,我们需要传递处理程序以将 `React Flow` 触发的更改应用到节点和边。为了选择、拖动和删除节点和边,我们需要实现 `onNodesChange` 和 `onEdgesChange` 处理程序:
65 |
66 | ```tsx
67 | import { useCallback, useState } from 'react';
68 | import { ReactFlow, applyEdgeChanges, applyNodeChanges } from '@xyflow/react';
69 |
70 | const initialNodes = [
71 | {
72 | id: '1',
73 | type: 'input',
74 | data: { label: 'Input Node' },
75 | position: { x: 250, y: 25 },
76 | },
77 |
78 | {
79 | id: '2',
80 | // you can also pass a React component as a label
81 | data: { label: Dooring Node
},
82 | position: { x: 100, y: 125 },
83 | },
84 | {
85 | id: '3',
86 | type: 'output',
87 | data: { label: 'Output Node' },
88 | position: { x: 250, y: 250 },
89 | },
90 | ];
91 |
92 | const initialEdges = [
93 | { id: 'e1-2', source: '1', target: '2' },
94 | { id: 'e2-3', source: '2', target: '3', animated: true },
95 | ];
96 |
97 | function Flow() {
98 | const [nodes, setNodes] = useState(initialNodes);
99 | const [edges, setEdges] = useState(initialEdges);
100 |
101 | const onNodesChange = useCallback(
102 | (changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
103 | [setNodes],
104 | );
105 | const onEdgesChange = useCallback(
106 | (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),
107 | [setEdges],
108 | );
109 |
110 | return (
111 |
112 |
119 |
120 |
121 | );
122 | }
123 |
124 | export default Flow;
125 | ```
126 |
127 | 这里发生了什么?每当 `React Flow` 触发更改(节点初始化、节点拖动、边缘选择等)时,就会调用 `onNodesChange` 处理程序。我们导出 `applyNodeChanges` 处理程序,以便您不需要自己处理更改。
128 |
129 | `applyNodeChanges` 处理程序返回更新后的节点数组,即新节点数组。您现在拥有具有以下交互类型的交互流:
130 |
131 | - 可选择的节点和边
132 | - 可拖动节点
133 | - 可移除节点和边
134 | -(按 `Backspace` 删除选定的节点或边,可以使用 `deleteKeyCode` 属性进行调整)
135 | 按 `Shift` 键可以选择多选区域(这是默认的选择键代码) 通过按命令进行多项选择(这是默认的 `multiSelectionKeyCode`)
136 |
137 | 为了方便起见,我们导出辅助钩子 `useNodesState` 和 `useEdgesState`,可以使用它们来创建节点和边状态:
138 |
139 | ```tsx | pure
140 | const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
141 | const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
142 | ```
143 |
144 | ### 连接节点
145 |
146 | 获得完整交互性所缺少的最后一个部分是 `onConnect` 处理程序。我们需要实现它,以便处理新连接。
147 |
148 | ```tsx
149 | import { useCallback, useState } from 'react';
150 | import { ReactFlow, addEdge, applyEdgeChanges, applyNodeChanges } from '@xyflow/react';
151 |
152 | const initialNodes = [
153 | {
154 | id: '1',
155 | type: 'input',
156 | data: { label: 'Input Node' },
157 | position: { x: 250, y: 25 },
158 | },
159 |
160 | {
161 | id: '2',
162 | // you can also pass a React component as a label
163 | data: { label: Dooring Node
},
164 | position: { x: 100, y: 125 },
165 | },
166 | {
167 | id: '3',
168 | type: 'output',
169 | data: { label: 'Output Node' },
170 | position: { x: 250, y: 250 },
171 | },
172 | ];
173 |
174 | const initialEdges = [
175 | { id: 'e1-2', source: '1', target: '2' },
176 | { id: 'e2-3', source: '2', target: '3', animated: true },
177 | ];
178 |
179 | function Flow() {
180 | const [nodes, setNodes] = useState(initialNodes);
181 | const [edges, setEdges] = useState(initialEdges);
182 |
183 | const onNodesChange = useCallback(
184 | (changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
185 | [setNodes],
186 | );
187 | const onEdgesChange = useCallback(
188 | (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),
189 | [setEdges],
190 | );
191 | const onConnect = useCallback(
192 | (connection) => setEdges((eds) => addEdge(connection, eds)),
193 | [setEdges],
194 | );
195 |
196 | return (
197 |
198 |
206 |
207 |
208 | );
209 | }
210 |
211 | export default Flow;
212 | ```
213 |
214 | 在此示例中,我们使用 `addEdge` 处理程序返回一组带有新创建的边的边。如果想在创建边缘时设置某个边缘选项,我们可以像这样传递选项:
215 |
216 | ```tsx | pure
217 | const onConnect = useCallback(
218 | (connection) =>
219 | setEdges((eds) => addEdge({ ...connection, animated: true }, eds)),
220 | [setEdges],
221 | );
222 | ```
223 |
224 | 或使用 `defaultEdgeOptions` 属性:
225 |
226 | ```tsx | pure
227 | const defaultEdgeOptions = { animated: true };
228 | ...
229 | ;
237 | ```
238 |
239 |
240 |
241 |
242 |
243 |
244 |
--------------------------------------------------------------------------------
/docs/guide/custom.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav: 指南
3 | title: 自定义节点
4 | order: 1
5 | group:
6 | title: 自定义React-Flow
7 | order: 2
8 | ---
9 |
10 | ## 自定义节点
11 |
12 | `React Flow` 的一个强大功能是添加自定义节点的能力。在我们的自定义节点中,可以渲染我们想要的一切。例如,可以定义多个源和目标句柄并呈现表单输入或图表。在本节中,我们将实现一个带有输入字段的节点,该输入字段更新应用程序另一部分中的一些文本。
13 |
14 | ### 实现自定义节点
15 |
16 | 自定义节点是一个 `React` 组件,它被包装以提供选择或拖动等基本功能。除了其他属性之外,我们还从包装器组件传递`位置`或`数据`等属性。让我们开始实现 `TextUpdaterNode`。我们使用 `Handle` 组件能够将自定义节点与其他节点连接并向该节点添加输入字段:
17 |
18 | ```tsx | pure
19 | import { useCallback } from 'react';
20 | import { Handle, Position } from '@xyflow/react';
21 |
22 | const handleStyle = { left: 10 };
23 |
24 | function TextUpdaterNode({ data }) {
25 | const onChange = useCallback((evt) => {
26 | console.log(evt.target.value);
27 | }, []);
28 |
29 | return (
30 | <>
31 |
32 |
33 |
34 |
35 |
36 |
37 |
43 | >
44 | );
45 | }
46 | ```
47 |
48 | 如您所见,我们已将类名 `nodrag` 添加到输入框中。可以防止在输入字段内拖动并让我们选择文本。
49 |
50 | ### 添加节点类型
51 |
52 | 我们可以通过将新的节点类型添加到 `nodeTypes` 属性来将其添加到 `React Flow`。节点类型在组件外部被记忆或定义是很重要的。否则,`React` 在每次渲染时都会创建一个新对象,这会导致性能问题和错误。
53 |
54 | ```tsx | pure
55 | const nodeTypes = useMemo(() => ({ textUpdater: TextUpdaterNode }), []);
56 |
57 | return ;
58 | ```
59 |
60 | 定义新的节点类型后,我们可以使用 `type` 节点选项来使用它:
61 |
62 | ```tsx | pure
63 | const nodes = [
64 | {
65 | id: 'node-1',
66 | type: 'textUpdater',
67 | position: { x: 0, y: 0 },
68 | data: { value: 123 },
69 | },
70 | ];
71 | ```
72 |
73 | 将所有内容放在一起并添加一些基本样式后,我们得到一个将文本打印到控制台的自定义节点:
74 |
75 |
76 |
77 | ### 使用多句柄
78 |
79 | 正如所看到的,我们向节点添加了两个源句柄,以便它有两个输出。如果你想用这些特定的句柄连接其他节点,节点 `id` 是不够的,你还需要传递特定的句柄`id`。在本例中,一个句柄的 `ID` 为`“a”`,另一个句柄的 `ID` 为`“b”`。句柄特定边使用引用节点内句柄的 `sourceHandle` 或 `targetHandle` 选项:
80 |
81 | ```tsx | pure
82 | const initialEdges = [
83 | { id: 'edge-1', source: 'node-1', sourceHandle: 'a', target: 'node-2' },
84 | { id: 'edge-2', source: 'node-1', sourceHandle: 'b', target: 'node-3' },
85 | ];
86 | ```
87 |
88 | 在这种情况下,两个句柄的源节点都是 `node-1`,但句柄 `ID` 不同。一个来自句柄 `ID“a”`,另一个来自 `“b”`。两条边也有不同的目标节点:
89 |
90 |
91 |
92 | 请注意,如果我们以编程方式更改自定义节点中句柄的位置或数量,则需要使用 `useUpdateNodeInternals` 挂钩来正确通知 `ReactFlow` 更改。从这章我们应该能够构建自定义节点。在大多数情况下,我们建议仅使用自定义节点。内置的只是基本示例。可以在自定义节点 `API` 部分找到传递的 `props` 列表和更多信息。
93 |
94 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/docs/guide/demo/components/Edges.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | BaseEdge,
3 | EdgeLabelRenderer,
4 | getStraightPath,
5 | getBezierPath,
6 | useReactFlow,
7 | } from '@xyflow/react';
8 |
9 | import { Tag } from 'antd';
10 |
11 | function CustomEdge({ id, sourceX, sourceY, targetX, targetY }) {
12 | const { setEdges } = useReactFlow();
13 | const [edgePath, labelX, labelY] = getBezierPath({
14 | sourceX,
15 | sourceY,
16 | targetX,
17 | targetY,
18 | });
19 |
20 | return (
21 | <>
22 |
23 |
24 | {
32 | // setEdges((es) => es.filter((e) => e.id !== id));
33 | }}
34 | >
35 | 持续升级
36 |
37 |
38 | >
39 | );
40 | }
41 |
42 | export {
43 | CustomEdge,
44 | }
--------------------------------------------------------------------------------
/docs/guide/demo/components/Nodes.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useState } from 'react';
2 | import {
3 | Handle, Position
4 | } from '@xyflow/react';
5 | import { Button, Card } from 'antd';
6 | import { GroupOutlined, GlobalOutlined, HddOutlined } from '@ant-design/icons';
7 | import './index.less';
8 |
9 | const handleStyle = { left: 10 };
10 |
11 | function TextUpdaterNode({ data, isConnectable }) {
12 | const [val, setVal] = useState(data.value);
13 | const onChange = useCallback((evt) => {
14 | setVal(evt.target.value);
15 | }, []);
16 |
17 | return (
18 |
19 |
24 |
25 |
26 |
27 |
28 |
29 |
36 |
42 |
43 | );
44 | }
45 |
46 | function LogoNode({ data, isConnectable }) {
47 | const { src, text } = data;
48 | return (
49 |
50 |
57 |
63 |
64 |

65 |
{ text }
66 |
67 |
68 | );
69 | }
70 |
71 | const iconMap = {
72 | l1: ,
73 | l2: ,
74 | l3:
75 | }
76 |
77 | function BaseNode({ data, isConnectable }) {
78 | const { iconType, text, id, type, noHandle, link } = data;
79 | return (
80 |
81 | {
82 | noHandle ? null :
83 | <>
84 |
91 |
98 |
104 | >
105 | }
106 |
107 |
113 |
114 | );
115 | }
116 |
117 | function ImgNode({ data, isConnectable }) {
118 | const { src, id } = data;
119 | return (
120 |
121 |
128 |
135 |
文档} style={{ width: 500 }}>
136 |
137 |
138 |
139 | );
140 | }
141 |
142 |
143 | export {
144 | TextUpdaterNode,
145 | LogoNode,
146 | BaseNode,
147 | ImgNode
148 | }
--------------------------------------------------------------------------------
/docs/guide/demo/components/data.ts:
--------------------------------------------------------------------------------
1 | const initialNodes = [
2 | {
3 | id: 'node-1',
4 | type: 'logo',
5 | position: { x: 0, y: 0 },
6 | data: {
7 | src: 'https://fast.dooring.vip/static/user/7e200e46-4976-44e4-8af9-7082871b6934/dd072c2a6fa1563c631e2a0955eced61.png' ,
8 | text: 'Dooring可视化搭建平台'
9 | },
10 | },
11 | {
12 | id: 'node-2',
13 | type: 'base',
14 | targetPosition: 'top',
15 | position: { x: 0, y: 200 },
16 | data: { iconType: 'l1', text: '应用场景', id: 'node-2' },
17 | },
18 | {
19 | id: 'node-2-1',
20 | type: 'base',
21 | targetPosition: 'bottom',
22 | position: { x: 100, y: 300 },
23 | data: { iconType: 'l3', text: '营销搭建中台', id: 'node-2-1' },
24 | },
25 | {
26 | id: 'node-2-2',
27 | type: 'base',
28 | targetPosition: 'top',
29 | position: { x: -100, y: 260 },
30 | data: { iconType: 'l3', text: '业务搭建平台', id: 'node-2-2' },
31 | },
32 | {
33 | id: 'node-2-3',
34 | type: 'base',
35 | targetPosition: 'top',
36 | position: { x: 30, y: 360 },
37 | data: { iconType: 'l3', text: '品宣设计平台', id: 'node-2-3' },
38 | },
39 | {
40 | id: 'node-2-4',
41 | type: 'base',
42 | targetPosition: 'top',
43 | position: { x: -160, y: 360 },
44 | data: { iconType: 'l3', text: '电商&活动搭建平台', id: 'node-2-4' },
45 | },
46 | {
47 | id: 'node-3',
48 | type: 'base',
49 | targetPosition: 'top',
50 | position: { x: 200, y: 200 },
51 | data: { iconType: 'l2', text: '核心模块', id: 'node-3' },
52 | },
53 | {
54 | id: 'core',
55 | type: 'output',
56 | targetPosition: 'top',
57 | data: { label: null },
58 | position: { x: 130, y: 420 },
59 | style: {
60 | width: 380,
61 | height: 150,
62 | },
63 | },
64 | {
65 | id: 'jump',
66 | type: 'base',
67 | targetPosition: 'top',
68 | data: { text: '立即体验', id: 'jump', link: 'https://dooring.vip', type: 'primary' },
69 | position: { x: -120, y: 520 },
70 | },
71 | {
72 | id: 'core-1',
73 | type: 'base',
74 | position: { x: 20, y: 20 },
75 | parentId: 'core',
76 | extent: 'parent',
77 | data: { type: 'primary', text: '搭建引擎', id: 'core-1', noHandle: true },
78 | },
79 | {
80 | id: 'core-2',
81 | type: 'base',
82 | position: { x: 130, y: 20 },
83 | parentId: 'core',
84 | extent: 'parent',
85 | data: { type: 'primary', text: '规则引擎', id: 'core-1', noHandle: true },
86 | },
87 | {
88 | id: 'core-3',
89 | type: 'base',
90 | position: { x: 240, y: 20 },
91 | parentId: 'core',
92 | extent: 'parent',
93 | data: { type: 'primary', text: '流程引擎', id: 'core-1', noHandle: true },
94 | },
95 | {
96 | id: 'core-4',
97 | type: 'base',
98 | position: { x: 20, y: 80 },
99 | parentId: 'core',
100 | extent: 'parent',
101 | data: { type: 'primary', text: '设计辅助', id: 'core-1', noHandle: true },
102 | },
103 | {
104 | id: 'core-5',
105 | type: 'base',
106 | position: { x: 130, y: 80 },
107 | parentId: 'core',
108 | extent: 'parent',
109 | data: { type: 'primary', text: '组件商店', id: 'core-1', noHandle: true },
110 | },
111 | {
112 | id: 'core-6',
113 | type: 'base',
114 | position: { x: 240, y: 80 },
115 | parentId: 'core',
116 | extent: 'parent',
117 | data: { type: 'primary', text: '数据源机制', id: 'core-1', noHandle: true },
118 | },
119 | {
120 | id: 'node-4',
121 | type: 'base',
122 | targetPosition: 'top',
123 | position: { x: 400, y: 200 },
124 | data: { iconType: 'l3', text: '技术架构', id: 'node-4' },
125 | },
126 | {
127 | id: 'node-5',
128 | type: 'img',
129 | targetPosition: 'top',
130 | position: { x: 520, y: 300 },
131 | data: { src: 'https://basic.dooring.cn/saas/user/7e200e46-4976-44e4-8af9-7082871b6934/FvK6aLnN_5S4GE2uGfqQjeoVz5Kt.png?imageView2/0/format/webp/q/75', id: 'node-5' },
132 | },
133 | ];
134 |
135 | const initialEdges = [
136 | { id: 'edge-1', source: 'node-1', target: 'node-2', sourceHandle: 'a', animated: true },
137 | { id: 'edge-2', source: 'node-1', target: 'node-3', type: 'custom-edge', sourceHandle: 'b', animated: true },
138 | { id: 'edge-3-1', source: 'node-3', target: 'core', type: 'custom-edge', animated: true },
139 | { id: 'jump', source: 'node-2-4', target: 'jump', type: 'custom-edge' },
140 | { id: 'edge-2-1', source: 'node-2', target: 'node-2-1', sourceHandle: 'node-2-btm', animated: true },
141 | { id: 'edge-2-2', source: 'node-2', target: 'node-2-2', sourceHandle: 'node-2-btm', animated: true },
142 | { id: 'edge-2-3', source: 'node-2', target: 'node-2-3', sourceHandle: 'node-2-btm', animated: true },
143 | { id: 'edge-2-4', source: 'node-2', target: 'node-2-4', sourceHandle: 'node-2-btm', animated: true },
144 | { id: 'edge-3', source: 'node-1', target: 'node-4', sourceHandle: 'b', animated: true },
145 | { id: 'edge-4', source: 'node-4', target: 'node-5', sourceHandle: 'node-4-btm' },
146 | ];
147 |
148 | export {
149 | initialNodes,
150 | initialEdges
151 | }
--------------------------------------------------------------------------------
/docs/guide/demo/components/index.less:
--------------------------------------------------------------------------------
1 | .text-updater-node {
2 | height: 50px;
3 | border: 1px solid #eee;
4 | padding: 5px;
5 | border-radius: 5px;
6 | background: white;
7 | }
8 |
9 | .text-updater-node label {
10 | display: block;
11 | color: #777;
12 | font-size: 12px;
13 | }
14 |
15 | .flow-logo {
16 |
17 | > div {
18 | display: flex;
19 | align-items: center;
20 | img {
21 | width: 42px;
22 | }
23 | .flow-logo-text {
24 | margin-left: 10px;
25 | font-size: 24px;
26 | }
27 | }
28 |
29 | }
--------------------------------------------------------------------------------
/docs/guide/demo/custom.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useState } from 'react';
2 | import {
3 | ReactFlow,
4 | addEdge,
5 | applyEdgeChanges,
6 | applyNodeChanges,
7 | Handle, Position
8 | } from '@xyflow/react';
9 | import './index.less';
10 |
11 | const handleStyle = { left: 10 };
12 |
13 | function TextUpdaterNode({ data, isConnectable }) {
14 | const [val, setVal] = useState(data.value);
15 | const onChange = useCallback((evt) => {
16 | console.log(evt.target.value);
17 | setVal(evt.target.value);
18 | }, []);
19 |
20 | return (
21 |
22 |
27 |
28 |
29 |
30 |
31 |
38 |
44 |
45 | );
46 | }
47 |
48 | const rfStyle = {
49 | backgroundColor: '#B8CEFF',
50 | };
51 |
52 | const initialNodes = [
53 | {
54 | id: 'node-1',
55 | type: 'textUpdater',
56 | position: { x: 0, y: 0 },
57 | data: { value: 'h5-dooring' },
58 | },
59 | ];
60 | // we define the nodeTypes outside of the component to prevent re-renderings
61 | // you could also use useMemo inside the component
62 | const nodeTypes = { textUpdater: TextUpdaterNode };
63 |
64 | function Flow() {
65 | const [nodes, setNodes] = useState(initialNodes);
66 | const [edges, setEdges] = useState([]);
67 |
68 | const onNodesChange = useCallback(
69 | (changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
70 | [setNodes],
71 | );
72 | const onEdgesChange = useCallback(
73 | (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),
74 | [setEdges],
75 | );
76 | const onConnect = useCallback(
77 | (connection) => setEdges((eds) => addEdge(connection, eds)),
78 | [setEdges],
79 | );
80 |
81 | return (
82 |
83 |
93 |
94 | );
95 | }
96 |
97 | export default Flow;
--------------------------------------------------------------------------------
/docs/guide/demo/dooring.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useState } from 'react';
2 | import {
3 | ReactFlow,
4 | addEdge,
5 | applyEdgeChanges,
6 | applyNodeChanges,
7 | SelectionMode,
8 | MiniMap,
9 | Controls,
10 | Background,
11 | Panel
12 | } from '@xyflow/react';
13 | import { Button } from 'antd';
14 | import { TextUpdaterNode, LogoNode, BaseNode, ImgNode } from './components/Nodes';
15 | import { CustomEdge } from './components/Edges';
16 | import { initialEdges, initialNodes } from './components/data';
17 |
18 | const handleStyle = { left: 10 };
19 | const panOnDrag = [1, 2];
20 |
21 | const rfStyle = {
22 | backgroundColor: '#f7f9fb',
23 | };
24 |
25 | const nodeTypes = {
26 | textUpdater: TextUpdaterNode,
27 | logo: LogoNode,
28 | base: BaseNode,
29 | img: ImgNode
30 | };
31 |
32 | const edgeTypes = {
33 | 'custom-edge': CustomEdge,
34 | };
35 |
36 | const nodeColor = (node) => {
37 | switch (node.type) {
38 | case 'base':
39 | return '#6ede87';
40 | case 'img':
41 | return '#1890ff';
42 | default:
43 | return '#ff0072';
44 | }
45 | };
46 |
47 | function Flow() {
48 | const [nodes, setNodes] = useState(initialNodes);
49 | const [edges, setEdges] = useState(initialEdges);
50 |
51 | const onNodesChange = useCallback(
52 | (changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
53 | [setNodes],
54 | );
55 | const onEdgesChange = useCallback(
56 | (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),
57 | [setEdges],
58 | );
59 | const onConnect = useCallback(
60 | (connection) => setEdges((eds) => addEdge(connection, eds)),
61 | [setEdges],
62 | );
63 |
64 | return (
65 |
66 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | );
90 | }
91 |
92 | export default Flow;
--------------------------------------------------------------------------------
/docs/guide/demo/index.less:
--------------------------------------------------------------------------------
1 | .react-flow__node-output {
2 | border: 1px solid #eee;
3 | box-shadow: none !important;
4 | background-color: #fff;
5 | border-radius: 6px;
6 | }
--------------------------------------------------------------------------------
/docs/guide/demo/layout.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback } from 'react';
2 | import {
3 | ReactFlow,
4 | ReactFlowProvider,
5 | useNodesState,
6 | useEdgesState,
7 | useReactFlow,
8 | } from '@xyflow/react';
9 |
10 | const initialNodes = [
11 | {
12 | id: '1',
13 | type: 'input',
14 | data: { label: 'input' },
15 | position: { x: 0, y: 0 },
16 | },
17 | {
18 | id: '2',
19 | data: { label: 'node 2' },
20 | position: { x: 0, y: 100 },
21 | },
22 | {
23 | id: '2a',
24 | data: { label: 'node 2a' },
25 | position: { x: 0, y: 200 },
26 | },
27 | {
28 | id: '2b',
29 | data: { label: 'node 2b' },
30 | position: { x: 0, y: 300 },
31 | },
32 | {
33 | id: '2c',
34 | data: { label: 'node 2c' },
35 | position: { x: 0, y: 400 },
36 | },
37 | {
38 | id: '2d',
39 | data: { label: 'node 2d' },
40 | position: { x: 0, y: 500 },
41 | },
42 | {
43 | id: '3',
44 | data: { label: 'node 3' },
45 | position: { x: 200, y: 100 },
46 | },
47 | ];
48 |
49 | const initialEdges = [
50 | { id: 'e12', source: '1', target: '2', animated: true },
51 | { id: 'e13', source: '1', target: '3', animated: true },
52 | { id: 'e22a', source: '2', target: '2a', animated: true },
53 | { id: 'e22b', source: '2', target: '2b', animated: true },
54 | { id: 'e22c', source: '2', target: '2c', animated: true },
55 | { id: 'e2c2d', source: '2c', target: '2d', animated: true },
56 | ];
57 |
58 |
59 | const getLayoutedElements = (nodes, edges) => {
60 | return { nodes, edges };
61 | };
62 |
63 | const LayoutFlow = () => {
64 | const { fitView } = useReactFlow();
65 | const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
66 | const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
67 |
68 | const onLayout = useCallback(() => {
69 | const layouted = getLayoutedElements(nodes, edges);
70 |
71 | setNodes([...layouted.nodes]);
72 | setEdges([...layouted.edges]);
73 |
74 | window.requestAnimationFrame(() => {
75 | fitView();
76 | });
77 | }, [nodes, edges]);
78 |
79 | return (
80 |
81 |
88 |
89 |
90 | );
91 | };
92 |
93 | export default function () {
94 | return (
95 |
96 |
97 |
98 | );
99 | }
--------------------------------------------------------------------------------
/docs/guide/demo/multihandle.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useState } from 'react';
2 | import {
3 | ReactFlow,
4 | addEdge,
5 | applyEdgeChanges,
6 | applyNodeChanges,
7 | Handle, Position
8 | } from '@xyflow/react';
9 | import './index.less';
10 |
11 | const handleStyle = { left: 10 };
12 |
13 | function TextUpdaterNode({ data, isConnectable }) {
14 | const [val, setVal] = useState(data.value);
15 | const onChange = useCallback((evt) => {
16 | console.log(evt.target.value);
17 | setVal(evt.target.value);
18 | }, []);
19 |
20 | return (
21 |
22 |
27 |
28 |
29 |
30 |
31 |
38 |
44 |
45 | );
46 | }
47 |
48 | const rfStyle = {
49 | backgroundColor: '#B8CEFF',
50 | };
51 |
52 | const initialNodes = [
53 | {
54 | id: 'node-1',
55 | type: 'textUpdater',
56 | position: { x: 0, y: 0 },
57 | data: { value: 123 },
58 | },
59 | {
60 | id: 'node-2',
61 | type: 'output',
62 | targetPosition: 'top',
63 | position: { x: 0, y: 200 },
64 | data: { label: 'node 2' },
65 | },
66 | {
67 | id: 'node-3',
68 | type: 'output',
69 | targetPosition: 'top',
70 | position: { x: 200, y: 200 },
71 | data: { label: 'node 3' },
72 | },
73 | ];
74 |
75 | const initialEdges = [
76 | { id: 'edge-1', source: 'node-1', target: 'node-2', sourceHandle: 'a' },
77 | { id: 'edge-2', source: 'node-1', target: 'node-3', sourceHandle: 'b' },
78 | ];
79 |
80 | const nodeTypes = { textUpdater: TextUpdaterNode };
81 |
82 | function Flow() {
83 | const [nodes, setNodes] = useState(initialNodes);
84 | const [edges, setEdges] = useState(initialEdges);
85 |
86 | const onNodesChange = useCallback(
87 | (changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
88 | [setNodes],
89 | );
90 | const onEdgesChange = useCallback(
91 | (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),
92 | [setEdges],
93 | );
94 | const onConnect = useCallback(
95 | (connection) => setEdges((eds) => addEdge(connection, eds)),
96 | [setEdges],
97 | );
98 |
99 | return (
100 |
101 |
111 |
112 | );
113 | }
114 |
115 | export default Flow;
--------------------------------------------------------------------------------
/docs/guide/edge.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav: 指南
3 | title: 自定义边
4 | order: 3
5 | group:
6 | title: 自定义React-Flow
7 | order: 2
8 | ---
9 |
10 | ## 自定义边
11 |
12 | 与自定义节点一样,`React Flow` 中的自定义边的一部分只是 `React` 组件:这意味着可以沿着边渲染任何想要的东西!本指南将展示如何使用一些附加控件来实现自定义边缘。
13 |
14 | ### 一个简单的自定义边案例
15 |
16 | 如果边不能渲染两个连接节点之间的路径,那么它对我们来说就没多大用处。这些路径始终基于 `SVG`,并且通常使用
17 | 组件进行渲染。为了计算要渲染的实际 `SVG` 路径,`React Flow` 附带了一些方便的实用函数:
18 |
19 | - getBezierPath
20 | - getSimpleBezierPath
21 | - getSmoothStepPath
22 | - getStraightPath
23 |
24 | 为了启动我们的自定义边缘,我们只需在源和目标之间渲染一条直线路径。
25 |
26 | ```tsx | pure
27 | import { BaseEdge, getStraightPath } from '@xyflow/react';
28 |
29 | export default function CustomEdge({ id, sourceX, sourceY, targetX, targetY }) {
30 | const [edgePath] = getStraightPath({
31 | sourceX,
32 | sourceY,
33 | targetX,
34 | targetY,
35 | });
36 |
37 | return (
38 | <>
39 |
40 | >
41 | );
42 | }
43 | ```
44 |
45 | :::info{title=提示}
46 | 传递给自定义边缘组件的所有 props 都可以在 EdgeProps 类型下的 API 参考中找到。
47 | :::
48 |
49 | 这为我们提供了一条直边,其行为与默认的 “`straight`(直)” 边类型相同。要使用它,我们还需要更新
50 | 组件上的 `edgeTypes` 属性。
51 |
52 | 在组件外部定义 `EdgeTypes` 对象或使用 `React` 的 `useMemo` 钩子来防止不必要的重新渲染非常重要。如果忘记执行此操作,`React Flow` 将在控制台中显示警告。
53 |
54 | ```tsx | pure
55 | import ReactFlow from '@xyflow/react'
56 | import CustomEdge from './CustomEdge'
57 |
58 |
59 | const edgeTypes = {
60 | 'custom-edge': CustomEdge
61 | }
62 |
63 | export function Flow() {
64 | return
65 | }
66 | ```
67 |
68 | 定义 `edgeTypes` 对象后,我们可以通过将边的类型字段设置为 “`custom-edge`” 来使用新的自定义边。
69 |
70 | ```tsx
71 | import { useCallback } from 'react';
72 | import {
73 | ReactFlow,
74 | addEdge,
75 | useNodesState,
76 | useEdgesState,
77 | BaseEdge, getStraightPath
78 | } from '@xyflow/react';
79 |
80 | import '@xyflow/react/dist/style.css';
81 |
82 | function CustomEdge({ id, sourceX, sourceY, targetX, targetY }) {
83 | const [edgePath] = getStraightPath({
84 | sourceX,
85 | sourceY,
86 | targetX,
87 | targetY,
88 | });
89 |
90 | return (
91 | <>
92 |
93 | >
94 | );
95 | }
96 |
97 | const initialNodes = [
98 | { id: 'a', position: { x: 0, y: 0 }, data: { label: 'Node A' } },
99 | { id: 'b', position: { x: 0, y: 100 }, data: { label: 'Node B' } },
100 | ];
101 |
102 | const initialEdges = [
103 | { id: 'a->b', type: 'custom-edge', source: 'a', target: 'b' },
104 | ];
105 |
106 | const edgeTypes = {
107 | 'custom-edge': CustomEdge,
108 | };
109 |
110 | function Flow() {
111 | const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
112 | const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
113 | const onConnect = useCallback(
114 | (connection) => {
115 | const edge = { ...connection, type: 'custom-edge' };
116 | setEdges((eds) => addEdge(edge, eds));
117 | },
118 | [setEdges],
119 | );
120 |
121 | return (
122 |
123 |
132 |
133 | );
134 | }
135 |
136 | export default Flow;
137 | ```
138 |
139 | ### 添加边标签
140 |
141 | 自定义边最常见的用途之一是沿着边缘的路径呈现一些控件或信息。在 `React Flow` 中,我们称之为边标签,与边路径不同,边标签可以是任何 `React` 组件!
142 |
143 | 要渲染自定义边标签,我们必须将其包装在 `` 组件中。出于性能原因,这是必要的:边标签渲染器是所有边标签都渲染到其中的单个容器的部分。
144 |
145 | 让我们向自定义边添加一个按钮,可用于删除其附加的边:
146 |
147 | ```tsx | pure
148 | import {
149 | BaseEdge,
150 | EdgeLabelRenderer,
151 | getStraightPath,
152 | useReactFlow,
153 | } from '@xyflow/react';
154 |
155 | export default function CustomEdge({ id, sourceX, sourceY, targetX, targetY }) {
156 | const { setEdges } = useReactFlow();
157 | const [edgePath] = getStraightPath({
158 | sourceX,
159 | sourceY,
160 | targetX,
161 | targetY,
162 | });
163 |
164 | return (
165 | <>
166 |
167 |
168 |
173 |
174 | >
175 | );
176 | }
177 | ```
178 |
179 | 如果我们现在尝试使用此边,我们将看到该按钮呈现在流程的中心(它可能隐藏在“节点 A”后面)。由于边标签的存在,我们需要做一些额外的工作来自己定位按钮。
180 |
181 | 
182 |
183 | 幸运的是,我们的路径`API`可以帮助我们做到这一点!除了要渲染的 `SVG` 路径之外,这些函数还返回路径中点的 `x` 和 `y` 坐标。然后,我们可以使用这些坐标将自定义边缘标签转换到正确的位置!
184 |
185 | ```tsx | pure
186 | export default function CustomEdge({ id, sourceX, sourceY, targetX, targetY }) {
187 | const { setEdges } = useReactFlow();
188 | const [edgePath, labelX, labelY] = getStraightPath({ ... });
189 |
190 | return (
191 | ...
192 |