power-link 1.0.2 → 1.0.3
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 +803 -803
- package/package.json +28 -28
package/README.md
CHANGED
|
@@ -1,803 +1,803 @@
|
|
|
1
|
-
# power-link
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/power-link)
|
|
4
|
-
[](https://github.com/Tem-man/power-link/blob/main)
|
|
5
|
-
|
|
6
|
-
A pure TypeScript visual node connector for creating draggable connections between nodes. Framework-agnostic and easy to use.
|
|
7
|
-
|
|
8
|
-

|
|
9
|
-
|
|
10
|
-
### 📹 Demo Video
|
|
11
|
-
|
|
12
|
-
<video width="100%" controls>
|
|
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)
|
|
18
|
-
|
|
19
|
-
## ✨ Features
|
|
20
|
-
|
|
21
|
-
- 🎯 **Visual Node Connections** - Create beautiful bezier curve connections between nodes
|
|
22
|
-
- 🖱️ **Drag & Drop** - Intuitive drag-and-drop connection creation
|
|
23
|
-
- 🔄 **Node Dragging** - Move nodes around with automatic connection updates
|
|
24
|
-
- 🧲 **Smart Snapping** - Automatic connection point detection and snapping
|
|
25
|
-
- 🎨 **Customizable** - Fully configurable colors, sizes, and behaviors
|
|
26
|
-
- 🚫 **Delete Connections** - Hover over connections to show delete button
|
|
27
|
-
- 📦 **Zero Dependencies** - Pure JavaScript, no framework required
|
|
28
|
-
- 🎭 **Multiple Connection Points** - Support for left and right connection dots
|
|
29
|
-
- 🔌 **Event Callbacks** - Listen to connection and disconnection events
|
|
30
|
-
|
|
31
|
-
## 📦 Installation
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
npm install power-link
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
Or using yarn:
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
yarn add power-link
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
Or using pnpm:
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
pnpm add power-link
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
## 🚀 Quick Start
|
|
50
|
-
|
|
51
|
-
### Basic Usage
|
|
52
|
-
|
|
53
|
-
```javascript
|
|
54
|
-
import Connector from "power-link";
|
|
55
|
-
|
|
56
|
-
// 1. Get container element
|
|
57
|
-
const container = document.getElementById("connector-container");
|
|
58
|
-
|
|
59
|
-
// 2. Create connector instance
|
|
60
|
-
const connector = new Connector({
|
|
61
|
-
container: container,
|
|
62
|
-
|
|
63
|
-
// Optional configuration
|
|
64
|
-
lineColor: "#155BD4",
|
|
65
|
-
lineWidth: 2,
|
|
66
|
-
dotSize: 12,
|
|
67
|
-
dotColor: "#155BD4",
|
|
68
|
-
|
|
69
|
-
// Event callbacks
|
|
70
|
-
onConnect: (connection) => {
|
|
71
|
-
console.log("Connection created:", connection);
|
|
72
|
-
// connection: { from: 'node1', to: 'node2', fromDot: 'right', toDot: 'left' }
|
|
73
|
-
},
|
|
74
|
-
|
|
75
|
-
onDisconnect: (connection) => {
|
|
76
|
-
console.log("Connection removed:", connection);
|
|
77
|
-
},
|
|
78
|
-
|
|
79
|
-
onViewChange: (viewState) => {
|
|
80
|
-
console.log("View changed:", viewState);
|
|
81
|
-
// viewState: { scale: 1, translateX: 0, translateY: 0 }
|
|
82
|
-
// Save view state to restore later
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
// 3. Register nodes
|
|
87
|
-
const node1 = document.getElementById("node1");
|
|
88
|
-
const node2 = document.getElementById("node2");
|
|
89
|
-
|
|
90
|
-
connector.registerNode("node1", node1, {
|
|
91
|
-
dotPositions: ["right"] // Only right connection dot
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
connector.registerNode("node2", node2, {
|
|
95
|
-
dotPositions: ["left", "right"] // Both left and right dots
|
|
96
|
-
});
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
### HTML Structure
|
|
100
|
-
|
|
101
|
-
```html
|
|
102
|
-
<div
|
|
103
|
-
id="connector-container"
|
|
104
|
-
style="position: relative; height: 600px;"
|
|
105
|
-
>
|
|
106
|
-
<div
|
|
107
|
-
id="node1"
|
|
108
|
-
style="position: absolute; left: 100px; top: 100px;"
|
|
109
|
-
>
|
|
110
|
-
Node 1
|
|
111
|
-
</div>
|
|
112
|
-
<div
|
|
113
|
-
id="node2"
|
|
114
|
-
style="position: absolute; left: 400px; top: 100px;"
|
|
115
|
-
>
|
|
116
|
-
Node 2
|
|
117
|
-
</div>
|
|
118
|
-
</div>
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
## 📖 API Documentation
|
|
122
|
-
|
|
123
|
-
### Constructor Options
|
|
124
|
-
|
|
125
|
-
| Option | Type | Default | Description |
|
|
126
|
-
| ------------------ | ----------- | ------------ | ----------------------------------------------- |
|
|
127
|
-
| `container` | HTMLElement | **Required** | Container element for the connector |
|
|
128
|
-
| `lineColor` | String | `'#155BD4'` | Color of connection lines |
|
|
129
|
-
| `lineWidth` | Number | `2` | Width of connection lines |
|
|
130
|
-
| `dotSize` | Number | `12` | Size of connection dots |
|
|
131
|
-
| `dotColor` | String | `'#155BD4'` | Color of connection dots |
|
|
132
|
-
| `dotHoverScale` | Number | `1.8` | Scale factor when hovering over connection dots |
|
|
133
|
-
| `deleteButtonSize` | Number | `20` | Size of delete button |
|
|
134
|
-
| `enableNodeDrag` | Boolean | `true` | Enable node dragging |
|
|
135
|
-
| `enableSnap` | Boolean | `true` | Enable connection snapping |
|
|
136
|
-
| `snapDistance` | Number | `20` | Snap distance in pixels |
|
|
137
|
-
| `enableZoom` | Boolean | `true` | Enable zoom functionality |
|
|
138
|
-
| `enablePan` | Boolean | `true` | Enable pan functionality |
|
|
139
|
-
| `minZoom` | Number | `0.1` | Minimum zoom level (10%) |
|
|
140
|
-
| `maxZoom` | Number | `4` | Maximum zoom level (400%) |
|
|
141
|
-
| `zoomStep` | Number | `0.1` | Zoom step size (10%) |
|
|
142
|
-
| `onConnect` | Function | `() => {}` | Callback when connection is created |
|
|
143
|
-
| `onDisconnect` | Function | `() => {}` | Callback when connection is removed |
|
|
144
|
-
| `onViewChange` | Function | `() => {}` | Callback when view state changes (zoom/pan) |
|
|
145
|
-
|
|
146
|
-
### Methods
|
|
147
|
-
|
|
148
|
-
#### `registerNode(id, element, options)`
|
|
149
|
-
|
|
150
|
-
Register a node for connection.
|
|
151
|
-
|
|
152
|
-
**Parameters:**
|
|
153
|
-
|
|
154
|
-
- `id` (String): Unique identifier for the node
|
|
155
|
-
- `element` (HTMLElement): DOM element of the node
|
|
156
|
-
- `options` (Object): Node configuration
|
|
157
|
-
- `dotPositions` (String | Array): Connection dot positions
|
|
158
|
-
- `'both'`: Both left and right dots
|
|
159
|
-
- `['left', 'right']`: Array format, both sides
|
|
160
|
-
- `['left']`: Only left dot
|
|
161
|
-
- `['right']`: Only right dot
|
|
162
|
-
- `info` (Object): Node extraneous information
|
|
163
|
-
|
|
164
|
-
**Returns:** Node object
|
|
165
|
-
|
|
166
|
-
**Example:**
|
|
167
|
-
|
|
168
|
-
```javascript
|
|
169
|
-
connector.registerNode("myNode", element, {
|
|
170
|
-
dotPositions: ["right"],
|
|
171
|
-
info: {
|
|
172
|
-
id: "123",
|
|
173
|
-
name: "apple",
|
|
174
|
-
desc: "this is a red apple"
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
#### `createConnection(fromNodeId, toNodeId, fromDot, toDot, options)`
|
|
180
|
-
|
|
181
|
-
Programmatically create a connection between nodes.
|
|
182
|
-
|
|
183
|
-
**Parameters:**
|
|
184
|
-
|
|
185
|
-
- `fromNodeId` (String): Source node ID
|
|
186
|
-
- `toNodeId` (String): Target node ID
|
|
187
|
-
- `fromDot` (String): Source connection dot position - `'left'` or `'right'` (optional)
|
|
188
|
-
- `toDot` (String): Target connection dot position - `'left'` or `'right'` (optional)
|
|
189
|
-
- `options` (Object): Configuration options (optional)
|
|
190
|
-
- `silent` (boolean): Whether to create silently (without triggering callbacks)
|
|
191
|
-
|
|
192
|
-
**Returns:** Connection object or undefined
|
|
193
|
-
|
|
194
|
-
**Example:**
|
|
195
|
-
|
|
196
|
-
```javascript
|
|
197
|
-
// Create connection with callbacks
|
|
198
|
-
connector.createConnection("node1", "node2");
|
|
199
|
-
|
|
200
|
-
// Create connection with specific dot positions
|
|
201
|
-
connector.createConnection("node1", "node2", "right", "left");
|
|
202
|
-
|
|
203
|
-
// Create connection silently without triggering callbacks
|
|
204
|
-
connector.createConnection("node1", "node2", "right", "left", { silent: true });
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
#### `disconnect(connectionId,options)`
|
|
208
|
-
|
|
209
|
-
Remove a connection.
|
|
210
|
-
|
|
211
|
-
**Parameters:**
|
|
212
|
-
|
|
213
|
-
- `connectionId` (String): Connection ID (optional, if not provided, removes all connections)
|
|
214
|
-
- `options` (Object): Configuration options (optional)
|
|
215
|
-
- `silent` (boolean): Whether to disconnect silently (without triggering callbacks)
|
|
216
|
-
|
|
217
|
-
**Example:**
|
|
218
|
-
|
|
219
|
-
```javascript
|
|
220
|
-
connector.disconnect(); // Remove all connections
|
|
221
|
-
connector.disconnect("connection-id"); // Remove specific connection
|
|
222
|
-
connector.disconnect("connection-id", { silent: true }); // Remove connection silently without triggering callbacks
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
#### `getConnections()`
|
|
226
|
-
|
|
227
|
-
Get all connections.
|
|
228
|
-
|
|
229
|
-
**Returns:** Array of connection information
|
|
230
|
-
|
|
231
|
-
**Example:**
|
|
232
|
-
|
|
233
|
-
```javascript
|
|
234
|
-
const connections = connector.getConnections();
|
|
235
|
-
// [{ id: '...', from: 'node1', to: 'node2', fromDot: 'right', toDot: 'left' }]
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
#### `getNodeConnections(nodeId)`
|
|
239
|
-
|
|
240
|
-
Get all connections for a specific node.
|
|
241
|
-
|
|
242
|
-
**Parameters:**
|
|
243
|
-
|
|
244
|
-
- `nodeId` (String): Node ID
|
|
245
|
-
|
|
246
|
-
**Returns:** Array of connection information
|
|
247
|
-
|
|
248
|
-
#### `updateNodePosition(nodeId)`
|
|
249
|
-
|
|
250
|
-
Update node position (called when node is moved).
|
|
251
|
-
|
|
252
|
-
**Parameters:**
|
|
253
|
-
|
|
254
|
-
- `nodeId` (String): Node ID
|
|
255
|
-
|
|
256
|
-
#### `destroy(options)`
|
|
257
|
-
|
|
258
|
-
Destroy the connector and clean up all resources.
|
|
259
|
-
|
|
260
|
-
**Parameters:**
|
|
261
|
-
|
|
262
|
-
- `options` (Object): Configuration options (optional)
|
|
263
|
-
- `silent` (boolean): Whether to destroy silently (without triggering callbacks)
|
|
264
|
-
|
|
265
|
-
**Example:**
|
|
266
|
-
|
|
267
|
-
```javascript
|
|
268
|
-
connector.destroy(); // Destroy silently by default (without triggering callbacks)
|
|
269
|
-
connector.destroy({ silent: false }); // Destroy non-silently (triggering callbacks)
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
#### `setViewState(state)`
|
|
273
|
-
|
|
274
|
-
Set the view state (for initialization or restoring view).
|
|
275
|
-
|
|
276
|
-
**Parameters:**
|
|
277
|
-
|
|
278
|
-
- `state` (Object): View state object
|
|
279
|
-
- `scale` (Number): View scale (optional)
|
|
280
|
-
- `translateX` (Number): X-axis translation (optional)
|
|
281
|
-
- `translateY` (Number): Y-axis translation (optional)
|
|
282
|
-
|
|
283
|
-
**Example:**
|
|
284
|
-
|
|
285
|
-
```javascript
|
|
286
|
-
connector.setViewState({
|
|
287
|
-
scale: 0.8,
|
|
288
|
-
translateX: 50,
|
|
289
|
-
translateY: 30
|
|
290
|
-
});
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
#### `getViewState()`
|
|
294
|
-
|
|
295
|
-
Get the current view state.
|
|
296
|
-
|
|
297
|
-
**Returns:** ViewState object with `scale`, `translateX`, and `translateY` properties
|
|
298
|
-
|
|
299
|
-
**Example:**
|
|
300
|
-
|
|
301
|
-
```javascript
|
|
302
|
-
const viewState = connector.getViewState();
|
|
303
|
-
console.log(viewState); // { scale: 1, translateX: 0, translateY: 0 }
|
|
304
|
-
```
|
|
305
|
-
|
|
306
|
-
#### `setZoom(scale)`
|
|
307
|
-
|
|
308
|
-
Set the zoom level (centered on canvas).
|
|
309
|
-
|
|
310
|
-
**Parameters:**
|
|
311
|
-
|
|
312
|
-
- `scale` (Number): Zoom scale (will be clamped to minZoom and maxZoom)
|
|
313
|
-
|
|
314
|
-
**Example:**
|
|
315
|
-
|
|
316
|
-
```javascript
|
|
317
|
-
connector.setZoom(1.5); // Zoom to 150%
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
#### `getZoom()`
|
|
321
|
-
|
|
322
|
-
Get the current zoom level.
|
|
323
|
-
|
|
324
|
-
**Returns:** Number - Current zoom scale
|
|
325
|
-
|
|
326
|
-
**Example:**
|
|
327
|
-
|
|
328
|
-
```javascript
|
|
329
|
-
const currentZoom = connector.getZoom();
|
|
330
|
-
console.log(currentZoom); // 1.0
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
#### `zoomIn()`
|
|
334
|
-
|
|
335
|
-
Zoom in by one step.
|
|
336
|
-
|
|
337
|
-
**Example:**
|
|
338
|
-
|
|
339
|
-
```javascript
|
|
340
|
-
connector.zoomIn(); // Increase zoom by zoomStep
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
#### `zoomOut()`
|
|
344
|
-
|
|
345
|
-
Zoom out by one step.
|
|
346
|
-
|
|
347
|
-
**Example:**
|
|
348
|
-
|
|
349
|
-
```javascript
|
|
350
|
-
connector.zoomOut(); // Decrease zoom by zoomStep
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
#### `resetView()`
|
|
354
|
-
|
|
355
|
-
Reset the view to default state (scale: 1, translateX: 0, translateY: 0).
|
|
356
|
-
|
|
357
|
-
**Example:**
|
|
358
|
-
|
|
359
|
-
```javascript
|
|
360
|
-
connector.resetView(); // Reset to default view
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
#### `updateAllConnections()`
|
|
364
|
-
|
|
365
|
-
Update all connection line positions (useful when container size changes or after manual node position updates).
|
|
366
|
-
|
|
367
|
-
**Example:**
|
|
368
|
-
|
|
369
|
-
```javascript
|
|
370
|
-
connector.updateAllConnections(); // Refresh all connection lines
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
## 🎨 Usage Examples
|
|
375
|
-
|
|
376
|
-
### Vue 3
|
|
377
|
-
|
|
378
|
-
```vue
|
|
379
|
-
<template>
|
|
380
|
-
<div
|
|
381
|
-
class="container"
|
|
382
|
-
ref="containerRef"
|
|
383
|
-
>
|
|
384
|
-
<div
|
|
385
|
-
class="node"
|
|
386
|
-
ref="node1Ref"
|
|
387
|
-
>
|
|
388
|
-
Node 1
|
|
389
|
-
</div>
|
|
390
|
-
<div
|
|
391
|
-
class="node"
|
|
392
|
-
ref="node2Ref"
|
|
393
|
-
>
|
|
394
|
-
Node 2
|
|
395
|
-
</div>
|
|
396
|
-
</div>
|
|
397
|
-
</template>
|
|
398
|
-
|
|
399
|
-
<script setup>
|
|
400
|
-
import { ref, onMounted, onBeforeUnmount } from "vue";
|
|
401
|
-
import Connector from "power-link";
|
|
402
|
-
|
|
403
|
-
const containerRef = ref(null);
|
|
404
|
-
const node1Ref = ref(null);
|
|
405
|
-
const node2Ref = ref(null);
|
|
406
|
-
|
|
407
|
-
let connector = null;
|
|
408
|
-
|
|
409
|
-
onMounted(() => {
|
|
410
|
-
connector = new Connector({
|
|
411
|
-
container: containerRef.value,
|
|
412
|
-
onConnect: (connection) => {
|
|
413
|
-
console.log("Connection created:", connection);
|
|
414
|
-
},
|
|
415
|
-
onDisconnect: (connection) => {
|
|
416
|
-
console.log("Connection removed:", connection);
|
|
417
|
-
}
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
connector.registerNode("node1", node1Ref.value, {
|
|
421
|
-
dotPositions: ["right"],
|
|
422
|
-
info: {
|
|
423
|
-
id: "123",
|
|
424
|
-
name: "apple",
|
|
425
|
-
desc: "this is a red apple"
|
|
426
|
-
}
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
connector.registerNode("node2", node2Ref.value, {
|
|
430
|
-
dotPositions: ["left"],
|
|
431
|
-
info: {
|
|
432
|
-
id: "456",
|
|
433
|
-
name: "pear",
|
|
434
|
-
desc: "this is a yellow pear"
|
|
435
|
-
}
|
|
436
|
-
});
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
onBeforeUnmount(() => {
|
|
440
|
-
if (connector) {
|
|
441
|
-
connector.destroy();
|
|
442
|
-
}
|
|
443
|
-
});
|
|
444
|
-
</script>
|
|
445
|
-
|
|
446
|
-
<style scoped>
|
|
447
|
-
.container {
|
|
448
|
-
position: relative;
|
|
449
|
-
height: 600px;
|
|
450
|
-
background: #f5f5f5;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
.node {
|
|
454
|
-
position: absolute;
|
|
455
|
-
padding: 20px;
|
|
456
|
-
background: white;
|
|
457
|
-
border-radius: 8px;
|
|
458
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
459
|
-
cursor: move;
|
|
460
|
-
}
|
|
461
|
-
</style>
|
|
462
|
-
````
|
|
463
|
-
|
|
464
|
-
### React
|
|
465
|
-
|
|
466
|
-
```jsx
|
|
467
|
-
import { useEffect, useRef } from "react";
|
|
468
|
-
import Connector from "power-link";
|
|
469
|
-
|
|
470
|
-
function App() {
|
|
471
|
-
const containerRef = useRef(null);
|
|
472
|
-
const node1Ref = useRef(null);
|
|
473
|
-
const node2Ref = useRef(null);
|
|
474
|
-
const connectorRef = useRef(null);
|
|
475
|
-
|
|
476
|
-
useEffect(() => {
|
|
477
|
-
if (!containerRef.current) return;
|
|
478
|
-
|
|
479
|
-
connectorRef.current = new Connector({
|
|
480
|
-
container: containerRef.current,
|
|
481
|
-
onConnect: (connection) => {
|
|
482
|
-
console.log("Connection created:", connection);
|
|
483
|
-
},
|
|
484
|
-
onDisconnect: (connection) => {
|
|
485
|
-
console.log("Connection removed:", connection);
|
|
486
|
-
}
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
connectorRef.current.registerNode("node1", node1Ref.current, {
|
|
490
|
-
dotPositions: ["right"],
|
|
491
|
-
info: {
|
|
492
|
-
id: "123",
|
|
493
|
-
name: "apple",
|
|
494
|
-
desc: "this is a red apple"
|
|
495
|
-
}
|
|
496
|
-
});
|
|
497
|
-
|
|
498
|
-
connectorRef.current.registerNode("node2", node2Ref.current, {
|
|
499
|
-
dotPositions: ["left"],
|
|
500
|
-
info: {
|
|
501
|
-
id: "456",
|
|
502
|
-
name: "pear",
|
|
503
|
-
desc: "this is a yellow pear"
|
|
504
|
-
}
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
return () => {
|
|
508
|
-
if (connectorRef.current) {
|
|
509
|
-
connectorRef.current.destroy();
|
|
510
|
-
}
|
|
511
|
-
};
|
|
512
|
-
}, []);
|
|
513
|
-
|
|
514
|
-
return (
|
|
515
|
-
<div
|
|
516
|
-
ref={containerRef}
|
|
517
|
-
style={{ position: "relative", height: "600px" }}
|
|
518
|
-
>
|
|
519
|
-
<div
|
|
520
|
-
ref={node1Ref}
|
|
521
|
-
style={{ position: "absolute", left: "100px", top: "100px" }}
|
|
522
|
-
>
|
|
523
|
-
Node 1
|
|
524
|
-
</div>
|
|
525
|
-
<div
|
|
526
|
-
ref={node2Ref}
|
|
527
|
-
style={{ position: "absolute", left: "400px", top: "100px" }}
|
|
528
|
-
>
|
|
529
|
-
Node 2
|
|
530
|
-
</div>
|
|
531
|
-
</div>
|
|
532
|
-
);
|
|
533
|
-
}
|
|
534
|
-
```
|
|
535
|
-
|
|
536
|
-
### Vanilla JavaScript
|
|
537
|
-
|
|
538
|
-
```html
|
|
539
|
-
<!DOCTYPE html>
|
|
540
|
-
<html>
|
|
541
|
-
<head>
|
|
542
|
-
<style>
|
|
543
|
-
#container {
|
|
544
|
-
position: relative;
|
|
545
|
-
height: 600px;
|
|
546
|
-
background: #f5f5f5;
|
|
547
|
-
}
|
|
548
|
-
.node {
|
|
549
|
-
position: absolute;
|
|
550
|
-
padding: 20px;
|
|
551
|
-
background: white;
|
|
552
|
-
border-radius: 8px;
|
|
553
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
554
|
-
cursor: move;
|
|
555
|
-
}
|
|
556
|
-
</style>
|
|
557
|
-
</head>
|
|
558
|
-
<body>
|
|
559
|
-
<div id="container">
|
|
560
|
-
<div
|
|
561
|
-
id="node1"
|
|
562
|
-
class="node"
|
|
563
|
-
style="left: 100px; top: 100px;"
|
|
564
|
-
>
|
|
565
|
-
Node 1
|
|
566
|
-
</div>
|
|
567
|
-
<div
|
|
568
|
-
id="node2"
|
|
569
|
-
class="node"
|
|
570
|
-
style="left: 400px; top: 100px;"
|
|
571
|
-
>
|
|
572
|
-
Node 2
|
|
573
|
-
</div>
|
|
574
|
-
</div>
|
|
575
|
-
|
|
576
|
-
<script type="module">
|
|
577
|
-
import Connector from "power-link";
|
|
578
|
-
|
|
579
|
-
const connector = new Connector({
|
|
580
|
-
container: document.getElementById("container"),
|
|
581
|
-
onConnect: (connection) => {
|
|
582
|
-
console.log("Connection created:", connection);
|
|
583
|
-
}
|
|
584
|
-
});
|
|
585
|
-
|
|
586
|
-
connector.registerNode("node1", document.getElementById("node1"), {
|
|
587
|
-
dotPositions: ["right"],
|
|
588
|
-
info: {
|
|
589
|
-
id: "123",
|
|
590
|
-
name: "apple",
|
|
591
|
-
desc: "this is a red apple"
|
|
592
|
-
}
|
|
593
|
-
});
|
|
594
|
-
|
|
595
|
-
connector.registerNode("node2", document.getElementById("node2"), {
|
|
596
|
-
dotPositions: ["left"],
|
|
597
|
-
info: {
|
|
598
|
-
id: "456",
|
|
599
|
-
name: "pear",
|
|
600
|
-
desc: "this is a yellow pear"
|
|
601
|
-
}
|
|
602
|
-
});
|
|
603
|
-
</script>
|
|
604
|
-
</body>
|
|
605
|
-
</html>
|
|
606
|
-
```
|
|
607
|
-
|
|
608
|
-
## 🎯 Advanced Features
|
|
609
|
-
|
|
610
|
-
### Multiple Connection Points
|
|
611
|
-
|
|
612
|
-
```javascript
|
|
613
|
-
// Node with both left and right connection points
|
|
614
|
-
connector.registerNode("centerNode", element, {
|
|
615
|
-
dotPositions: ["left", "right"]
|
|
616
|
-
});
|
|
617
|
-
|
|
618
|
-
// Node with only left connection point
|
|
619
|
-
connector.registerNode("endNode", element, {
|
|
620
|
-
dotPositions: ["left"],
|
|
621
|
-
info: {
|
|
622
|
-
id: "456",
|
|
623
|
-
name: "pear",
|
|
624
|
-
desc: "this is a yellow pear"
|
|
625
|
-
}
|
|
626
|
-
});
|
|
627
|
-
|
|
628
|
-
// Node with only right connection point
|
|
629
|
-
connector.registerNode("startNode", element, {
|
|
630
|
-
dotPositions: ["right"]
|
|
631
|
-
});
|
|
632
|
-
```
|
|
633
|
-
|
|
634
|
-
### Silent Operations (No Callbacks)
|
|
635
|
-
|
|
636
|
-
Sometimes you may want to perform operations without triggering callbacks, such as when initializing connections from saved data or bulk operations.
|
|
637
|
-
|
|
638
|
-
```javascript
|
|
639
|
-
// Silent connection creation (won't trigger onConnect callback)
|
|
640
|
-
connector.createConnection("node1", "node2", "right", "left", { silent: true });
|
|
641
|
-
|
|
642
|
-
// Silent disconnection (won't trigger onDisconnect callback)
|
|
643
|
-
connector.disconnect("connection-id", { silent: true });
|
|
644
|
-
|
|
645
|
-
// Silent destroy (won't trigger callbacks, default behavior)
|
|
646
|
-
connector.destroy(); // Default is silent
|
|
647
|
-
connector.destroy({ silent: false }); // Non-silent destroy (triggers callbacks)
|
|
648
|
-
|
|
649
|
-
// Example: Restore connections from saved data without triggering callbacks
|
|
650
|
-
const savedConnections = [
|
|
651
|
-
{ from: "node1", to: "node2", fromDot: "right", toDot: "left" },
|
|
652
|
-
{ from: "node2", to: "node3", fromDot: "right", toDot: "left" }
|
|
653
|
-
];
|
|
654
|
-
|
|
655
|
-
savedConnections.forEach((conn) => {
|
|
656
|
-
connector.createConnection(
|
|
657
|
-
conn.from,
|
|
658
|
-
conn.to,
|
|
659
|
-
conn.fromDot,
|
|
660
|
-
conn.toDot,
|
|
661
|
-
{ silent: true }
|
|
662
|
-
);
|
|
663
|
-
});
|
|
664
|
-
```
|
|
665
|
-
|
|
666
|
-
### Node Info and Connection Data
|
|
667
|
-
|
|
668
|
-
You can attach custom information to nodes using the `info` parameter, which will be available in connection callbacks.
|
|
669
|
-
|
|
670
|
-
```javascript
|
|
671
|
-
// Register node with custom info
|
|
672
|
-
const items = [
|
|
673
|
-
{ id: "node1", name: "Apple", desc: "This is a red apple", type: "fruit" },
|
|
674
|
-
{ id: "node2", name: "Pear", desc: "This is a yellow pear", type: "fruit" }
|
|
675
|
-
];
|
|
676
|
-
|
|
677
|
-
items.forEach((item) => {
|
|
678
|
-
const nodeElement = document.getElementById(item.id);
|
|
679
|
-
connector.registerNode(item.id, nodeElement, {
|
|
680
|
-
dotPositions: ["left"],
|
|
681
|
-
info: item // Attach custom info to the node
|
|
682
|
-
});
|
|
683
|
-
});
|
|
684
|
-
|
|
685
|
-
// Access node info in connection callbacks
|
|
686
|
-
const connector = new Connector({
|
|
687
|
-
container: container,
|
|
688
|
-
onConnect: async (connection) => {
|
|
689
|
-
console.log("Connection created:", connection);
|
|
690
|
-
console.log("From node info:", connection.fromInfo);
|
|
691
|
-
// { id: "node1", name: "Apple", desc: "This is a red apple", type: "fruit" }
|
|
692
|
-
console.log("To node info:", connection.toInfo);
|
|
693
|
-
// { id: "node2", name: "Pear", desc: "This is a yellow pear", type: "fruit" }
|
|
694
|
-
|
|
695
|
-
// You can use the info for saving to database, validation, etc.
|
|
696
|
-
await saveConnection({
|
|
697
|
-
from: connection.from,
|
|
698
|
-
to: connection.to,
|
|
699
|
-
fromInfo: connection.fromInfo,
|
|
700
|
-
toInfo: connection.toInfo
|
|
701
|
-
});
|
|
702
|
-
},
|
|
703
|
-
|
|
704
|
-
onDisconnect: (connection) => {
|
|
705
|
-
console.log("Connection removed:", connection);
|
|
706
|
-
console.log("From node info:", connection.fromInfo);
|
|
707
|
-
console.log("To node info:", connection.toInfo);
|
|
708
|
-
}
|
|
709
|
-
});
|
|
710
|
-
```
|
|
711
|
-
|
|
712
|
-
### Custom Styling
|
|
713
|
-
|
|
714
|
-
```javascript
|
|
715
|
-
const connector = new Connector({
|
|
716
|
-
container: container,
|
|
717
|
-
lineColor: "#FF6B6B", // Red connections
|
|
718
|
-
lineWidth: 3, // Thicker lines
|
|
719
|
-
dotSize: 16, // Larger dots
|
|
720
|
-
dotColor: "#4ECDC4", // Teal dots
|
|
721
|
-
deleteButtonSize: 24 // Larger delete button
|
|
722
|
-
});
|
|
723
|
-
```
|
|
724
|
-
|
|
725
|
-
### Event Handling
|
|
726
|
-
|
|
727
|
-
```javascript
|
|
728
|
-
const connector = new Connector({
|
|
729
|
-
container: container,
|
|
730
|
-
|
|
731
|
-
onConnect: (connection) => {
|
|
732
|
-
console.log("New connection:", connection);
|
|
733
|
-
// { from: 'node1', to: 'node2', fromDot: 'right', toDot: 'left' }
|
|
734
|
-
|
|
735
|
-
// Save to database, update state, etc.
|
|
736
|
-
saveConnection(connection);
|
|
737
|
-
},
|
|
738
|
-
|
|
739
|
-
onDisconnect: (connection) => {
|
|
740
|
-
console.log("Connection removed:", connection);
|
|
741
|
-
|
|
742
|
-
// Update database, state, etc.
|
|
743
|
-
removeConnection(connection);
|
|
744
|
-
},
|
|
745
|
-
|
|
746
|
-
onViewChange: (viewState) => {
|
|
747
|
-
console.log("View changed:", viewState);
|
|
748
|
-
// { scale: 1, translateX: 0, translateY: 0 }
|
|
749
|
-
|
|
750
|
-
// Save view state to restore later
|
|
751
|
-
saveViewState(viewState);
|
|
752
|
-
}
|
|
753
|
-
});
|
|
754
|
-
```
|
|
755
|
-
|
|
756
|
-
### View Management
|
|
757
|
-
|
|
758
|
-
```javascript
|
|
759
|
-
// Get current view state
|
|
760
|
-
const viewState = connector.getViewState();
|
|
761
|
-
console.log(viewState); // { scale: 1, translateX: 0, translateY: 0 }
|
|
762
|
-
|
|
763
|
-
// Set view state (restore saved view)
|
|
764
|
-
connector.setViewState({
|
|
765
|
-
scale: 0.8,
|
|
766
|
-
translateX: 100,
|
|
767
|
-
translateY: 50
|
|
768
|
-
});
|
|
769
|
-
|
|
770
|
-
// Zoom controls
|
|
771
|
-
connector.setZoom(1.5); // Set zoom to 150%
|
|
772
|
-
connector.zoomIn(); // Zoom in by one step
|
|
773
|
-
connector.zoomOut(); // Zoom out by one step
|
|
774
|
-
const currentZoom = connector.getZoom(); // Get current zoom
|
|
775
|
-
|
|
776
|
-
// Reset view
|
|
777
|
-
connector.resetView(); // Reset to default (scale: 1, translateX: 0, translateY: 0)
|
|
778
|
-
|
|
779
|
-
// Update all connections (useful after manual node position changes)
|
|
780
|
-
connector.updateAllConnections();
|
|
781
|
-
```
|
|
782
|
-
|
|
783
|
-
## 🔧 Browser Support
|
|
784
|
-
|
|
785
|
-
- Chrome (latest)
|
|
786
|
-
- Firefox (latest)
|
|
787
|
-
- Safari (latest)
|
|
788
|
-
- Edge (latest)
|
|
789
|
-
|
|
790
|
-
## 📝 License
|
|
791
|
-
|
|
792
|
-
MIT License
|
|
793
|
-
|
|
794
|
-
## 🌟 Show Your Support
|
|
795
|
-
|
|
796
|
-
Give a ⭐️ on [GitHub](https://github.com/Tem-man/power-link) if this project helped you!
|
|
797
|
-
|
|
798
|
-
## 🤝 Contributing
|
|
799
|
-
|
|
800
|
-
If you have any questions or need help, please open an issue on GitHub.
|
|
801
|
-
|
|
802
|
-
---
|
|
803
|
-
|
|
1
|
+
# power-link
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/power-link)
|
|
4
|
+
[](https://github.com/Tem-man/power-link/blob/main)
|
|
5
|
+
|
|
6
|
+
A pure TypeScript visual node connector for creating draggable connections between nodes. Framework-agnostic and easy to use.
|
|
7
|
+
|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
### 📹 Demo Video
|
|
11
|
+
|
|
12
|
+
<video width="100%" controls>
|
|
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)
|
|
18
|
+
|
|
19
|
+
## ✨ Features
|
|
20
|
+
|
|
21
|
+
- 🎯 **Visual Node Connections** - Create beautiful bezier curve connections between nodes
|
|
22
|
+
- 🖱️ **Drag & Drop** - Intuitive drag-and-drop connection creation
|
|
23
|
+
- 🔄 **Node Dragging** - Move nodes around with automatic connection updates
|
|
24
|
+
- 🧲 **Smart Snapping** - Automatic connection point detection and snapping
|
|
25
|
+
- 🎨 **Customizable** - Fully configurable colors, sizes, and behaviors
|
|
26
|
+
- 🚫 **Delete Connections** - Hover over connections to show delete button
|
|
27
|
+
- 📦 **Zero Dependencies** - Pure JavaScript, no framework required
|
|
28
|
+
- 🎭 **Multiple Connection Points** - Support for left and right connection dots
|
|
29
|
+
- 🔌 **Event Callbacks** - Listen to connection and disconnection events
|
|
30
|
+
|
|
31
|
+
## 📦 Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install power-link
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Or using yarn:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
yarn add power-link
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Or using pnpm:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pnpm add power-link
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## 🚀 Quick Start
|
|
50
|
+
|
|
51
|
+
### Basic Usage
|
|
52
|
+
|
|
53
|
+
```javascript
|
|
54
|
+
import Connector from "power-link";
|
|
55
|
+
|
|
56
|
+
// 1. Get container element
|
|
57
|
+
const container = document.getElementById("connector-container");
|
|
58
|
+
|
|
59
|
+
// 2. Create connector instance
|
|
60
|
+
const connector = new Connector({
|
|
61
|
+
container: container,
|
|
62
|
+
|
|
63
|
+
// Optional configuration
|
|
64
|
+
lineColor: "#155BD4",
|
|
65
|
+
lineWidth: 2,
|
|
66
|
+
dotSize: 12,
|
|
67
|
+
dotColor: "#155BD4",
|
|
68
|
+
|
|
69
|
+
// Event callbacks
|
|
70
|
+
onConnect: (connection) => {
|
|
71
|
+
console.log("Connection created:", connection);
|
|
72
|
+
// connection: { from: 'node1', to: 'node2', fromDot: 'right', toDot: 'left' }
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
onDisconnect: (connection) => {
|
|
76
|
+
console.log("Connection removed:", connection);
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
onViewChange: (viewState) => {
|
|
80
|
+
console.log("View changed:", viewState);
|
|
81
|
+
// viewState: { scale: 1, translateX: 0, translateY: 0 }
|
|
82
|
+
// Save view state to restore later
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// 3. Register nodes
|
|
87
|
+
const node1 = document.getElementById("node1");
|
|
88
|
+
const node2 = document.getElementById("node2");
|
|
89
|
+
|
|
90
|
+
connector.registerNode("node1", node1, {
|
|
91
|
+
dotPositions: ["right"] // Only right connection dot
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
connector.registerNode("node2", node2, {
|
|
95
|
+
dotPositions: ["left", "right"] // Both left and right dots
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### HTML Structure
|
|
100
|
+
|
|
101
|
+
```html
|
|
102
|
+
<div
|
|
103
|
+
id="connector-container"
|
|
104
|
+
style="position: relative; height: 600px;"
|
|
105
|
+
>
|
|
106
|
+
<div
|
|
107
|
+
id="node1"
|
|
108
|
+
style="position: absolute; left: 100px; top: 100px;"
|
|
109
|
+
>
|
|
110
|
+
Node 1
|
|
111
|
+
</div>
|
|
112
|
+
<div
|
|
113
|
+
id="node2"
|
|
114
|
+
style="position: absolute; left: 400px; top: 100px;"
|
|
115
|
+
>
|
|
116
|
+
Node 2
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## 📖 API Documentation
|
|
122
|
+
|
|
123
|
+
### Constructor Options
|
|
124
|
+
|
|
125
|
+
| Option | Type | Default | Description |
|
|
126
|
+
| ------------------ | ----------- | ------------ | ----------------------------------------------- |
|
|
127
|
+
| `container` | HTMLElement | **Required** | Container element for the connector |
|
|
128
|
+
| `lineColor` | String | `'#155BD4'` | Color of connection lines |
|
|
129
|
+
| `lineWidth` | Number | `2` | Width of connection lines |
|
|
130
|
+
| `dotSize` | Number | `12` | Size of connection dots |
|
|
131
|
+
| `dotColor` | String | `'#155BD4'` | Color of connection dots |
|
|
132
|
+
| `dotHoverScale` | Number | `1.8` | Scale factor when hovering over connection dots |
|
|
133
|
+
| `deleteButtonSize` | Number | `20` | Size of delete button |
|
|
134
|
+
| `enableNodeDrag` | Boolean | `true` | Enable node dragging |
|
|
135
|
+
| `enableSnap` | Boolean | `true` | Enable connection snapping |
|
|
136
|
+
| `snapDistance` | Number | `20` | Snap distance in pixels |
|
|
137
|
+
| `enableZoom` | Boolean | `true` | Enable zoom functionality |
|
|
138
|
+
| `enablePan` | Boolean | `true` | Enable pan functionality |
|
|
139
|
+
| `minZoom` | Number | `0.1` | Minimum zoom level (10%) |
|
|
140
|
+
| `maxZoom` | Number | `4` | Maximum zoom level (400%) |
|
|
141
|
+
| `zoomStep` | Number | `0.1` | Zoom step size (10%) |
|
|
142
|
+
| `onConnect` | Function | `() => {}` | Callback when connection is created |
|
|
143
|
+
| `onDisconnect` | Function | `() => {}` | Callback when connection is removed |
|
|
144
|
+
| `onViewChange` | Function | `() => {}` | Callback when view state changes (zoom/pan) |
|
|
145
|
+
|
|
146
|
+
### Methods
|
|
147
|
+
|
|
148
|
+
#### `registerNode(id, element, options)`
|
|
149
|
+
|
|
150
|
+
Register a node for connection.
|
|
151
|
+
|
|
152
|
+
**Parameters:**
|
|
153
|
+
|
|
154
|
+
- `id` (String): Unique identifier for the node
|
|
155
|
+
- `element` (HTMLElement): DOM element of the node
|
|
156
|
+
- `options` (Object): Node configuration
|
|
157
|
+
- `dotPositions` (String | Array): Connection dot positions
|
|
158
|
+
- `'both'`: Both left and right dots
|
|
159
|
+
- `['left', 'right']`: Array format, both sides
|
|
160
|
+
- `['left']`: Only left dot
|
|
161
|
+
- `['right']`: Only right dot
|
|
162
|
+
- `info` (Object): Node extraneous information
|
|
163
|
+
|
|
164
|
+
**Returns:** Node object
|
|
165
|
+
|
|
166
|
+
**Example:**
|
|
167
|
+
|
|
168
|
+
```javascript
|
|
169
|
+
connector.registerNode("myNode", element, {
|
|
170
|
+
dotPositions: ["right"],
|
|
171
|
+
info: {
|
|
172
|
+
id: "123",
|
|
173
|
+
name: "apple",
|
|
174
|
+
desc: "this is a red apple"
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
#### `createConnection(fromNodeId, toNodeId, fromDot, toDot, options)`
|
|
180
|
+
|
|
181
|
+
Programmatically create a connection between nodes.
|
|
182
|
+
|
|
183
|
+
**Parameters:**
|
|
184
|
+
|
|
185
|
+
- `fromNodeId` (String): Source node ID
|
|
186
|
+
- `toNodeId` (String): Target node ID
|
|
187
|
+
- `fromDot` (String): Source connection dot position - `'left'` or `'right'` (optional)
|
|
188
|
+
- `toDot` (String): Target connection dot position - `'left'` or `'right'` (optional)
|
|
189
|
+
- `options` (Object): Configuration options (optional)
|
|
190
|
+
- `silent` (boolean): Whether to create silently (without triggering callbacks)
|
|
191
|
+
|
|
192
|
+
**Returns:** Connection object or undefined
|
|
193
|
+
|
|
194
|
+
**Example:**
|
|
195
|
+
|
|
196
|
+
```javascript
|
|
197
|
+
// Create connection with callbacks
|
|
198
|
+
connector.createConnection("node1", "node2");
|
|
199
|
+
|
|
200
|
+
// Create connection with specific dot positions
|
|
201
|
+
connector.createConnection("node1", "node2", "right", "left");
|
|
202
|
+
|
|
203
|
+
// Create connection silently without triggering callbacks
|
|
204
|
+
connector.createConnection("node1", "node2", "right", "left", { silent: true });
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
#### `disconnect(connectionId,options)`
|
|
208
|
+
|
|
209
|
+
Remove a connection.
|
|
210
|
+
|
|
211
|
+
**Parameters:**
|
|
212
|
+
|
|
213
|
+
- `connectionId` (String): Connection ID (optional, if not provided, removes all connections)
|
|
214
|
+
- `options` (Object): Configuration options (optional)
|
|
215
|
+
- `silent` (boolean): Whether to disconnect silently (without triggering callbacks)
|
|
216
|
+
|
|
217
|
+
**Example:**
|
|
218
|
+
|
|
219
|
+
```javascript
|
|
220
|
+
connector.disconnect(); // Remove all connections
|
|
221
|
+
connector.disconnect("connection-id"); // Remove specific connection
|
|
222
|
+
connector.disconnect("connection-id", { silent: true }); // Remove connection silently without triggering callbacks
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
#### `getConnections()`
|
|
226
|
+
|
|
227
|
+
Get all connections.
|
|
228
|
+
|
|
229
|
+
**Returns:** Array of connection information
|
|
230
|
+
|
|
231
|
+
**Example:**
|
|
232
|
+
|
|
233
|
+
```javascript
|
|
234
|
+
const connections = connector.getConnections();
|
|
235
|
+
// [{ id: '...', from: 'node1', to: 'node2', fromDot: 'right', toDot: 'left' }]
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
#### `getNodeConnections(nodeId)`
|
|
239
|
+
|
|
240
|
+
Get all connections for a specific node.
|
|
241
|
+
|
|
242
|
+
**Parameters:**
|
|
243
|
+
|
|
244
|
+
- `nodeId` (String): Node ID
|
|
245
|
+
|
|
246
|
+
**Returns:** Array of connection information
|
|
247
|
+
|
|
248
|
+
#### `updateNodePosition(nodeId)`
|
|
249
|
+
|
|
250
|
+
Update node position (called when node is moved).
|
|
251
|
+
|
|
252
|
+
**Parameters:**
|
|
253
|
+
|
|
254
|
+
- `nodeId` (String): Node ID
|
|
255
|
+
|
|
256
|
+
#### `destroy(options)`
|
|
257
|
+
|
|
258
|
+
Destroy the connector and clean up all resources.
|
|
259
|
+
|
|
260
|
+
**Parameters:**
|
|
261
|
+
|
|
262
|
+
- `options` (Object): Configuration options (optional)
|
|
263
|
+
- `silent` (boolean): Whether to destroy silently (without triggering callbacks)
|
|
264
|
+
|
|
265
|
+
**Example:**
|
|
266
|
+
|
|
267
|
+
```javascript
|
|
268
|
+
connector.destroy(); // Destroy silently by default (without triggering callbacks)
|
|
269
|
+
connector.destroy({ silent: false }); // Destroy non-silently (triggering callbacks)
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
#### `setViewState(state)`
|
|
273
|
+
|
|
274
|
+
Set the view state (for initialization or restoring view).
|
|
275
|
+
|
|
276
|
+
**Parameters:**
|
|
277
|
+
|
|
278
|
+
- `state` (Object): View state object
|
|
279
|
+
- `scale` (Number): View scale (optional)
|
|
280
|
+
- `translateX` (Number): X-axis translation (optional)
|
|
281
|
+
- `translateY` (Number): Y-axis translation (optional)
|
|
282
|
+
|
|
283
|
+
**Example:**
|
|
284
|
+
|
|
285
|
+
```javascript
|
|
286
|
+
connector.setViewState({
|
|
287
|
+
scale: 0.8,
|
|
288
|
+
translateX: 50,
|
|
289
|
+
translateY: 30
|
|
290
|
+
});
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
#### `getViewState()`
|
|
294
|
+
|
|
295
|
+
Get the current view state.
|
|
296
|
+
|
|
297
|
+
**Returns:** ViewState object with `scale`, `translateX`, and `translateY` properties
|
|
298
|
+
|
|
299
|
+
**Example:**
|
|
300
|
+
|
|
301
|
+
```javascript
|
|
302
|
+
const viewState = connector.getViewState();
|
|
303
|
+
console.log(viewState); // { scale: 1, translateX: 0, translateY: 0 }
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
#### `setZoom(scale)`
|
|
307
|
+
|
|
308
|
+
Set the zoom level (centered on canvas).
|
|
309
|
+
|
|
310
|
+
**Parameters:**
|
|
311
|
+
|
|
312
|
+
- `scale` (Number): Zoom scale (will be clamped to minZoom and maxZoom)
|
|
313
|
+
|
|
314
|
+
**Example:**
|
|
315
|
+
|
|
316
|
+
```javascript
|
|
317
|
+
connector.setZoom(1.5); // Zoom to 150%
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
#### `getZoom()`
|
|
321
|
+
|
|
322
|
+
Get the current zoom level.
|
|
323
|
+
|
|
324
|
+
**Returns:** Number - Current zoom scale
|
|
325
|
+
|
|
326
|
+
**Example:**
|
|
327
|
+
|
|
328
|
+
```javascript
|
|
329
|
+
const currentZoom = connector.getZoom();
|
|
330
|
+
console.log(currentZoom); // 1.0
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
#### `zoomIn()`
|
|
334
|
+
|
|
335
|
+
Zoom in by one step.
|
|
336
|
+
|
|
337
|
+
**Example:**
|
|
338
|
+
|
|
339
|
+
```javascript
|
|
340
|
+
connector.zoomIn(); // Increase zoom by zoomStep
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
#### `zoomOut()`
|
|
344
|
+
|
|
345
|
+
Zoom out by one step.
|
|
346
|
+
|
|
347
|
+
**Example:**
|
|
348
|
+
|
|
349
|
+
```javascript
|
|
350
|
+
connector.zoomOut(); // Decrease zoom by zoomStep
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
#### `resetView()`
|
|
354
|
+
|
|
355
|
+
Reset the view to default state (scale: 1, translateX: 0, translateY: 0).
|
|
356
|
+
|
|
357
|
+
**Example:**
|
|
358
|
+
|
|
359
|
+
```javascript
|
|
360
|
+
connector.resetView(); // Reset to default view
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
#### `updateAllConnections()`
|
|
364
|
+
|
|
365
|
+
Update all connection line positions (useful when container size changes or after manual node position updates).
|
|
366
|
+
|
|
367
|
+
**Example:**
|
|
368
|
+
|
|
369
|
+
```javascript
|
|
370
|
+
connector.updateAllConnections(); // Refresh all connection lines
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
## 🎨 Usage Examples
|
|
375
|
+
|
|
376
|
+
### Vue 3
|
|
377
|
+
|
|
378
|
+
```vue
|
|
379
|
+
<template>
|
|
380
|
+
<div
|
|
381
|
+
class="container"
|
|
382
|
+
ref="containerRef"
|
|
383
|
+
>
|
|
384
|
+
<div
|
|
385
|
+
class="node"
|
|
386
|
+
ref="node1Ref"
|
|
387
|
+
>
|
|
388
|
+
Node 1
|
|
389
|
+
</div>
|
|
390
|
+
<div
|
|
391
|
+
class="node"
|
|
392
|
+
ref="node2Ref"
|
|
393
|
+
>
|
|
394
|
+
Node 2
|
|
395
|
+
</div>
|
|
396
|
+
</div>
|
|
397
|
+
</template>
|
|
398
|
+
|
|
399
|
+
<script setup>
|
|
400
|
+
import { ref, onMounted, onBeforeUnmount } from "vue";
|
|
401
|
+
import Connector from "power-link";
|
|
402
|
+
|
|
403
|
+
const containerRef = ref(null);
|
|
404
|
+
const node1Ref = ref(null);
|
|
405
|
+
const node2Ref = ref(null);
|
|
406
|
+
|
|
407
|
+
let connector = null;
|
|
408
|
+
|
|
409
|
+
onMounted(() => {
|
|
410
|
+
connector = new Connector({
|
|
411
|
+
container: containerRef.value,
|
|
412
|
+
onConnect: (connection) => {
|
|
413
|
+
console.log("Connection created:", connection);
|
|
414
|
+
},
|
|
415
|
+
onDisconnect: (connection) => {
|
|
416
|
+
console.log("Connection removed:", connection);
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
connector.registerNode("node1", node1Ref.value, {
|
|
421
|
+
dotPositions: ["right"],
|
|
422
|
+
info: {
|
|
423
|
+
id: "123",
|
|
424
|
+
name: "apple",
|
|
425
|
+
desc: "this is a red apple"
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
connector.registerNode("node2", node2Ref.value, {
|
|
430
|
+
dotPositions: ["left"],
|
|
431
|
+
info: {
|
|
432
|
+
id: "456",
|
|
433
|
+
name: "pear",
|
|
434
|
+
desc: "this is a yellow pear"
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
onBeforeUnmount(() => {
|
|
440
|
+
if (connector) {
|
|
441
|
+
connector.destroy();
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
</script>
|
|
445
|
+
|
|
446
|
+
<style scoped>
|
|
447
|
+
.container {
|
|
448
|
+
position: relative;
|
|
449
|
+
height: 600px;
|
|
450
|
+
background: #f5f5f5;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
.node {
|
|
454
|
+
position: absolute;
|
|
455
|
+
padding: 20px;
|
|
456
|
+
background: white;
|
|
457
|
+
border-radius: 8px;
|
|
458
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
459
|
+
cursor: move;
|
|
460
|
+
}
|
|
461
|
+
</style>
|
|
462
|
+
````
|
|
463
|
+
|
|
464
|
+
### React
|
|
465
|
+
|
|
466
|
+
```jsx
|
|
467
|
+
import { useEffect, useRef } from "react";
|
|
468
|
+
import Connector from "power-link";
|
|
469
|
+
|
|
470
|
+
function App() {
|
|
471
|
+
const containerRef = useRef(null);
|
|
472
|
+
const node1Ref = useRef(null);
|
|
473
|
+
const node2Ref = useRef(null);
|
|
474
|
+
const connectorRef = useRef(null);
|
|
475
|
+
|
|
476
|
+
useEffect(() => {
|
|
477
|
+
if (!containerRef.current) return;
|
|
478
|
+
|
|
479
|
+
connectorRef.current = new Connector({
|
|
480
|
+
container: containerRef.current,
|
|
481
|
+
onConnect: (connection) => {
|
|
482
|
+
console.log("Connection created:", connection);
|
|
483
|
+
},
|
|
484
|
+
onDisconnect: (connection) => {
|
|
485
|
+
console.log("Connection removed:", connection);
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
connectorRef.current.registerNode("node1", node1Ref.current, {
|
|
490
|
+
dotPositions: ["right"],
|
|
491
|
+
info: {
|
|
492
|
+
id: "123",
|
|
493
|
+
name: "apple",
|
|
494
|
+
desc: "this is a red apple"
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
connectorRef.current.registerNode("node2", node2Ref.current, {
|
|
499
|
+
dotPositions: ["left"],
|
|
500
|
+
info: {
|
|
501
|
+
id: "456",
|
|
502
|
+
name: "pear",
|
|
503
|
+
desc: "this is a yellow pear"
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
return () => {
|
|
508
|
+
if (connectorRef.current) {
|
|
509
|
+
connectorRef.current.destroy();
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
}, []);
|
|
513
|
+
|
|
514
|
+
return (
|
|
515
|
+
<div
|
|
516
|
+
ref={containerRef}
|
|
517
|
+
style={{ position: "relative", height: "600px" }}
|
|
518
|
+
>
|
|
519
|
+
<div
|
|
520
|
+
ref={node1Ref}
|
|
521
|
+
style={{ position: "absolute", left: "100px", top: "100px" }}
|
|
522
|
+
>
|
|
523
|
+
Node 1
|
|
524
|
+
</div>
|
|
525
|
+
<div
|
|
526
|
+
ref={node2Ref}
|
|
527
|
+
style={{ position: "absolute", left: "400px", top: "100px" }}
|
|
528
|
+
>
|
|
529
|
+
Node 2
|
|
530
|
+
</div>
|
|
531
|
+
</div>
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
### Vanilla JavaScript
|
|
537
|
+
|
|
538
|
+
```html
|
|
539
|
+
<!DOCTYPE html>
|
|
540
|
+
<html>
|
|
541
|
+
<head>
|
|
542
|
+
<style>
|
|
543
|
+
#container {
|
|
544
|
+
position: relative;
|
|
545
|
+
height: 600px;
|
|
546
|
+
background: #f5f5f5;
|
|
547
|
+
}
|
|
548
|
+
.node {
|
|
549
|
+
position: absolute;
|
|
550
|
+
padding: 20px;
|
|
551
|
+
background: white;
|
|
552
|
+
border-radius: 8px;
|
|
553
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
554
|
+
cursor: move;
|
|
555
|
+
}
|
|
556
|
+
</style>
|
|
557
|
+
</head>
|
|
558
|
+
<body>
|
|
559
|
+
<div id="container">
|
|
560
|
+
<div
|
|
561
|
+
id="node1"
|
|
562
|
+
class="node"
|
|
563
|
+
style="left: 100px; top: 100px;"
|
|
564
|
+
>
|
|
565
|
+
Node 1
|
|
566
|
+
</div>
|
|
567
|
+
<div
|
|
568
|
+
id="node2"
|
|
569
|
+
class="node"
|
|
570
|
+
style="left: 400px; top: 100px;"
|
|
571
|
+
>
|
|
572
|
+
Node 2
|
|
573
|
+
</div>
|
|
574
|
+
</div>
|
|
575
|
+
|
|
576
|
+
<script type="module">
|
|
577
|
+
import Connector from "power-link";
|
|
578
|
+
|
|
579
|
+
const connector = new Connector({
|
|
580
|
+
container: document.getElementById("container"),
|
|
581
|
+
onConnect: (connection) => {
|
|
582
|
+
console.log("Connection created:", connection);
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
connector.registerNode("node1", document.getElementById("node1"), {
|
|
587
|
+
dotPositions: ["right"],
|
|
588
|
+
info: {
|
|
589
|
+
id: "123",
|
|
590
|
+
name: "apple",
|
|
591
|
+
desc: "this is a red apple"
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
connector.registerNode("node2", document.getElementById("node2"), {
|
|
596
|
+
dotPositions: ["left"],
|
|
597
|
+
info: {
|
|
598
|
+
id: "456",
|
|
599
|
+
name: "pear",
|
|
600
|
+
desc: "this is a yellow pear"
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
</script>
|
|
604
|
+
</body>
|
|
605
|
+
</html>
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
## 🎯 Advanced Features
|
|
609
|
+
|
|
610
|
+
### Multiple Connection Points
|
|
611
|
+
|
|
612
|
+
```javascript
|
|
613
|
+
// Node with both left and right connection points
|
|
614
|
+
connector.registerNode("centerNode", element, {
|
|
615
|
+
dotPositions: ["left", "right"]
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
// Node with only left connection point
|
|
619
|
+
connector.registerNode("endNode", element, {
|
|
620
|
+
dotPositions: ["left"],
|
|
621
|
+
info: {
|
|
622
|
+
id: "456",
|
|
623
|
+
name: "pear",
|
|
624
|
+
desc: "this is a yellow pear"
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
// Node with only right connection point
|
|
629
|
+
connector.registerNode("startNode", element, {
|
|
630
|
+
dotPositions: ["right"]
|
|
631
|
+
});
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
### Silent Operations (No Callbacks)
|
|
635
|
+
|
|
636
|
+
Sometimes you may want to perform operations without triggering callbacks, such as when initializing connections from saved data or bulk operations.
|
|
637
|
+
|
|
638
|
+
```javascript
|
|
639
|
+
// Silent connection creation (won't trigger onConnect callback)
|
|
640
|
+
connector.createConnection("node1", "node2", "right", "left", { silent: true });
|
|
641
|
+
|
|
642
|
+
// Silent disconnection (won't trigger onDisconnect callback)
|
|
643
|
+
connector.disconnect("connection-id", { silent: true });
|
|
644
|
+
|
|
645
|
+
// Silent destroy (won't trigger callbacks, default behavior)
|
|
646
|
+
connector.destroy(); // Default is silent
|
|
647
|
+
connector.destroy({ silent: false }); // Non-silent destroy (triggers callbacks)
|
|
648
|
+
|
|
649
|
+
// Example: Restore connections from saved data without triggering callbacks
|
|
650
|
+
const savedConnections = [
|
|
651
|
+
{ from: "node1", to: "node2", fromDot: "right", toDot: "left" },
|
|
652
|
+
{ from: "node2", to: "node3", fromDot: "right", toDot: "left" }
|
|
653
|
+
];
|
|
654
|
+
|
|
655
|
+
savedConnections.forEach((conn) => {
|
|
656
|
+
connector.createConnection(
|
|
657
|
+
conn.from,
|
|
658
|
+
conn.to,
|
|
659
|
+
conn.fromDot,
|
|
660
|
+
conn.toDot,
|
|
661
|
+
{ silent: true }
|
|
662
|
+
);
|
|
663
|
+
});
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
### Node Info and Connection Data
|
|
667
|
+
|
|
668
|
+
You can attach custom information to nodes using the `info` parameter, which will be available in connection callbacks.
|
|
669
|
+
|
|
670
|
+
```javascript
|
|
671
|
+
// Register node with custom info
|
|
672
|
+
const items = [
|
|
673
|
+
{ id: "node1", name: "Apple", desc: "This is a red apple", type: "fruit" },
|
|
674
|
+
{ id: "node2", name: "Pear", desc: "This is a yellow pear", type: "fruit" }
|
|
675
|
+
];
|
|
676
|
+
|
|
677
|
+
items.forEach((item) => {
|
|
678
|
+
const nodeElement = document.getElementById(item.id);
|
|
679
|
+
connector.registerNode(item.id, nodeElement, {
|
|
680
|
+
dotPositions: ["left"],
|
|
681
|
+
info: item // Attach custom info to the node
|
|
682
|
+
});
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
// Access node info in connection callbacks
|
|
686
|
+
const connector = new Connector({
|
|
687
|
+
container: container,
|
|
688
|
+
onConnect: async (connection) => {
|
|
689
|
+
console.log("Connection created:", connection);
|
|
690
|
+
console.log("From node info:", connection.fromInfo);
|
|
691
|
+
// { id: "node1", name: "Apple", desc: "This is a red apple", type: "fruit" }
|
|
692
|
+
console.log("To node info:", connection.toInfo);
|
|
693
|
+
// { id: "node2", name: "Pear", desc: "This is a yellow pear", type: "fruit" }
|
|
694
|
+
|
|
695
|
+
// You can use the info for saving to database, validation, etc.
|
|
696
|
+
await saveConnection({
|
|
697
|
+
from: connection.from,
|
|
698
|
+
to: connection.to,
|
|
699
|
+
fromInfo: connection.fromInfo,
|
|
700
|
+
toInfo: connection.toInfo
|
|
701
|
+
});
|
|
702
|
+
},
|
|
703
|
+
|
|
704
|
+
onDisconnect: (connection) => {
|
|
705
|
+
console.log("Connection removed:", connection);
|
|
706
|
+
console.log("From node info:", connection.fromInfo);
|
|
707
|
+
console.log("To node info:", connection.toInfo);
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
### Custom Styling
|
|
713
|
+
|
|
714
|
+
```javascript
|
|
715
|
+
const connector = new Connector({
|
|
716
|
+
container: container,
|
|
717
|
+
lineColor: "#FF6B6B", // Red connections
|
|
718
|
+
lineWidth: 3, // Thicker lines
|
|
719
|
+
dotSize: 16, // Larger dots
|
|
720
|
+
dotColor: "#4ECDC4", // Teal dots
|
|
721
|
+
deleteButtonSize: 24 // Larger delete button
|
|
722
|
+
});
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
### Event Handling
|
|
726
|
+
|
|
727
|
+
```javascript
|
|
728
|
+
const connector = new Connector({
|
|
729
|
+
container: container,
|
|
730
|
+
|
|
731
|
+
onConnect: (connection) => {
|
|
732
|
+
console.log("New connection:", connection);
|
|
733
|
+
// { from: 'node1', to: 'node2', fromDot: 'right', toDot: 'left' }
|
|
734
|
+
|
|
735
|
+
// Save to database, update state, etc.
|
|
736
|
+
saveConnection(connection);
|
|
737
|
+
},
|
|
738
|
+
|
|
739
|
+
onDisconnect: (connection) => {
|
|
740
|
+
console.log("Connection removed:", connection);
|
|
741
|
+
|
|
742
|
+
// Update database, state, etc.
|
|
743
|
+
removeConnection(connection);
|
|
744
|
+
},
|
|
745
|
+
|
|
746
|
+
onViewChange: (viewState) => {
|
|
747
|
+
console.log("View changed:", viewState);
|
|
748
|
+
// { scale: 1, translateX: 0, translateY: 0 }
|
|
749
|
+
|
|
750
|
+
// Save view state to restore later
|
|
751
|
+
saveViewState(viewState);
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
### View Management
|
|
757
|
+
|
|
758
|
+
```javascript
|
|
759
|
+
// Get current view state
|
|
760
|
+
const viewState = connector.getViewState();
|
|
761
|
+
console.log(viewState); // { scale: 1, translateX: 0, translateY: 0 }
|
|
762
|
+
|
|
763
|
+
// Set view state (restore saved view)
|
|
764
|
+
connector.setViewState({
|
|
765
|
+
scale: 0.8,
|
|
766
|
+
translateX: 100,
|
|
767
|
+
translateY: 50
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
// Zoom controls
|
|
771
|
+
connector.setZoom(1.5); // Set zoom to 150%
|
|
772
|
+
connector.zoomIn(); // Zoom in by one step
|
|
773
|
+
connector.zoomOut(); // Zoom out by one step
|
|
774
|
+
const currentZoom = connector.getZoom(); // Get current zoom
|
|
775
|
+
|
|
776
|
+
// Reset view
|
|
777
|
+
connector.resetView(); // Reset to default (scale: 1, translateX: 0, translateY: 0)
|
|
778
|
+
|
|
779
|
+
// Update all connections (useful after manual node position changes)
|
|
780
|
+
connector.updateAllConnections();
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
## 🔧 Browser Support
|
|
784
|
+
|
|
785
|
+
- Chrome (latest)
|
|
786
|
+
- Firefox (latest)
|
|
787
|
+
- Safari (latest)
|
|
788
|
+
- Edge (latest)
|
|
789
|
+
|
|
790
|
+
## 📝 License
|
|
791
|
+
|
|
792
|
+
MIT License
|
|
793
|
+
|
|
794
|
+
## 🌟 Show Your Support
|
|
795
|
+
|
|
796
|
+
Give a ⭐️ on [GitHub](https://github.com/Tem-man/power-link) if this project helped you!
|
|
797
|
+
|
|
798
|
+
## 🤝 Contributing
|
|
799
|
+
|
|
800
|
+
If you have any questions or need help, please open an issue on GitHub.
|
|
801
|
+
|
|
802
|
+
---
|
|
803
|
+
|