@knotx/react 0.4.12 → 0.4.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.en.md +722 -0
  2. package/README.md +722 -0
  3. package/package.json +8 -8
package/README.md ADDED
@@ -0,0 +1,722 @@
1
+ # @knotx/react
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@knotx/react.svg)](https://www.npmjs.com/package/@knotx/react)
4
+ [![license](https://img.shields.io/npm/l/@knotx/react.svg)](https://github.com/boenfu/knotx/blob/main/LICENSE)
5
+
6
+ > 🚀 一个强大的 React 节点编辑器组件,支持可视化流程图、组织架构图、脑图等多种场景
7
+
8
+ ## 📦 安装
9
+
10
+ ```bash
11
+ npm install @knotx/react
12
+ ```
13
+
14
+ ```bash
15
+ yarn add @knotx/react
16
+ ```
17
+
18
+ ```bash
19
+ pnpm add @knotx/react
20
+ ```
21
+
22
+ ### 对等依赖
23
+
24
+ 该包需要以下对等依赖:
25
+
26
+ ```json
27
+ {
28
+ "react": ">=17.0.0",
29
+ "react-dom": ">=17.0.0"
30
+ }
31
+ ```
32
+
33
+ ## 🎯 基本概述
34
+
35
+ `@knotx/react` 是 Knotx 生态系统中的 React 适配器,提供了一个功能强大且灵活的节点编辑器组件。它支持:
36
+
37
+ - 🎨 **可视化节点编辑** - 拖拽、缩放、连接节点
38
+ - 🔧 **插件系统** - 高度可扩展的插件架构
39
+ - 🎭 **自定义渲染** - 完全自定义的节点和边的渲染
40
+ - 📱 **响应式设计** - 适配不同屏幕尺寸
41
+ - ⚡ **高性能** - 基于 RxJS 的响应式状态管理
42
+ - 🎪 **丰富交互** - 支持选择、拖拽、缩放、小地图等
43
+
44
+ ## 🚀 快速开始
45
+
46
+ ### 基础用法
47
+
48
+ ```tsx
49
+ import type { Edge, Node } from '@knotx/core'
50
+
51
+ import { Knotx } from '@knotx/react'
52
+ import React from 'react'
53
+
54
+ function App() {
55
+ const initialNodes: Node[] = [
56
+ {
57
+ id: '1',
58
+ type: 'basic',
59
+ position: { x: 300, y: 300 },
60
+ measured: { width: 160, height: 60 },
61
+ data: { label: '节点 1' }
62
+ },
63
+ {
64
+ id: '2',
65
+ type: 'basic',
66
+ position: { x: 600, y: 300 },
67
+ measured: { width: 160, height: 60 },
68
+ data: { label: '节点 2' }
69
+ }
70
+ ]
71
+
72
+ const initialEdges: Edge[] = [
73
+ {
74
+ id: '1-2',
75
+ type: 'bezier',
76
+ source: '1',
77
+ target: '2',
78
+ data: {}
79
+ }
80
+ ]
81
+
82
+ return (
83
+ <div style={{ width: '100vw', height: '100vh' }}>
84
+ <Knotx
85
+ initialNodes={initialNodes}
86
+ initialEdges={initialEdges}
87
+ plugins={[]} // 插件列表
88
+ />
89
+ </div>
90
+ )
91
+ }
92
+
93
+ export default App
94
+ ```
95
+
96
+ ### 自定义节点渲染
97
+
98
+ ```tsx
99
+ import type { Node } from '@knotx/core'
100
+
101
+ import { BasePlugin } from '@knotx/core'
102
+ import { nodeType } from '@knotx/decorators'
103
+ import { Knotx } from '@knotx/react'
104
+ import React from 'react'
105
+
106
+ // 创建自定义节点插件
107
+ class CustomNodePlugin extends BasePlugin<'customNode'> {
108
+ name = 'customNode' as const
109
+
110
+ @nodeType('custom')
111
+ renderCustomNode({ node }: { node: Node }) {
112
+ return (
113
+ <div
114
+ style={{
115
+ width: '100%',
116
+ height: '100%',
117
+ background: '#f0f0f0',
118
+ border: '2px solid #ccc',
119
+ borderRadius: '8px',
120
+ padding: '12px',
121
+ display: 'flex',
122
+ alignItems: 'center',
123
+ justifyContent: 'center',
124
+ fontSize: '14px',
125
+ fontWeight: 'bold'
126
+ }}
127
+ >
128
+ {node.data?.label || node.id}
129
+ </div>
130
+ )
131
+ }
132
+ }
133
+
134
+ function App() {
135
+ const nodes: Node[] = [
136
+ {
137
+ id: '1',
138
+ type: 'custom',
139
+ position: { x: 300, y: 300 },
140
+ measured: { width: 200, height: 80 },
141
+ data: { label: '自定义节点' }
142
+ }
143
+ ]
144
+
145
+ return (
146
+ <div style={{ width: '100vw', height: '100vh' }}>
147
+ <Knotx
148
+ initialNodes={nodes}
149
+ plugins={[CustomNodePlugin]}
150
+ />
151
+ </div>
152
+ )
153
+ }
154
+ ```
155
+
156
+ ### 受控组件模式
157
+
158
+ ```tsx
159
+ import type { Edge, Node, ReactEngine } from '@knotx/react'
160
+
161
+ import { Knotx } from '@knotx/react'
162
+ import React, { useState } from 'react'
163
+
164
+ function App() {
165
+ const [nodes, setNodes] = useState<Node[]>([
166
+ {
167
+ id: '1',
168
+ type: 'basic',
169
+ position: { x: 300, y: 300 },
170
+ measured: { width: 160, height: 60 },
171
+ data: { label: '节点 1' }
172
+ }
173
+ ])
174
+
175
+ const [edges, setEdges] = useState<Edge[]>([])
176
+
177
+ const handleInit = (engine: ReactEngine) => {
178
+ console.log('引擎初始化完成:', engine)
179
+
180
+ // 监听节点变化
181
+ engine.nodesManager.dataMap$.subscribe((nodesMap) => {
182
+ setNodes(Array.from(nodesMap.values()))
183
+ })
184
+
185
+ // 监听边变化
186
+ engine.edgesManager.dataMap$.subscribe((edgesMap) => {
187
+ setEdges(Array.from(edgesMap.values()))
188
+ })
189
+ }
190
+
191
+ return (
192
+ <div style={{ width: '100vw', height: '100vh' }}>
193
+ <Knotx
194
+ nodes={nodes}
195
+ edges={edges}
196
+ onInit={handleInit}
197
+ plugins={[]}
198
+ />
199
+ </div>
200
+ )
201
+ }
202
+ ```
203
+
204
+ ## 🔧 API 参考
205
+
206
+ ### Knotx 组件
207
+
208
+ `Knotx` 是主要的 React 组件,用于渲染节点编辑器。
209
+
210
+ #### Props
211
+
212
+ | 属性 | 类型 | 默认值 | 描述 |
213
+ |------|------|--------|------|
214
+ | `className` | `string` | - | 容器的 CSS 类名 |
215
+ | `style` | `CSSProperties` | - | 容器的内联样式 |
216
+ | `getContainerStyle` | `(engine: ReactEngine) => CSSProperties` | - | 获取容器样式的函数 |
217
+ | `initialNodes` | `Node[]` | `[]` | 初始节点数据 |
218
+ | `initialEdges` | `Edge[]` | `[]` | 初始边数据 |
219
+ | `nodes` | `Node[]` | - | 受控的节点数据 |
220
+ | `edges` | `Edge[]` | - | 受控的边数据 |
221
+ | `plugins` | `Plugin[]` | `[]` | 插件列表 |
222
+ | `pluginConfig` | `PluginConfig` | `{}` | 插件配置 |
223
+ | `disablePresetPlugins` | `boolean` | `false` | 禁用预设插件 |
224
+ | `onInit` | `(engine: ReactEngine) => void` | - | 引擎初始化回调 |
225
+ | `direction` | `'horizontal' \| 'vertical'` | `'horizontal'` | 布局方向 |
226
+ | `ref` | `Ref<KnotxInstance>` | - | 组件引用 |
227
+
228
+ #### 示例
229
+
230
+ ```tsx
231
+ import type { KnotxInstance, ReactEngine } from '@knotx/react'
232
+
233
+ import { Knotx } from '@knotx/react'
234
+ import React, { useRef } from 'react'
235
+
236
+ function App() {
237
+ const knotxRef = useRef<KnotxInstance>(null)
238
+
239
+ const handleInit = (engine: ReactEngine) => {
240
+ console.log('引擎初始化完成')
241
+ }
242
+
243
+ const handleRerender = () => {
244
+ knotxRef.current?.rerender()
245
+ }
246
+
247
+ return (
248
+ <div>
249
+ <button onClick={handleRerender}>重新渲染</button>
250
+ <Knotx
251
+ ref={knotxRef}
252
+ className="my-knotx"
253
+ style={{ border: '1px solid #ccc' }}
254
+ initialNodes={[]}
255
+ initialEdges={[]}
256
+ plugins={[]}
257
+ direction="horizontal"
258
+ onInit={handleInit}
259
+ getContainerStyle={engine => ({
260
+ backgroundColor: '#f5f5f5'
261
+ })}
262
+ />
263
+ </div>
264
+ )
265
+ }
266
+ ```
267
+
268
+ ### KnotxInstance 接口
269
+
270
+ 通过 `ref` 获取的组件实例接口。
271
+
272
+ #### 属性
273
+
274
+ | 属性 | 类型 | 描述 |
275
+ |------|------|------|
276
+ | `engineRef` | `MutableRefObject<ReactEngine \| null>` | 引擎实例引用 |
277
+ | `rerender` | `() => void` | 强制重新渲染组件 |
278
+
279
+ #### 示例
280
+
281
+ ```tsx
282
+ import type { KnotxInstance } from '@knotx/react'
283
+
284
+ import { Knotx } from '@knotx/react'
285
+ import React, { useEffect, useRef } from 'react'
286
+
287
+ function App() {
288
+ const knotxRef = useRef<KnotxInstance>(null)
289
+
290
+ useEffect(() => {
291
+ if (knotxRef.current) {
292
+ const engine = knotxRef.current.engineRef.current
293
+ if (engine) {
294
+ // 使用引擎 API
295
+ console.log('当前节点数量:', engine.nodesManager.dataMap$.value.size)
296
+ }
297
+ }
298
+ }, [])
299
+
300
+ return (
301
+ <Knotx
302
+ ref={knotxRef}
303
+ initialNodes={[]}
304
+ initialEdges={[]}
305
+ plugins={[]}
306
+ />
307
+ )
308
+ }
309
+ ```
310
+
311
+ ### ReactEngine 类型
312
+
313
+ 扩展自 `@knotx/core` 的 `Engine` 类型,专门用于 React 环境。
314
+
315
+ #### 主要方法
316
+
317
+ | 方法 | 描述 |
318
+ |------|------|
319
+ | `dispatchNodeOperation(operation: NodeOperation)` | 分发节点操作 |
320
+ | `dispatchEdgeOperation(operation: EdgeOperation)` | 分发边操作 |
321
+ | `getLayerComponents(layer: number)` | 获取层级组件 |
322
+ | `destroy()` | 销毁引擎实例 |
323
+
324
+ ### 插件配置
325
+
326
+ #### 插件配置类型
327
+
328
+ ```typescript
329
+ interface PluginConfig {
330
+ [pluginName: string]: any
331
+ }
332
+ ```
333
+
334
+ #### 示例
335
+
336
+ ```tsx
337
+ import { Canvas } from '@knotx/plugins-canvas'
338
+ import { Drag } from '@knotx/plugins-drag'
339
+ import { Knotx } from '@knotx/react'
340
+ import React from 'react'
341
+
342
+ function App() {
343
+ const pluginConfig = {
344
+ canvas: {
345
+ minScale: 0.1,
346
+ maxScale: 2.0,
347
+ wheel: {
348
+ step: 0.1,
349
+ wheelDisabled: false
350
+ }
351
+ },
352
+ drag: {
353
+ allowDrag: true,
354
+ dragThreshold: 5
355
+ }
356
+ }
357
+
358
+ return (
359
+ <Knotx
360
+ initialNodes={[]}
361
+ initialEdges={[]}
362
+ plugins={[Canvas, Drag]}
363
+ pluginConfig={pluginConfig}
364
+ />
365
+ )
366
+ }
367
+ ```
368
+
369
+ ## 🧩 插件系统
370
+
371
+ ### 常用插件
372
+
373
+ 以下是一些常用的插件:
374
+
375
+ - `@knotx/plugins-canvas` - 画布功能(缩放、平移)
376
+ - `@knotx/plugins-drag` - 拖拽功能
377
+ - `@knotx/plugins-selection` - 选择功能
378
+ - `@knotx/plugins-minimap` - 小地图
379
+ - `@knotx/plugins-background` - 背景
380
+ - `@knotx/plugins-bounding` - 边界调整
381
+ - `@knotx/plugins-connection-line` - 连接线
382
+ - `@knotx/plugins-group` - 分组
383
+ - `@knotx/plugins-history` - 历史记录
384
+
385
+ ### 创建自定义插件
386
+
387
+ ```tsx
388
+ import type { EdgeProps, Node } from '@knotx/core'
389
+
390
+ import { BasePlugin } from '@knotx/core'
391
+ import { edgeType, nodeType } from '@knotx/decorators'
392
+
393
+ class MyPlugin extends BasePlugin<'myPlugin'> {
394
+ name = 'myPlugin' as const
395
+
396
+ @nodeType('myNode')
397
+ renderMyNode({ node }: { node: Node }) {
398
+ return (
399
+ <div style={{ padding: '10px', border: '1px solid #ccc' }}>
400
+ 自定义节点:
401
+ {' '}
402
+ {node.id}
403
+ </div>
404
+ )
405
+ }
406
+
407
+ @edgeType('myEdge')
408
+ renderMyEdge(props: EdgeProps) {
409
+ return (
410
+ <path
411
+ d={props.path}
412
+ stroke="#ff0000"
413
+ strokeWidth={2}
414
+ fill="none"
415
+ />
416
+ )
417
+ }
418
+ }
419
+
420
+ // 使用插件
421
+ function App() {
422
+ return (
423
+ <Knotx
424
+ initialNodes={[
425
+ {
426
+ id: '1',
427
+ type: 'myNode',
428
+ position: { x: 100, y: 100 },
429
+ measured: { width: 120, height: 60 },
430
+ data: {}
431
+ }
432
+ ]}
433
+ initialEdges={[]}
434
+ plugins={[MyPlugin]}
435
+ />
436
+ )
437
+ }
438
+ ```
439
+
440
+ ## 📂 文件目录结构
441
+
442
+ ```
443
+ packages/react/
444
+ ├── src/
445
+ │ ├── components/ # React 组件
446
+ │ │ ├── content.tsx # 层级内容组件
447
+ │ │ └── index.ts # 组件导出
448
+ │ ├── hooks/ # React Hooks
449
+ │ │ ├── container.ts # 容器引用 Hook
450
+ │ │ ├── data.ts # 数据更新 Hook
451
+ │ │ ├── engine.ts # 引擎管理 Hook
452
+ │ │ ├── plugin.ts # 插件管理 Hook
453
+ │ │ └── index.ts # Hooks 导出
454
+ │ ├── definition.ts # 类型定义
455
+ │ ├── engine.ts # React 引擎初始化
456
+ │ ├── index.ts # 主入口文件
457
+ │ ├── knotx.tsx # 主要 Knotx 组件
458
+ │ └── layer.tsx # 层级渲染组件
459
+ ├── dist/ # 构建输出目录
460
+ ├── CHANGELOG.md # 变更日志
461
+ ├── package.json # 包配置
462
+ ├── README.md # 中文文档
463
+ ├── README.en.md # 英文文档
464
+ └── tsconfig.json # TypeScript 配置
465
+ ```
466
+
467
+ ### 核心文件说明
468
+
469
+ | 文件 | 描述 |
470
+ |------|------|
471
+ | `knotx.tsx` | 主要的 Knotx React 组件实现 |
472
+ | `definition.ts` | TypeScript 类型定义和接口 |
473
+ | `engine.ts` | React 引擎初始化和运行时配置 |
474
+ | `layer.tsx` | 层级渲染逻辑 |
475
+ | `components/content.tsx` | 层级内容渲染组件 |
476
+ | `hooks/container.ts` | 容器大小监听 Hook |
477
+ | `hooks/data.ts` | 数据差异更新 Hook |
478
+ | `hooks/engine.ts` | 引擎生命周期管理 Hook |
479
+ | `hooks/plugin.ts` | 插件合并和配置更新 Hook |
480
+
481
+ ## 🔗 类型导出
482
+
483
+ 该包重新导出了所有来自 `@knotx/core` 的类型,方便使用:
484
+
485
+ ```typescript
486
+ import type {
487
+ // 其他
488
+ Direction,
489
+ Edge,
490
+ EdgeOperation,
491
+ EdgeProps,
492
+
493
+ Engine,
494
+ EngineOptions,
495
+
496
+ IData,
497
+ IPlugin,
498
+
499
+ KnotxInstance,
500
+ KnotxProps,
501
+
502
+ Layer,
503
+ // 核心类型
504
+ Node,
505
+
506
+ // 操作类型
507
+ NodeOperation,
508
+ NodePosition,
509
+ // 属性类型
510
+ NodeProps,
511
+ Plugin,
512
+
513
+ // 配置类型
514
+ PluginConfigs,
515
+ // 位置类型
516
+ Position,
517
+ // React 特定类型
518
+ ReactEngine
519
+ } from '@knotx/react'
520
+ ```
521
+
522
+ ## 🎨 最佳实践
523
+
524
+ ### 1. 性能优化
525
+
526
+ ```tsx
527
+ import { Knotx } from '@knotx/react'
528
+ import React, { memo, useMemo } from 'react'
529
+
530
+ const MyKnotx = memo(({ data }: { data: any }) => {
531
+ // 使用 useMemo 缓存节点和边数据
532
+ const nodes = useMemo(() => {
533
+ return data.nodes || []
534
+ }, [data.nodes])
535
+
536
+ const edges = useMemo(() => {
537
+ return data.edges || []
538
+ }, [data.edges])
539
+
540
+ return (
541
+ <Knotx
542
+ nodes={nodes}
543
+ edges={edges}
544
+ plugins={[]}
545
+ />
546
+ )
547
+ })
548
+ ```
549
+
550
+ ### 2. 错误处理
551
+
552
+ ```tsx
553
+ import { Knotx } from '@knotx/react'
554
+ import React, { ErrorBoundary } from 'react'
555
+
556
+ class KnotxErrorBoundary extends React.Component {
557
+ constructor(props: any) {
558
+ super(props)
559
+ this.state = { hasError: false }
560
+ }
561
+
562
+ static getDerivedStateFromError(error: Error) {
563
+ // 处理错误:记录错误并返回错误状态
564
+ console.error('React Error Boundary:', error)
565
+ return { hasError: true }
566
+ }
567
+
568
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
569
+ // 处理错误:记录错误信息
570
+ console.error('Knotx Error:', error, errorInfo)
571
+
572
+ // 可以在这里添加错误上报逻辑
573
+ // 例如:this.reportError(error, errorInfo)
574
+ }
575
+
576
+ render() {
577
+ if (this.state.hasError) {
578
+ return <div>Something went wrong with Knotx.</div>
579
+ }
580
+
581
+ return this.props.children
582
+ }
583
+ }
584
+
585
+ function App() {
586
+ return (
587
+ <KnotxErrorBoundary>
588
+ <Knotx
589
+ initialNodes={[]}
590
+ initialEdges={[]}
591
+ plugins={[]}
592
+ />
593
+ </KnotxErrorBoundary>
594
+ )
595
+ }
596
+ ```
597
+
598
+ ### 3. 主题定制
599
+
600
+ ```tsx
601
+ import { BasePlugin } from '@knotx/core'
602
+ import { layer } from '@knotx/decorators'
603
+ import { Knotx } from '@knotx/react'
604
+ import React from 'react'
605
+
606
+ class ThemePlugin extends BasePlugin<'theme'> {
607
+ name = 'theme' as const
608
+
609
+ @layer(100)
610
+ renderTheme() {
611
+ return (
612
+ <style>
613
+ {`
614
+ .knotx-container {
615
+ --node-bg: #ffffff;
616
+ --node-border: #e1e4e8;
617
+ --edge-stroke: #586069;
618
+ --selection-bg: rgba(0, 123, 255, 0.1);
619
+ --selection-border: #007bff;
620
+ }
621
+
622
+ .dark-theme .knotx-container {
623
+ --node-bg: #2d3748;
624
+ --node-border: #4a5568;
625
+ --edge-stroke: #a0aec0;
626
+ --selection-bg: rgba(66, 153, 225, 0.1);
627
+ --selection-border: #4299e1;
628
+ }
629
+ `}
630
+ </style>
631
+ )
632
+ }
633
+ }
634
+
635
+ function App() {
636
+ return (
637
+ <div className="dark-theme">
638
+ <Knotx
639
+ initialNodes={[]}
640
+ initialEdges={[]}
641
+ plugins={[ThemePlugin]}
642
+ />
643
+ </div>
644
+ )
645
+ }
646
+ ```
647
+
648
+ ## 📝 常见问题
649
+
650
+ ### Q: 如何处理大量节点的性能问题?
651
+
652
+ A: 可以使用虚拟化技术,只渲染可见区域的节点:
653
+
654
+ ```tsx
655
+ import { BasePlugin } from '@knotx/core'
656
+ import { Knotx } from '@knotx/react'
657
+ import React from 'react'
658
+
659
+ // 实现虚拟化插件
660
+ class VirtualizationPlugin extends BasePlugin<'virtualization'> {
661
+ name = 'virtualization' as const
662
+
663
+ // 实现虚拟化逻辑
664
+ // ...
665
+ }
666
+ ```
667
+
668
+ ### Q: 如何实现节点的自定义连接点?
669
+
670
+ A: 使用 `@knotx/plugins-connection-line` 插件:
671
+
672
+ ```tsx
673
+ import { ConnectionLine } from '@knotx/plugins-connection-line'
674
+
675
+ // 在节点中添加连接点
676
+ function CustomNode({ node }) {
677
+ return (
678
+ <div>
679
+ <div
680
+ className="connection-handle"
681
+ data-handle-position="top"
682
+ />
683
+ 节点内容
684
+ </div>
685
+ )
686
+ }
687
+ ```
688
+
689
+ ### Q: 如何保存和恢复画布状态?
690
+
691
+ A: 通过引擎的数据管理器:
692
+
693
+ ```tsx
694
+ function saveState(engine: ReactEngine) {
695
+ const state = {
696
+ nodes: Array.from(engine.nodesManager.dataMap$.value.values()),
697
+ edges: Array.from(engine.edgesManager.dataMap$.value.values()),
698
+ viewport: engine.container
699
+ }
700
+ localStorage.setItem('knotx-state', JSON.stringify(state))
701
+ }
702
+
703
+ function loadState(engine: ReactEngine) {
704
+ const state = JSON.parse(localStorage.getItem('knotx-state') || '{}')
705
+ // 恢复状态逻辑
706
+ }
707
+ ```
708
+
709
+ ## 📄 许可证
710
+
711
+ MIT License - 详见 [LICENSE](https://github.com/boenfu/knotx/blob/main/LICENSE) 文件
712
+
713
+ ## 🤝 贡献
714
+
715
+ 欢迎提交 Pull Request 和 Issue!
716
+
717
+ ## 🔗 相关链接
718
+
719
+ - [GitHub 仓库](https://github.com/boenfu/knotx)
720
+ - [在线示例](https://knotx.dev)
721
+ - [API 文档](https://knotx.dev/docs)
722
+ - [更新日志](./CHANGELOG.md)