@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.
- package/README.en.md +722 -0
- package/README.md +722 -0
- package/package.json +8 -8
package/README.en.md
ADDED
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
# @knotx/react
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@knotx/react)
|
|
4
|
+
[](https://github.com/boenfu/knotx/blob/main/LICENSE)
|
|
5
|
+
|
|
6
|
+
> 🚀 A powerful React node editor component supporting visual flowcharts, organizational charts, mind maps, and more
|
|
7
|
+
|
|
8
|
+
## 📦 Installation
|
|
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
|
+
### Peer Dependencies
|
|
23
|
+
|
|
24
|
+
This package requires the following peer dependencies:
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"react": ">=17.0.0",
|
|
29
|
+
"react-dom": ">=17.0.0"
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 🎯 Overview
|
|
34
|
+
|
|
35
|
+
`@knotx/react` is the React adapter for the Knotx ecosystem, providing a powerful and flexible node editor component. It supports:
|
|
36
|
+
|
|
37
|
+
- 🎨 **Visual Node Editing** - Drag, zoom, and connect nodes
|
|
38
|
+
- 🔧 **Plugin System** - Highly extensible plugin architecture
|
|
39
|
+
- 🎭 **Custom Rendering** - Fully customizable node and edge rendering
|
|
40
|
+
- 📱 **Responsive Design** - Adapts to different screen sizes
|
|
41
|
+
- ⚡ **High Performance** - Reactive state management based on RxJS
|
|
42
|
+
- 🎪 **Rich Interactions** - Selection, dragging, zooming, minimap, and more
|
|
43
|
+
|
|
44
|
+
## 🚀 Quick Start
|
|
45
|
+
|
|
46
|
+
### Basic Usage
|
|
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: 'Node 1' }
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: '2',
|
|
65
|
+
type: 'basic',
|
|
66
|
+
position: { x: 600, y: 300 },
|
|
67
|
+
measured: { width: 160, height: 60 },
|
|
68
|
+
data: { label: 'Node 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={[]} // Plugin list
|
|
88
|
+
/>
|
|
89
|
+
</div>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export default App
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Custom Node Rendering
|
|
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
|
+
// Create custom node plugin
|
|
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: 'Custom Node' }
|
|
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
|
+
### Controlled Component Mode
|
|
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: 'Node 1' }
|
|
172
|
+
}
|
|
173
|
+
])
|
|
174
|
+
|
|
175
|
+
const [edges, setEdges] = useState<Edge[]>([])
|
|
176
|
+
|
|
177
|
+
const handleInit = (engine: ReactEngine) => {
|
|
178
|
+
console.log('Engine initialized:', engine)
|
|
179
|
+
|
|
180
|
+
// Listen to node changes
|
|
181
|
+
engine.nodesManager.dataMap$.subscribe((nodesMap) => {
|
|
182
|
+
setNodes(Array.from(nodesMap.values()))
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
// Listen to edge changes
|
|
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 Reference
|
|
205
|
+
|
|
206
|
+
### Knotx Component
|
|
207
|
+
|
|
208
|
+
`Knotx` is the main React component for rendering the node editor.
|
|
209
|
+
|
|
210
|
+
#### Props
|
|
211
|
+
|
|
212
|
+
| Property | Type | Default | Description |
|
|
213
|
+
|----------|------|---------|-------------|
|
|
214
|
+
| `className` | `string` | - | CSS class name for the container |
|
|
215
|
+
| `style` | `CSSProperties` | - | Inline styles for the container |
|
|
216
|
+
| `getContainerStyle` | `(engine: ReactEngine) => CSSProperties` | - | Function to get container styles |
|
|
217
|
+
| `initialNodes` | `Node[]` | `[]` | Initial node data |
|
|
218
|
+
| `initialEdges` | `Edge[]` | `[]` | Initial edge data |
|
|
219
|
+
| `nodes` | `Node[]` | - | Controlled node data |
|
|
220
|
+
| `edges` | `Edge[]` | - | Controlled edge data |
|
|
221
|
+
| `plugins` | `Plugin[]` | `[]` | Plugin list |
|
|
222
|
+
| `pluginConfig` | `PluginConfig` | `{}` | Plugin configuration |
|
|
223
|
+
| `disablePresetPlugins` | `boolean` | `false` | Disable preset plugins |
|
|
224
|
+
| `onInit` | `(engine: ReactEngine) => void` | - | Engine initialization callback |
|
|
225
|
+
| `direction` | `'horizontal' \| 'vertical'` | `'horizontal'` | Layout direction |
|
|
226
|
+
| `ref` | `Ref<KnotxInstance>` | - | Component reference |
|
|
227
|
+
|
|
228
|
+
#### Example
|
|
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('Engine initialized')
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const handleRerender = () => {
|
|
244
|
+
knotxRef.current?.rerender()
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return (
|
|
248
|
+
<div>
|
|
249
|
+
<button onClick={handleRerender}>Rerender</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 Interface
|
|
269
|
+
|
|
270
|
+
The component instance interface obtained through `ref`.
|
|
271
|
+
|
|
272
|
+
#### Properties
|
|
273
|
+
|
|
274
|
+
| Property | Type | Description |
|
|
275
|
+
|----------|------|-------------|
|
|
276
|
+
| `engineRef` | `MutableRefObject<ReactEngine \| null>` | Engine instance reference |
|
|
277
|
+
| `rerender` | `() => void` | Force rerender the component |
|
|
278
|
+
|
|
279
|
+
#### Example
|
|
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
|
+
// Use engine API
|
|
295
|
+
console.log('Current node count:', 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 Type
|
|
312
|
+
|
|
313
|
+
Extended from `@knotx/core`'s `Engine` type, specifically for React environment.
|
|
314
|
+
|
|
315
|
+
#### Main Methods
|
|
316
|
+
|
|
317
|
+
| Method | Description |
|
|
318
|
+
|--------|-------------|
|
|
319
|
+
| `dispatchNodeOperation(operation: NodeOperation)` | Dispatch node operation |
|
|
320
|
+
| `dispatchEdgeOperation(operation: EdgeOperation)` | Dispatch edge operation |
|
|
321
|
+
| `getLayerComponents(layer: number)` | Get layer components |
|
|
322
|
+
| `destroy()` | Destroy engine instance |
|
|
323
|
+
|
|
324
|
+
### Plugin Configuration
|
|
325
|
+
|
|
326
|
+
#### Plugin Configuration Type
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
interface PluginConfig {
|
|
330
|
+
[pluginName: string]: any
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
#### Example
|
|
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
|
+
## 🧩 Plugin System
|
|
370
|
+
|
|
371
|
+
### Common Plugins
|
|
372
|
+
|
|
373
|
+
Here are some commonly used plugins:
|
|
374
|
+
|
|
375
|
+
- `@knotx/plugins-canvas` - Canvas functionality (zoom, pan)
|
|
376
|
+
- `@knotx/plugins-drag` - Drag functionality
|
|
377
|
+
- `@knotx/plugins-selection` - Selection functionality
|
|
378
|
+
- `@knotx/plugins-minimap` - Minimap
|
|
379
|
+
- `@knotx/plugins-background` - Background
|
|
380
|
+
- `@knotx/plugins-bounding` - Boundary adjustment
|
|
381
|
+
- `@knotx/plugins-connection-line` - Connection lines
|
|
382
|
+
- `@knotx/plugins-group` - Grouping
|
|
383
|
+
- `@knotx/plugins-history` - History management
|
|
384
|
+
|
|
385
|
+
### Creating Custom Plugins
|
|
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
|
+
Custom Node:
|
|
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
|
+
// Using the plugin
|
|
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
|
+
## 📂 Directory Structure
|
|
441
|
+
|
|
442
|
+
```
|
|
443
|
+
packages/react/
|
|
444
|
+
├── src/
|
|
445
|
+
│ ├── components/ # React components
|
|
446
|
+
│ │ ├── content.tsx # Layer content component
|
|
447
|
+
│ │ └── index.ts # Component exports
|
|
448
|
+
│ ├── hooks/ # React Hooks
|
|
449
|
+
│ │ ├── container.ts # Container reference Hook
|
|
450
|
+
│ │ ├── data.ts # Data update Hook
|
|
451
|
+
│ │ ├── engine.ts # Engine management Hook
|
|
452
|
+
│ │ ├── plugin.ts # Plugin management Hook
|
|
453
|
+
│ │ └── index.ts # Hooks exports
|
|
454
|
+
│ ├── definition.ts # Type definitions
|
|
455
|
+
│ ├── engine.ts # React engine initialization
|
|
456
|
+
│ ├── index.ts # Main entry point
|
|
457
|
+
│ ├── knotx.tsx # Main Knotx component
|
|
458
|
+
│ └── layer.tsx # Layer rendering component
|
|
459
|
+
├── dist/ # Build output directory
|
|
460
|
+
├── CHANGELOG.md # Changelog
|
|
461
|
+
├── package.json # Package configuration
|
|
462
|
+
├── README.md # Chinese documentation
|
|
463
|
+
├── README.en.md # English documentation
|
|
464
|
+
└── tsconfig.json # TypeScript configuration
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### Core File Descriptions
|
|
468
|
+
|
|
469
|
+
| File | Description |
|
|
470
|
+
|------|-------------|
|
|
471
|
+
| `knotx.tsx` | Main Knotx React component implementation |
|
|
472
|
+
| `definition.ts` | TypeScript type definitions and interfaces |
|
|
473
|
+
| `engine.ts` | React engine initialization and runtime configuration |
|
|
474
|
+
| `layer.tsx` | Layer rendering logic |
|
|
475
|
+
| `components/content.tsx` | Layer content rendering component |
|
|
476
|
+
| `hooks/container.ts` | Container size monitoring Hook |
|
|
477
|
+
| `hooks/data.ts` | Data diff update Hook |
|
|
478
|
+
| `hooks/engine.ts` | Engine lifecycle management Hook |
|
|
479
|
+
| `hooks/plugin.ts` | Plugin merging and configuration update Hook |
|
|
480
|
+
|
|
481
|
+
## 🔗 Type Exports
|
|
482
|
+
|
|
483
|
+
This package re-exports all types from `@knotx/core` for convenience:
|
|
484
|
+
|
|
485
|
+
```typescript
|
|
486
|
+
import type {
|
|
487
|
+
// Others
|
|
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
|
+
// Core types
|
|
504
|
+
Node,
|
|
505
|
+
|
|
506
|
+
// Operation types
|
|
507
|
+
NodeOperation,
|
|
508
|
+
NodePosition,
|
|
509
|
+
// Property types
|
|
510
|
+
NodeProps,
|
|
511
|
+
Plugin,
|
|
512
|
+
|
|
513
|
+
// Configuration types
|
|
514
|
+
PluginConfigs,
|
|
515
|
+
// Position types
|
|
516
|
+
Position,
|
|
517
|
+
// React-specific types
|
|
518
|
+
ReactEngine
|
|
519
|
+
} from '@knotx/react'
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
## 🎨 Best Practices
|
|
523
|
+
|
|
524
|
+
### 1. Performance Optimization
|
|
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
|
+
// Use useMemo to cache node and edge data
|
|
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. Error Handling
|
|
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
|
+
// Handle error: log error and return error state
|
|
564
|
+
console.error('React Error Boundary:', error)
|
|
565
|
+
return { hasError: true }
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
|
569
|
+
// Handle error: log error information
|
|
570
|
+
console.error('Knotx Error:', error, errorInfo)
|
|
571
|
+
|
|
572
|
+
// You can add error reporting logic here
|
|
573
|
+
// For example: 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. Theme Customization
|
|
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
|
+
## 📝 FAQ
|
|
649
|
+
|
|
650
|
+
### Q: How to handle performance issues with large numbers of nodes?
|
|
651
|
+
|
|
652
|
+
A: Use virtualization techniques to render only visible nodes:
|
|
653
|
+
|
|
654
|
+
```tsx
|
|
655
|
+
import { BasePlugin } from '@knotx/core'
|
|
656
|
+
import { Knotx } from '@knotx/react'
|
|
657
|
+
import React from 'react'
|
|
658
|
+
|
|
659
|
+
// Implement virtualization plugin
|
|
660
|
+
class VirtualizationPlugin extends BasePlugin<'virtualization'> {
|
|
661
|
+
name = 'virtualization' as const
|
|
662
|
+
|
|
663
|
+
// Implement virtualization logic
|
|
664
|
+
// ...
|
|
665
|
+
}
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
### Q: How to implement custom connection points for nodes?
|
|
669
|
+
|
|
670
|
+
A: Use the `@knotx/plugins-connection-line` plugin:
|
|
671
|
+
|
|
672
|
+
```tsx
|
|
673
|
+
import { ConnectionLine } from '@knotx/plugins-connection-line'
|
|
674
|
+
|
|
675
|
+
// Add connection points in nodes
|
|
676
|
+
function CustomNode({ node }) {
|
|
677
|
+
return (
|
|
678
|
+
<div>
|
|
679
|
+
<div
|
|
680
|
+
className="connection-handle"
|
|
681
|
+
data-handle-position="top"
|
|
682
|
+
/>
|
|
683
|
+
Node content
|
|
684
|
+
</div>
|
|
685
|
+
)
|
|
686
|
+
}
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
### Q: How to save and restore canvas state?
|
|
690
|
+
|
|
691
|
+
A: Use the engine's data managers:
|
|
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
|
+
// Restore state logic
|
|
706
|
+
}
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
## 📄 License
|
|
710
|
+
|
|
711
|
+
MIT License - See [LICENSE](https://github.com/boenfu/knotx/blob/main/LICENSE) file for details
|
|
712
|
+
|
|
713
|
+
## 🤝 Contributing
|
|
714
|
+
|
|
715
|
+
Pull requests and issues are welcome!
|
|
716
|
+
|
|
717
|
+
## 🔗 Related Links
|
|
718
|
+
|
|
719
|
+
- [GitHub Repository](https://github.com/boenfu/knotx)
|
|
720
|
+
- [Online Examples](https://knotx.dev)
|
|
721
|
+
- [API Documentation](https://knotx.dev/docs)
|
|
722
|
+
- [Changelog](./CHANGELOG.md)
|