power-link 1.0.11 → 2.0.0
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.md +294 -9
- package/dist/index.d.mts +111 -3
- package/dist/index.d.ts +111 -3
- package/dist/index.global.js +339 -41
- package/dist/index.js +339 -41
- package/dist/index.mjs +339 -41
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -7,14 +7,8 @@ A pure TypeScript visual node connector for creating draggable connections betwe
|
|
|
7
7
|
|
|
8
8
|

|
|
9
9
|
|
|
10
|
-
### 📹 Demo Video
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
<source src="https://github.com/Tem-man/power-link/blob/main/public/images/video.mp4" type="video/mp4">
|
|
14
|
-
Your browser does not support the video tag.
|
|
15
|
-
</video>
|
|
16
|
-
|
|
17
|
-
**Watch the demo video** to see power-link in action! [Download video](https://github.com/Tem-man/node-link-utils/raw/main/packages/images/video.mp4)
|
|
11
|
+
### [online demo](https://tem-man.github.io/power-link)
|
|
18
12
|
|
|
19
13
|
## ✨ Features
|
|
20
14
|
|
|
@@ -80,6 +74,12 @@ const connector = new Connector({
|
|
|
80
74
|
console.log("View changed:", viewState);
|
|
81
75
|
// viewState: { scale: 1, translateX: 0, translateY: 0 }
|
|
82
76
|
// Save view state to restore later
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
onNodeMove: ({ id, x, y }) => {
|
|
80
|
+
console.log("Node moved:", id, "to", x, y);
|
|
81
|
+
// Synchronize node position with your state management
|
|
82
|
+
// This prevents nodes from jumping back after dragging
|
|
83
83
|
}
|
|
84
84
|
});
|
|
85
85
|
|
|
@@ -142,6 +142,9 @@ connector.registerNode("node2", node2, {
|
|
|
142
142
|
| `onConnect` | Function | `() => {}` | Callback when connection is created |
|
|
143
143
|
| `onDisconnect` | Function | `() => {}` | Callback when connection is removed |
|
|
144
144
|
| `onViewChange` | Function | `() => {}` | Callback when view state changes (zoom/pan) |
|
|
145
|
+
| `onNodeMove` | Function | `() => {}` | Callback when a node is dragged (receives `{ id, x, y }`) |
|
|
146
|
+
| `onNodeSelect` | Function | `() => {}` | Callback when a node is selected/deselected (receives node info or `null`) |
|
|
147
|
+
| `onNodeDelete` | Function | `() => {}` | Callback when a node is deleted (receives `{ id, info }`) |
|
|
145
148
|
|
|
146
149
|
### Methods
|
|
147
150
|
|
|
@@ -156,9 +159,11 @@ Register a node for connection.
|
|
|
156
159
|
- `options` (Object): Node configuration
|
|
157
160
|
- `dotPositions` (String | Array): Connection dot positions
|
|
158
161
|
- `'both'`: Both left and right dots
|
|
162
|
+
- `'left'`: Only left dot (string format)
|
|
163
|
+
- `'right'`: Only right dot (string format)
|
|
159
164
|
- `['left', 'right']`: Array format, both sides
|
|
160
|
-
- `['left']`: Only left dot
|
|
161
|
-
- `['right']`: Only right dot
|
|
165
|
+
- `['left']`: Only left dot (array format)
|
|
166
|
+
- `['right']`: Only right dot (array format)
|
|
162
167
|
- `info` (Object): Node extraneous information
|
|
163
168
|
|
|
164
169
|
**Returns:** Node object
|
|
@@ -253,6 +258,34 @@ Update node position (called when node is moved).
|
|
|
253
258
|
|
|
254
259
|
- `nodeId` (String): Node ID
|
|
255
260
|
|
|
261
|
+
#### `deleteNode(id)`
|
|
262
|
+
|
|
263
|
+
Delete a node and all its connections. This method removes the node from the connector, disconnects all associated connections, and triggers the `onNodeDelete` callback.
|
|
264
|
+
|
|
265
|
+
**Note:** This method does not remove the DOM element itself. You should handle DOM removal in your framework (e.g., Vue/React) within the `onNodeDelete` callback.
|
|
266
|
+
|
|
267
|
+
**Parameters:**
|
|
268
|
+
|
|
269
|
+
- `id` (String): Node ID to delete
|
|
270
|
+
|
|
271
|
+
**Example:**
|
|
272
|
+
|
|
273
|
+
```javascript
|
|
274
|
+
// Delete a specific node
|
|
275
|
+
connector.deleteNode('node1');
|
|
276
|
+
|
|
277
|
+
// In your connector setup, handle the deletion callback
|
|
278
|
+
const connector = new Connector({
|
|
279
|
+
container: container,
|
|
280
|
+
onNodeDelete: ({ id, info }) => {
|
|
281
|
+
console.log('Node deleted:', id);
|
|
282
|
+
// Remove node from your state management
|
|
283
|
+
// In Vue: nodes.value = nodes.value.filter(n => n.id !== id);
|
|
284
|
+
// In React: setNodes(nodes.filter(n => n.id !== id));
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
```
|
|
288
|
+
|
|
256
289
|
#### `destroy(options)`
|
|
257
290
|
|
|
258
291
|
Destroy the connector and clean up all resources.
|
|
@@ -370,6 +403,104 @@ Update all connection line positions (useful when container size changes or afte
|
|
|
370
403
|
connector.updateAllConnections(); // Refresh all connection lines
|
|
371
404
|
```
|
|
372
405
|
|
|
406
|
+
#### `export()`
|
|
407
|
+
|
|
408
|
+
Export the current topology (nodes, connections, and view state) as a JSON object.
|
|
409
|
+
|
|
410
|
+
**Returns:** ExportData object containing:
|
|
411
|
+
- `nodes`: Array of node data (id, x, y, info, dotPositions)
|
|
412
|
+
- `connections`: Array of connection data (from, to, fromDot, toDot)
|
|
413
|
+
- `viewState`: Current view state (scale, translateX, translateY)
|
|
414
|
+
|
|
415
|
+
**Example:**
|
|
416
|
+
|
|
417
|
+
```javascript
|
|
418
|
+
const data = connector.export();
|
|
419
|
+
console.log(data);
|
|
420
|
+
// {
|
|
421
|
+
// nodes: [
|
|
422
|
+
// { id: 'node1', x: 100, y: 100, info: {...}, dotPositions: ['right'] },
|
|
423
|
+
// { id: 'node2', x: 400, y: 100, info: {...}, dotPositions: ['left'] }
|
|
424
|
+
// ],
|
|
425
|
+
// connections: [
|
|
426
|
+
// { from: 'node1', to: 'node2', fromDot: 'right', toDot: 'left' }
|
|
427
|
+
// ],
|
|
428
|
+
// viewState: { scale: 1, translateX: 0, translateY: 0 }
|
|
429
|
+
// }
|
|
430
|
+
|
|
431
|
+
// Save to localStorage
|
|
432
|
+
localStorage.setItem('topology', JSON.stringify(data));
|
|
433
|
+
|
|
434
|
+
// Or send to server
|
|
435
|
+
await fetch('/api/topology', {
|
|
436
|
+
method: 'POST',
|
|
437
|
+
body: JSON.stringify(data)
|
|
438
|
+
});
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
#### `import(data, nodeFactory?)`
|
|
442
|
+
|
|
443
|
+
Restore topology from exported data. Supports two modes:
|
|
444
|
+
|
|
445
|
+
**Parameters:**
|
|
446
|
+
|
|
447
|
+
- `data` (ExportData): Topology data returned by `export()`
|
|
448
|
+
- `nodeFactory` (Function, optional): Factory function for creating DOM elements (native JS mode only)
|
|
449
|
+
|
|
450
|
+
**Two Usage Modes:**
|
|
451
|
+
|
|
452
|
+
1. **Framework Mode (Vue/React/etc.) - No nodeFactory**
|
|
453
|
+
- Framework handles DOM rendering
|
|
454
|
+
- Library finds elements by `id` attribute
|
|
455
|
+
- Use when nodes are managed by framework reactivity
|
|
456
|
+
|
|
457
|
+
2. **Native JS Mode - With nodeFactory**
|
|
458
|
+
- Library calls factory to create DOM elements
|
|
459
|
+
- Library handles positioning and mounting
|
|
460
|
+
- Use for pure JavaScript applications
|
|
461
|
+
|
|
462
|
+
**Returns:** Promise<void>
|
|
463
|
+
|
|
464
|
+
**Example (Framework Mode - Vue):**
|
|
465
|
+
|
|
466
|
+
```javascript
|
|
467
|
+
// Save
|
|
468
|
+
const data = connector.export();
|
|
469
|
+
localStorage.setItem('topology', JSON.stringify(data));
|
|
470
|
+
|
|
471
|
+
// Load
|
|
472
|
+
const savedData = JSON.parse(localStorage.getItem('topology'));
|
|
473
|
+
// 1. Update framework reactive state (triggers DOM rendering)
|
|
474
|
+
nodes.value = savedData.nodes;
|
|
475
|
+
// 2. Wait for DOM to be ready
|
|
476
|
+
await nextTick();
|
|
477
|
+
// 3. Import (library finds elements by id and restores connections)
|
|
478
|
+
await connector.import(savedData);
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
**Example (Native JS Mode):**
|
|
482
|
+
|
|
483
|
+
```javascript
|
|
484
|
+
// Save
|
|
485
|
+
const data = connector.export();
|
|
486
|
+
localStorage.setItem('topology', JSON.stringify(data));
|
|
487
|
+
|
|
488
|
+
// Load
|
|
489
|
+
const savedData = JSON.parse(localStorage.getItem('topology'));
|
|
490
|
+
await connector.import(savedData, (nodeData) => {
|
|
491
|
+
// Factory function: create and return DOM element
|
|
492
|
+
const el = document.createElement('div');
|
|
493
|
+
el.id = nodeData.id;
|
|
494
|
+
el.className = 'node';
|
|
495
|
+
el.textContent = nodeData.info?.name || nodeData.id;
|
|
496
|
+
el.style.position = 'absolute';
|
|
497
|
+
// Library will set left/top automatically
|
|
498
|
+
return el;
|
|
499
|
+
});
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
**Note:** Connections are restored silently (without triggering `onConnect` callbacks) to avoid duplicate events during data restoration.
|
|
503
|
+
|
|
373
504
|
|
|
374
505
|
## 🎨 Usage Examples
|
|
375
506
|
|
|
@@ -749,6 +880,65 @@ const connector = new Connector({
|
|
|
749
880
|
|
|
750
881
|
// Save view state to restore later
|
|
751
882
|
saveViewState(viewState);
|
|
883
|
+
},
|
|
884
|
+
|
|
885
|
+
onNodeMove: ({ id, x, y }) => {
|
|
886
|
+
console.log("Node moved:", id, "to", x, y);
|
|
887
|
+
// Synchronize node position with your state management
|
|
888
|
+
// This prevents nodes from jumping back to original position after dragging
|
|
889
|
+
// In Vue: const node = nodes.value.find(n => n.id === id); if (node) { node.x = x; node.y = y; }
|
|
890
|
+
// In React: setNodes(nodes.map(n => n.id === id ? { ...n, x, y } : n));
|
|
891
|
+
},
|
|
892
|
+
|
|
893
|
+
onNodeSelect: (info) => {
|
|
894
|
+
if (info) {
|
|
895
|
+
console.log("Node selected:", info.id, info.info);
|
|
896
|
+
} else {
|
|
897
|
+
console.log("Node deselected");
|
|
898
|
+
}
|
|
899
|
+
},
|
|
900
|
+
|
|
901
|
+
onNodeDelete: ({ id, info }) => {
|
|
902
|
+
console.log("Node deleted:", id);
|
|
903
|
+
// Remove node from your state management
|
|
904
|
+
// In Vue: nodes.value = nodes.value.filter(n => n.id !== id);
|
|
905
|
+
// In React: setNodes(nodes.filter(n => n.id !== id));
|
|
906
|
+
}
|
|
907
|
+
});
|
|
908
|
+
```
|
|
909
|
+
|
|
910
|
+
### Node Management
|
|
911
|
+
|
|
912
|
+
#### Node Position Synchronization
|
|
913
|
+
|
|
914
|
+
When using frameworks like Vue or React, you need to synchronize node positions after dragging to prevent nodes from jumping back to their original position. Use the `onNodeMove` callback:
|
|
915
|
+
|
|
916
|
+
**Vue Example:**
|
|
917
|
+
|
|
918
|
+
```javascript
|
|
919
|
+
const connector = new Connector({
|
|
920
|
+
container: containerRef.value,
|
|
921
|
+
onNodeMove: ({ id, x, y }) => {
|
|
922
|
+
// Update Vue reactive state
|
|
923
|
+
const node = nodes.value.find(n => n.id === id);
|
|
924
|
+
if (node) {
|
|
925
|
+
node.x = x;
|
|
926
|
+
node.y = y;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
});
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
**React Example:**
|
|
933
|
+
|
|
934
|
+
```javascript
|
|
935
|
+
const connector = new Connector({
|
|
936
|
+
container: containerRef.current,
|
|
937
|
+
onNodeMove: ({ id, x, y }) => {
|
|
938
|
+
// Update React state
|
|
939
|
+
setNodes(prevNodes =>
|
|
940
|
+
prevNodes.map(n => n.id === id ? { ...n, x, y } : n)
|
|
941
|
+
);
|
|
752
942
|
}
|
|
753
943
|
});
|
|
754
944
|
```
|
|
@@ -780,6 +970,101 @@ connector.resetView(); // Reset to default (scale: 1, translateX: 0, translateY:
|
|
|
780
970
|
connector.updateAllConnections();
|
|
781
971
|
```
|
|
782
972
|
|
|
973
|
+
### Save and Restore Topology
|
|
974
|
+
|
|
975
|
+
The `export()` and `import()` methods allow you to save and restore the entire topology (nodes, connections, and view state).
|
|
976
|
+
|
|
977
|
+
**Save Topology:**
|
|
978
|
+
|
|
979
|
+
```javascript
|
|
980
|
+
// Export current topology
|
|
981
|
+
const data = connector.export();
|
|
982
|
+
|
|
983
|
+
// Save to localStorage
|
|
984
|
+
localStorage.setItem('topology', JSON.stringify(data));
|
|
985
|
+
|
|
986
|
+
// Or save to server
|
|
987
|
+
await fetch('/api/topology', {
|
|
988
|
+
method: 'POST',
|
|
989
|
+
headers: { 'Content-Type': 'application/json' },
|
|
990
|
+
body: JSON.stringify(data)
|
|
991
|
+
});
|
|
992
|
+
```
|
|
993
|
+
|
|
994
|
+
**Restore Topology (Framework Mode - Vue Example):**
|
|
995
|
+
|
|
996
|
+
```vue
|
|
997
|
+
<script setup>
|
|
998
|
+
import { ref, onMounted, nextTick } from 'vue';
|
|
999
|
+
import Connector from 'power-link';
|
|
1000
|
+
|
|
1001
|
+
const containerRef = ref(null);
|
|
1002
|
+
const nodes = ref([]);
|
|
1003
|
+
let connector = null;
|
|
1004
|
+
|
|
1005
|
+
const saveTopology = () => {
|
|
1006
|
+
const data = connector.export();
|
|
1007
|
+
// Merge custom fields (label, type, etc.) if needed
|
|
1008
|
+
data.nodes = data.nodes.map(exportNode => {
|
|
1009
|
+
const origin = nodes.value.find(n => n.id === exportNode.id);
|
|
1010
|
+
return { ...exportNode, label: origin?.label, type: origin?.type };
|
|
1011
|
+
});
|
|
1012
|
+
localStorage.setItem('topology', JSON.stringify(data));
|
|
1013
|
+
};
|
|
1014
|
+
|
|
1015
|
+
const loadTopology = async () => {
|
|
1016
|
+
const saved = localStorage.getItem('topology');
|
|
1017
|
+
if (saved) {
|
|
1018
|
+
const data = JSON.parse(saved);
|
|
1019
|
+
// 1. Update reactive state (triggers framework rendering)
|
|
1020
|
+
nodes.value = data.nodes;
|
|
1021
|
+
// 2. Wait for DOM to be ready
|
|
1022
|
+
await nextTick();
|
|
1023
|
+
// 3. Import (library finds elements by id and restores connections + view state)
|
|
1024
|
+
await connector.import(data);
|
|
1025
|
+
}
|
|
1026
|
+
};
|
|
1027
|
+
|
|
1028
|
+
onMounted(() => {
|
|
1029
|
+
connector = new Connector({ container: containerRef.value });
|
|
1030
|
+
loadTopology();
|
|
1031
|
+
});
|
|
1032
|
+
</script>
|
|
1033
|
+
```
|
|
1034
|
+
|
|
1035
|
+
**Restore Topology (Native JS Mode):**
|
|
1036
|
+
|
|
1037
|
+
```javascript
|
|
1038
|
+
const loadTopology = async () => {
|
|
1039
|
+
const saved = localStorage.getItem('topology');
|
|
1040
|
+
if (saved) {
|
|
1041
|
+
const data = JSON.parse(saved);
|
|
1042
|
+
// Import with factory function
|
|
1043
|
+
await connector.import(data, (nodeData) => {
|
|
1044
|
+
const el = document.createElement('div');
|
|
1045
|
+
el.id = nodeData.id;
|
|
1046
|
+
el.className = 'node';
|
|
1047
|
+
el.textContent = nodeData.info?.name || nodeData.id;
|
|
1048
|
+
// Library will set position automatically
|
|
1049
|
+
return el;
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
};
|
|
1053
|
+
```
|
|
1054
|
+
|
|
1055
|
+
**What's Included in Export:**
|
|
1056
|
+
|
|
1057
|
+
- **Nodes**: ID, position (x, y), custom info, dot positions
|
|
1058
|
+
- **Connections**: Source/target nodes, dot positions
|
|
1059
|
+
- **View State**: Current zoom level and pan position (scale, translateX, translateY)
|
|
1060
|
+
|
|
1061
|
+
**Benefits:**
|
|
1062
|
+
|
|
1063
|
+
- ✅ Save/load entire graph state
|
|
1064
|
+
- ✅ Restore view position and zoom level
|
|
1065
|
+
- ✅ Preserve all node positions and connections
|
|
1066
|
+
- ✅ Works with any storage backend (localStorage, database, etc.)
|
|
1067
|
+
|
|
783
1068
|
## 🔧 Browser Support
|
|
784
1069
|
|
|
785
1070
|
- Chrome (latest)
|
package/dist/index.d.mts
CHANGED
|
@@ -28,6 +28,10 @@ interface ConnectorNode {
|
|
|
28
28
|
dots: Partial<Record<DotPosition, Dot>>;
|
|
29
29
|
dotPositions: DotPosition[];
|
|
30
30
|
connections: Connection[];
|
|
31
|
+
/** 节点相对于 contentWrapper 的 x 坐标(由库内部维护) */
|
|
32
|
+
x: number;
|
|
33
|
+
/** 节点相对于 contentWrapper 的 y 坐标(由库内部维护) */
|
|
34
|
+
y: number;
|
|
31
35
|
}
|
|
32
36
|
/** 连接对象 */
|
|
33
37
|
interface Connection {
|
|
@@ -38,7 +42,7 @@ interface Connection {
|
|
|
38
42
|
toDot: Dot;
|
|
39
43
|
line: SVGPathElement;
|
|
40
44
|
hoverPath?: SVGPathElement;
|
|
41
|
-
deleteButton:
|
|
45
|
+
deleteButton: SVGGElement;
|
|
42
46
|
}
|
|
43
47
|
/** 连接信息(回调 / API 返回用) */
|
|
44
48
|
interface ConnectionInfo {
|
|
@@ -66,6 +70,24 @@ interface ConnectorConfig {
|
|
|
66
70
|
minZoom: number;
|
|
67
71
|
maxZoom: number;
|
|
68
72
|
zoomStep: number;
|
|
73
|
+
/** 节点选中时的高亮颜色 */
|
|
74
|
+
selectedBorderColor: string;
|
|
75
|
+
}
|
|
76
|
+
/** 节点移动信息(拖拽回调用) */
|
|
77
|
+
interface NodeMoveInfo {
|
|
78
|
+
id: string;
|
|
79
|
+
x: number;
|
|
80
|
+
y: number;
|
|
81
|
+
}
|
|
82
|
+
/** 节点选中信息 */
|
|
83
|
+
interface NodeSelectInfo {
|
|
84
|
+
id: string;
|
|
85
|
+
info?: Record<string, unknown>;
|
|
86
|
+
}
|
|
87
|
+
/** 节点删除信息 */
|
|
88
|
+
interface NodeDeleteInfo {
|
|
89
|
+
id: string;
|
|
90
|
+
info?: Record<string, unknown>;
|
|
69
91
|
}
|
|
70
92
|
/** 构造函数选项 */
|
|
71
93
|
interface ConnectorOptions extends Partial<ConnectorConfig> {
|
|
@@ -73,17 +95,50 @@ interface ConnectorOptions extends Partial<ConnectorConfig> {
|
|
|
73
95
|
onConnect?: (info: ConnectionInfo) => void;
|
|
74
96
|
onDisconnect?: (info: ConnectionInfo) => void;
|
|
75
97
|
onViewChange?: (state: ViewState) => void;
|
|
98
|
+
onNodeMove?: (info: NodeMoveInfo) => void;
|
|
99
|
+
/** 节点被选中/取消选中时触发,取消选中时 info 为 null */
|
|
100
|
+
onNodeSelect?: (info: NodeSelectInfo | null) => void;
|
|
101
|
+
/** 节点被删除时触发 */
|
|
102
|
+
onNodeDelete?: (info: NodeDeleteInfo) => void;
|
|
76
103
|
config?: Partial<ConnectorConfig>;
|
|
77
104
|
}
|
|
78
105
|
/** 节点注册选项 */
|
|
79
106
|
interface RegisterNodeOptions {
|
|
80
107
|
info?: Record<string, unknown>;
|
|
81
|
-
dotPositions?: 'both' | DotPosition[];
|
|
108
|
+
dotPositions?: 'both' | DotPosition | DotPosition[];
|
|
82
109
|
}
|
|
83
110
|
/** 静默操作选项 */
|
|
84
111
|
interface SilentOptions {
|
|
85
112
|
silent?: boolean;
|
|
86
113
|
}
|
|
114
|
+
/** 导出的单个节点数据 */
|
|
115
|
+
interface ExportNodeData {
|
|
116
|
+
id: string;
|
|
117
|
+
x: number;
|
|
118
|
+
y: number;
|
|
119
|
+
info?: Record<string, unknown>;
|
|
120
|
+
dotPositions: DotPosition[];
|
|
121
|
+
}
|
|
122
|
+
/** 导出的单条连接数据 */
|
|
123
|
+
interface ExportConnectionData {
|
|
124
|
+
from: string;
|
|
125
|
+
to: string;
|
|
126
|
+
fromDot: DotPosition;
|
|
127
|
+
toDot: DotPosition;
|
|
128
|
+
}
|
|
129
|
+
/** export() 返回的完整拓扑数据 */
|
|
130
|
+
interface ExportData {
|
|
131
|
+
nodes: ExportNodeData[];
|
|
132
|
+
connections: ExportConnectionData[];
|
|
133
|
+
/** 视图状态(缩放、平移),可选 */
|
|
134
|
+
viewState?: ViewState;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* 节点工厂函数(用于 import() 的原生 JS 场景)
|
|
138
|
+
* 接收节点数据,返回对应的 HTMLElement(或 Promise)
|
|
139
|
+
* 库会负责定位和挂载,工厂只需构建元素结构
|
|
140
|
+
*/
|
|
141
|
+
type NodeFactory = (nodeData: ExportNodeData) => HTMLElement | Promise<HTMLElement>;
|
|
87
142
|
|
|
88
143
|
/**
|
|
89
144
|
* @fileoverview 连线器主类
|
|
@@ -113,6 +168,24 @@ declare class Connector {
|
|
|
113
168
|
* 更新节点位置(当节点移动时调用)
|
|
114
169
|
*/
|
|
115
170
|
updateNodePosition(nodeId: string): void;
|
|
171
|
+
/**
|
|
172
|
+
* 选中指定节点
|
|
173
|
+
* @param id - 节点 ID
|
|
174
|
+
*/
|
|
175
|
+
selectNode(id: string): void;
|
|
176
|
+
/**
|
|
177
|
+
* 取消选中当前节点
|
|
178
|
+
*/
|
|
179
|
+
deselectNode(): void;
|
|
180
|
+
/**
|
|
181
|
+
* 获取当前选中的节点信息
|
|
182
|
+
*/
|
|
183
|
+
getSelectedNode(): ConnectorNode | null;
|
|
184
|
+
/**
|
|
185
|
+
* 删除指定节点(移除其连接与触点,触发 onNodeDelete 回调)
|
|
186
|
+
* @param id - 节点 ID
|
|
187
|
+
*/
|
|
188
|
+
deleteNode(id: string): void;
|
|
116
189
|
/**
|
|
117
190
|
* 创建连接(编程式)
|
|
118
191
|
*/
|
|
@@ -135,6 +208,41 @@ declare class Connector {
|
|
|
135
208
|
* 更新所有连接线位置
|
|
136
209
|
*/
|
|
137
210
|
updateAllConnections(): void;
|
|
211
|
+
/**
|
|
212
|
+
* 导出当前拓扑快照
|
|
213
|
+
* 返回所有节点(含坐标)、连接关系和视图状态的标准 JSON,可直接持久化
|
|
214
|
+
*/
|
|
215
|
+
export(): ExportData;
|
|
216
|
+
/**
|
|
217
|
+
* 从拓扑快照恢复节点与连接
|
|
218
|
+
*
|
|
219
|
+
* **两种使用模式:**
|
|
220
|
+
*
|
|
221
|
+
* 1. **框架模式(Vue / React 等)—— 不传 nodeFactory**
|
|
222
|
+
* 调用方负责将节点渲染到 DOM(使用框架响应式),等待渲染完成后再调用
|
|
223
|
+
* `import(data)`,库会在 contentWrapper 内按 `id` 属性查找已存在的元素,
|
|
224
|
+
* 完成注册并还原连接。
|
|
225
|
+
* ```js
|
|
226
|
+
* nodes.value = data.nodes; // 触发框架渲染
|
|
227
|
+
* await nextTick(); // 等待 DOM 就绪
|
|
228
|
+
* await connector.import(data); // 注册 + 连线
|
|
229
|
+
* ```
|
|
230
|
+
*
|
|
231
|
+
* 2. **原生 JS 模式 —— 传入 nodeFactory**
|
|
232
|
+
* 库调用工厂函数创建 DOM 元素,自动定位、挂载、注册,最后还原连接。
|
|
233
|
+
* ```js
|
|
234
|
+
* await connector.import(data, (nodeData) => {
|
|
235
|
+
* const el = document.createElement('div');
|
|
236
|
+
* el.className = 'node';
|
|
237
|
+
* el.textContent = nodeData.info?.name ?? nodeData.id;
|
|
238
|
+
* return el;
|
|
239
|
+
* });
|
|
240
|
+
* ```
|
|
241
|
+
*
|
|
242
|
+
* @param data 由 `export()` 返回的拓扑数据
|
|
243
|
+
* @param nodeFactory 可选,原生 JS 场景下的节点元素工厂
|
|
244
|
+
*/
|
|
245
|
+
import(data: ExportData, nodeFactory?: NodeFactory): Promise<void>;
|
|
138
246
|
/**
|
|
139
247
|
* 设置缩放比例(以画布中心为基准)
|
|
140
248
|
*/
|
|
@@ -175,4 +283,4 @@ declare class Connector {
|
|
|
175
283
|
* @description 导出 Connector 类及相关类型定义
|
|
176
284
|
*/
|
|
177
285
|
|
|
178
|
-
export { type Connection, type ConnectionInfo, Connector, type ConnectorConfig, type ConnectorNode, type ConnectorOptions, type Dot, type DotPosition, type Point, type RegisterNodeOptions, type SilentOptions, type ViewState, Connector as default };
|
|
286
|
+
export { type Connection, type ConnectionInfo, Connector, type ConnectorConfig, type ConnectorNode, type ConnectorOptions, type Dot, type DotPosition, type ExportConnectionData, type ExportData, type ExportNodeData, type NodeDeleteInfo, type NodeFactory, type NodeMoveInfo, type NodeSelectInfo, type Point, type RegisterNodeOptions, type SilentOptions, type ViewState, Connector as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -28,6 +28,10 @@ interface ConnectorNode {
|
|
|
28
28
|
dots: Partial<Record<DotPosition, Dot>>;
|
|
29
29
|
dotPositions: DotPosition[];
|
|
30
30
|
connections: Connection[];
|
|
31
|
+
/** 节点相对于 contentWrapper 的 x 坐标(由库内部维护) */
|
|
32
|
+
x: number;
|
|
33
|
+
/** 节点相对于 contentWrapper 的 y 坐标(由库内部维护) */
|
|
34
|
+
y: number;
|
|
31
35
|
}
|
|
32
36
|
/** 连接对象 */
|
|
33
37
|
interface Connection {
|
|
@@ -38,7 +42,7 @@ interface Connection {
|
|
|
38
42
|
toDot: Dot;
|
|
39
43
|
line: SVGPathElement;
|
|
40
44
|
hoverPath?: SVGPathElement;
|
|
41
|
-
deleteButton:
|
|
45
|
+
deleteButton: SVGGElement;
|
|
42
46
|
}
|
|
43
47
|
/** 连接信息(回调 / API 返回用) */
|
|
44
48
|
interface ConnectionInfo {
|
|
@@ -66,6 +70,24 @@ interface ConnectorConfig {
|
|
|
66
70
|
minZoom: number;
|
|
67
71
|
maxZoom: number;
|
|
68
72
|
zoomStep: number;
|
|
73
|
+
/** 节点选中时的高亮颜色 */
|
|
74
|
+
selectedBorderColor: string;
|
|
75
|
+
}
|
|
76
|
+
/** 节点移动信息(拖拽回调用) */
|
|
77
|
+
interface NodeMoveInfo {
|
|
78
|
+
id: string;
|
|
79
|
+
x: number;
|
|
80
|
+
y: number;
|
|
81
|
+
}
|
|
82
|
+
/** 节点选中信息 */
|
|
83
|
+
interface NodeSelectInfo {
|
|
84
|
+
id: string;
|
|
85
|
+
info?: Record<string, unknown>;
|
|
86
|
+
}
|
|
87
|
+
/** 节点删除信息 */
|
|
88
|
+
interface NodeDeleteInfo {
|
|
89
|
+
id: string;
|
|
90
|
+
info?: Record<string, unknown>;
|
|
69
91
|
}
|
|
70
92
|
/** 构造函数选项 */
|
|
71
93
|
interface ConnectorOptions extends Partial<ConnectorConfig> {
|
|
@@ -73,17 +95,50 @@ interface ConnectorOptions extends Partial<ConnectorConfig> {
|
|
|
73
95
|
onConnect?: (info: ConnectionInfo) => void;
|
|
74
96
|
onDisconnect?: (info: ConnectionInfo) => void;
|
|
75
97
|
onViewChange?: (state: ViewState) => void;
|
|
98
|
+
onNodeMove?: (info: NodeMoveInfo) => void;
|
|
99
|
+
/** 节点被选中/取消选中时触发,取消选中时 info 为 null */
|
|
100
|
+
onNodeSelect?: (info: NodeSelectInfo | null) => void;
|
|
101
|
+
/** 节点被删除时触发 */
|
|
102
|
+
onNodeDelete?: (info: NodeDeleteInfo) => void;
|
|
76
103
|
config?: Partial<ConnectorConfig>;
|
|
77
104
|
}
|
|
78
105
|
/** 节点注册选项 */
|
|
79
106
|
interface RegisterNodeOptions {
|
|
80
107
|
info?: Record<string, unknown>;
|
|
81
|
-
dotPositions?: 'both' | DotPosition[];
|
|
108
|
+
dotPositions?: 'both' | DotPosition | DotPosition[];
|
|
82
109
|
}
|
|
83
110
|
/** 静默操作选项 */
|
|
84
111
|
interface SilentOptions {
|
|
85
112
|
silent?: boolean;
|
|
86
113
|
}
|
|
114
|
+
/** 导出的单个节点数据 */
|
|
115
|
+
interface ExportNodeData {
|
|
116
|
+
id: string;
|
|
117
|
+
x: number;
|
|
118
|
+
y: number;
|
|
119
|
+
info?: Record<string, unknown>;
|
|
120
|
+
dotPositions: DotPosition[];
|
|
121
|
+
}
|
|
122
|
+
/** 导出的单条连接数据 */
|
|
123
|
+
interface ExportConnectionData {
|
|
124
|
+
from: string;
|
|
125
|
+
to: string;
|
|
126
|
+
fromDot: DotPosition;
|
|
127
|
+
toDot: DotPosition;
|
|
128
|
+
}
|
|
129
|
+
/** export() 返回的完整拓扑数据 */
|
|
130
|
+
interface ExportData {
|
|
131
|
+
nodes: ExportNodeData[];
|
|
132
|
+
connections: ExportConnectionData[];
|
|
133
|
+
/** 视图状态(缩放、平移),可选 */
|
|
134
|
+
viewState?: ViewState;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* 节点工厂函数(用于 import() 的原生 JS 场景)
|
|
138
|
+
* 接收节点数据,返回对应的 HTMLElement(或 Promise)
|
|
139
|
+
* 库会负责定位和挂载,工厂只需构建元素结构
|
|
140
|
+
*/
|
|
141
|
+
type NodeFactory = (nodeData: ExportNodeData) => HTMLElement | Promise<HTMLElement>;
|
|
87
142
|
|
|
88
143
|
/**
|
|
89
144
|
* @fileoverview 连线器主类
|
|
@@ -113,6 +168,24 @@ declare class Connector {
|
|
|
113
168
|
* 更新节点位置(当节点移动时调用)
|
|
114
169
|
*/
|
|
115
170
|
updateNodePosition(nodeId: string): void;
|
|
171
|
+
/**
|
|
172
|
+
* 选中指定节点
|
|
173
|
+
* @param id - 节点 ID
|
|
174
|
+
*/
|
|
175
|
+
selectNode(id: string): void;
|
|
176
|
+
/**
|
|
177
|
+
* 取消选中当前节点
|
|
178
|
+
*/
|
|
179
|
+
deselectNode(): void;
|
|
180
|
+
/**
|
|
181
|
+
* 获取当前选中的节点信息
|
|
182
|
+
*/
|
|
183
|
+
getSelectedNode(): ConnectorNode | null;
|
|
184
|
+
/**
|
|
185
|
+
* 删除指定节点(移除其连接与触点,触发 onNodeDelete 回调)
|
|
186
|
+
* @param id - 节点 ID
|
|
187
|
+
*/
|
|
188
|
+
deleteNode(id: string): void;
|
|
116
189
|
/**
|
|
117
190
|
* 创建连接(编程式)
|
|
118
191
|
*/
|
|
@@ -135,6 +208,41 @@ declare class Connector {
|
|
|
135
208
|
* 更新所有连接线位置
|
|
136
209
|
*/
|
|
137
210
|
updateAllConnections(): void;
|
|
211
|
+
/**
|
|
212
|
+
* 导出当前拓扑快照
|
|
213
|
+
* 返回所有节点(含坐标)、连接关系和视图状态的标准 JSON,可直接持久化
|
|
214
|
+
*/
|
|
215
|
+
export(): ExportData;
|
|
216
|
+
/**
|
|
217
|
+
* 从拓扑快照恢复节点与连接
|
|
218
|
+
*
|
|
219
|
+
* **两种使用模式:**
|
|
220
|
+
*
|
|
221
|
+
* 1. **框架模式(Vue / React 等)—— 不传 nodeFactory**
|
|
222
|
+
* 调用方负责将节点渲染到 DOM(使用框架响应式),等待渲染完成后再调用
|
|
223
|
+
* `import(data)`,库会在 contentWrapper 内按 `id` 属性查找已存在的元素,
|
|
224
|
+
* 完成注册并还原连接。
|
|
225
|
+
* ```js
|
|
226
|
+
* nodes.value = data.nodes; // 触发框架渲染
|
|
227
|
+
* await nextTick(); // 等待 DOM 就绪
|
|
228
|
+
* await connector.import(data); // 注册 + 连线
|
|
229
|
+
* ```
|
|
230
|
+
*
|
|
231
|
+
* 2. **原生 JS 模式 —— 传入 nodeFactory**
|
|
232
|
+
* 库调用工厂函数创建 DOM 元素,自动定位、挂载、注册,最后还原连接。
|
|
233
|
+
* ```js
|
|
234
|
+
* await connector.import(data, (nodeData) => {
|
|
235
|
+
* const el = document.createElement('div');
|
|
236
|
+
* el.className = 'node';
|
|
237
|
+
* el.textContent = nodeData.info?.name ?? nodeData.id;
|
|
238
|
+
* return el;
|
|
239
|
+
* });
|
|
240
|
+
* ```
|
|
241
|
+
*
|
|
242
|
+
* @param data 由 `export()` 返回的拓扑数据
|
|
243
|
+
* @param nodeFactory 可选,原生 JS 场景下的节点元素工厂
|
|
244
|
+
*/
|
|
245
|
+
import(data: ExportData, nodeFactory?: NodeFactory): Promise<void>;
|
|
138
246
|
/**
|
|
139
247
|
* 设置缩放比例(以画布中心为基准)
|
|
140
248
|
*/
|
|
@@ -175,4 +283,4 @@ declare class Connector {
|
|
|
175
283
|
* @description 导出 Connector 类及相关类型定义
|
|
176
284
|
*/
|
|
177
285
|
|
|
178
|
-
export { type Connection, type ConnectionInfo, Connector, type ConnectorConfig, type ConnectorNode, type ConnectorOptions, type Dot, type DotPosition, type Point, type RegisterNodeOptions, type SilentOptions, type ViewState, Connector as default };
|
|
286
|
+
export { type Connection, type ConnectionInfo, Connector, type ConnectorConfig, type ConnectorNode, type ConnectorOptions, type Dot, type DotPosition, type ExportConnectionData, type ExportData, type ExportNodeData, type NodeDeleteInfo, type NodeFactory, type NodeMoveInfo, type NodeSelectInfo, type Point, type RegisterNodeOptions, type SilentOptions, type ViewState, Connector as default };
|