@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.en.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
+ > 🚀 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)