power-link 0.1.2 → 1.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 CHANGED
@@ -1,680 +1,808 @@
1
- # power-link
2
-
3
- [![npm version](https://img.shields.io/npm/v/power-link.svg)](https://www.npmjs.com/package/power-link)
4
- [![license](https://img.shields.io/npm/l/power-link.svg)](https://github.com/Tem-man/power-link/blob/main)
5
-
6
- A pure JavaScript visual node connector for creating draggable connections between nodes. Framework-agnostic and easy to use.
7
-
8
- ![Node Link Connector Demo](https://github.com/Tem-man/power-link/blob/main/public/images/screen-shot.png)
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
-
80
- // 3. Register nodes
81
- const node1 = document.getElementById("node1");
82
- const node2 = document.getElementById("node2");
83
-
84
- connector.registerNode("node1", node1, {
85
- dotPositions: ["right"] // Only right connection dot
86
- });
87
-
88
- connector.registerNode("node2", node2, {
89
- dotPositions: ["left", "right"] // Both left and right dots
90
- });
91
- ```
92
-
93
- ### HTML Structure
94
-
95
- ```html
96
- <div
97
- id="connector-container"
98
- style="position: relative; height: 600px;"
99
- >
100
- <div
101
- id="node1"
102
- style="position: absolute; left: 100px; top: 100px;"
103
- >
104
- Node 1
105
- </div>
106
- <div
107
- id="node2"
108
- style="position: absolute; left: 400px; top: 100px;"
109
- >
110
- Node 2
111
- </div>
112
- </div>
113
- ```
114
-
115
- ## 📖 API Documentation
116
-
117
- ### Constructor Options
118
-
119
- | Option | Type | Default | Description |
120
- | ------------------ | ----------- | ------------ | ----------------------------------------------- |
121
- | `container` | HTMLElement | **Required** | Container element for the connector |
122
- | `lineColor` | String | `'#155BD4'` | Color of connection lines |
123
- | `lineWidth` | Number | `2` | Width of connection lines |
124
- | `dotSize` | Number | `12` | Size of connection dots |
125
- | `dotColor` | String | `'#155BD4'` | Color of connection dots |
126
- | `dotHoverScale` | Number | `1.8` | Scale factor when hovering over connection dots |
127
- | `deleteButtonSize` | Number | `20` | Size of delete button |
128
- | `enableNodeDrag` | Boolean | `true` | Enable node dragging |
129
- | `enableSnap` | Boolean | `true` | Enable connection snapping |
130
- | `snapDistance` | Number | `20` | Snap distance in pixels |
131
- | `enableZoom` | Boolean | `true` | Enable zoom functionality |
132
- | `enablePan` | Boolean | `true` | Enable pan functionality |
133
- | `minZoom` | Number | `0.1` | Minimum zoom level (10%) |
134
- | `maxZoom` | Number | `4` | Maximum zoom level (400%) |
135
- | `zoomStep` | Number | `0.1` | Zoom step size (10%) |
136
- | `onConnect` | Function | `() => {}` | Callback when connection is created |
137
- | `onDisconnect` | Function | `() => {}` | Callback when connection is removed |
138
-
139
- ### Methods
140
-
141
- #### `registerNode(id, element, options)`
142
-
143
- Register a node for connection.
144
-
145
- **Parameters:**
146
-
147
- - `id` (String): Unique identifier for the node
148
- - `element` (HTMLElement): DOM element of the node
149
- - `options` (Object): Node configuration
150
- - `dotPositions` (String | Array): Connection dot positions
151
- - `'both'`: Both left and right dots
152
- - `['left', 'right']`: Array format, both sides
153
- - `['left']`: Only left dot
154
- - `['right']`: Only right dot
155
- - `info` (Object): Node extraneous information
156
-
157
- **Returns:** Node object
158
-
159
- **Example:**
160
-
161
- ```javascript
162
- connector.registerNode("myNode", element, {
163
- dotPositions: ["right"],
164
- info: {
165
- id: "123",
166
- name: "apple",
167
- desc: "this is a red apple"
168
- }
169
- });
170
- ```
171
-
172
- #### `createConnection(fromNode, toNode, fromDot, toDot,options)`
173
-
174
- Programmatically create a connection between nodes.
175
-
176
- **Parameters:**
177
-
178
- - `fromNode` (Object): Source node object
179
- - `toNode` (Object): Target node object
180
- - `fromDot` (Object): Source connection dot (optional)
181
- - `toDot` (Object): Target connection dot (optional)
182
- - `options` (Object): Configuration options (optional)
183
- - `silent` (boolean): Whether to create silently (without triggering callbacks)
184
-
185
- **Returns:** Connection object
186
-
187
- **Example:**
188
-
189
- ```javascript
190
- const node1 = connector.nodes[0];
191
- const node2 = connector.nodes[1];
192
- connector.createConnection(node1, node2); // Create connection with callbacks
193
- connector.createConnection(node1, node2, null, null, { silent: true }); // Create connection silently without triggering callbacks
194
- ```
195
-
196
- #### `disconnect(connectionId,options)`
197
-
198
- Remove a connection.
199
-
200
- **Parameters:**
201
-
202
- - `connectionId` (String): Connection ID (optional, if not provided, removes all connections)
203
- - `options` (Object): Configuration options (optional)
204
- - `silent` (boolean): Whether to disconnect silently (without triggering callbacks)
205
-
206
- **Example:**
207
-
208
- ```javascript
209
- connector.disconnect(); // Remove all connections
210
- connector.disconnect("connection-id"); // Remove specific connection
211
- connector.disconnect("connection-id", { silent: true }); // Remove connection silently without triggering callbacks
212
- ```
213
-
214
- #### `getConnections()`
215
-
216
- Get all connections.
217
-
218
- **Returns:** Array of connection information
219
-
220
- **Example:**
221
-
222
- ```javascript
223
- const connections = connector.getConnections();
224
- // [{ id: '...', from: 'node1', to: 'node2', fromDot: 'right', toDot: 'left' }]
225
- ```
226
-
227
- #### `getNodeConnections(nodeId)`
228
-
229
- Get all connections for a specific node.
230
-
231
- **Parameters:**
232
-
233
- - `nodeId` (String): Node ID
234
-
235
- **Returns:** Array of connection information
236
-
237
- #### `updateNodePosition(nodeId)`
238
-
239
- Update node position (called when node is moved).
240
-
241
- **Parameters:**
242
-
243
- - `nodeId` (String): Node ID
244
-
245
- #### `destroy(options)`
246
-
247
- Destroy the connector and clean up all resources.
248
-
249
- **Parameters:**
250
-
251
- - `options` (Object): Configuration options (optional)
252
- - `silent` (boolean): Whether to destroy silently (without triggering callbacks)
253
-
254
- **Example:**
255
-
256
- ```javascript
257
- connector.destroy(); // Destroy silently by default (without triggering callbacks)
258
- connector.destroy({ silent: false }); // Destroy non-silently (triggering callbacks)
259
- ```
260
-
261
- #### `setViewState(scale,translateX,translateY)`
262
-
263
- Set the initial view state
264
-
265
- **Parameters:**
266
-
267
- - `scale` (Number): set initial view scale
268
- - `translateX` (Number): set initial view x-axis translation
269
- - `translateY` (Number): set initial view y-axis translation
270
-
271
- **Example:**
272
-
273
- ````javascript
274
- connector.setViewState({
275
- scale: 0.8,
276
- translateX: 50,
277
- translateY: 30
278
- })
279
- ```
280
-
281
-
282
- ## 🎨 Usage Examples
283
-
284
- ### Vue 3
285
-
286
- ```vue
287
- <template>
288
- <div
289
- class="container"
290
- ref="containerRef"
291
- >
292
- <div
293
- class="node"
294
- ref="node1Ref"
295
- >
296
- Node 1
297
- </div>
298
- <div
299
- class="node"
300
- ref="node2Ref"
301
- >
302
- Node 2
303
- </div>
304
- </div>
305
- </template>
306
-
307
- <script setup>
308
- import { ref, onMounted, onBeforeUnmount } from "vue";
309
- import Connector from "power-link";
310
-
311
- const containerRef = ref(null);
312
- const node1Ref = ref(null);
313
- const node2Ref = ref(null);
314
-
315
- let connector = null;
316
-
317
- onMounted(() => {
318
- connector = new Connector({
319
- container: containerRef.value,
320
- onConnect: (connection) => {
321
- console.log("Connection created:", connection);
322
- },
323
- onDisconnect: (connection) => {
324
- console.log("Connection removed:", connection);
325
- }
326
- });
327
-
328
- connector.registerNode("node1", node1Ref.value, {
329
- dotPositions: ["right"],
330
- info: {
331
- id: "123",
332
- name: "apple",
333
- desc: "this is a red apple"
334
- }
335
- });
336
-
337
- connector.registerNode("node2", node2Ref.value, {
338
- dotPositions: ["left"],
339
- info: {
340
- id: "456",
341
- name: "pear",
342
- desc: "this is a yellow pear"
343
- }
344
- });
345
- });
346
-
347
- onBeforeUnmount(() => {
348
- if (connector) {
349
- connector.destroy();
350
- }
351
- });
352
- </script>
353
-
354
- <style scoped>
355
- .container {
356
- position: relative;
357
- height: 600px;
358
- background: #f5f5f5;
359
- }
360
-
361
- .node {
362
- position: absolute;
363
- padding: 20px;
364
- background: white;
365
- border-radius: 8px;
366
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
367
- cursor: move;
368
- }
369
- </style>
370
- ````
371
-
372
- ### React
373
-
374
- ```jsx
375
- import { useEffect, useRef } from "react";
376
- import Connector from "power-link";
377
-
378
- function App() {
379
- const containerRef = useRef(null);
380
- const node1Ref = useRef(null);
381
- const node2Ref = useRef(null);
382
- const connectorRef = useRef(null);
383
-
384
- useEffect(() => {
385
- if (!containerRef.current) return;
386
-
387
- connectorRef.current = new Connector({
388
- container: containerRef.current,
389
- onConnect: (connection) => {
390
- console.log("Connection created:", connection);
391
- },
392
- onDisconnect: (connection) => {
393
- console.log("Connection removed:", connection);
394
- }
395
- });
396
-
397
- connectorRef.current.registerNode("node1", node1Ref.current, {
398
- dotPositions: ["right"],
399
- info: {
400
- id: "123",
401
- name: "apple",
402
- desc: "this is a red apple"
403
- }
404
- });
405
-
406
- connectorRef.current.registerNode("node2", node2Ref.current, {
407
- dotPositions: ["left"],
408
- info: {
409
- id: "456",
410
- name: "pear",
411
- desc: "this is a yellow pear"
412
- }
413
- });
414
-
415
- return () => {
416
- if (connectorRef.current) {
417
- connectorRef.current.destroy();
418
- }
419
- };
420
- }, []);
421
-
422
- return (
423
- <div
424
- ref={containerRef}
425
- style={{ position: "relative", height: "600px" }}
426
- >
427
- <div
428
- ref={node1Ref}
429
- style={{ position: "absolute", left: "100px", top: "100px" }}
430
- >
431
- Node 1
432
- </div>
433
- <div
434
- ref={node2Ref}
435
- style={{ position: "absolute", left: "400px", top: "100px" }}
436
- >
437
- Node 2
438
- </div>
439
- </div>
440
- );
441
- }
442
- ```
443
-
444
- ### Vanilla JavaScript
445
-
446
- ```html
447
- <!DOCTYPE html>
448
- <html>
449
- <head>
450
- <style>
451
- #container {
452
- position: relative;
453
- height: 600px;
454
- background: #f5f5f5;
455
- }
456
- .node {
457
- position: absolute;
458
- padding: 20px;
459
- background: white;
460
- border-radius: 8px;
461
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
462
- cursor: move;
463
- }
464
- </style>
465
- </head>
466
- <body>
467
- <div id="container">
468
- <div
469
- id="node1"
470
- class="node"
471
- style="left: 100px; top: 100px;"
472
- >
473
- Node 1
474
- </div>
475
- <div
476
- id="node2"
477
- class="node"
478
- style="left: 400px; top: 100px;"
479
- >
480
- Node 2
481
- </div>
482
- </div>
483
-
484
- <script type="module">
485
- import Connector from "power-link";
486
-
487
- const connector = new Connector({
488
- container: document.getElementById("container"),
489
- onConnect: (connection) => {
490
- console.log("Connection created:", connection);
491
- }
492
- });
493
-
494
- connector.registerNode("node1", document.getElementById("node1"), {
495
- dotPositions: ["right"],
496
- info: {
497
- id: "123",
498
- name: "apple",
499
- desc: "this is a red apple"
500
- }
501
- });
502
-
503
- connector.registerNode("node2", document.getElementById("node2"), {
504
- dotPositions: ["left"],
505
- info: {
506
- id: "456",
507
- name: "pear",
508
- desc: "this is a yellow pear"
509
- }
510
- });
511
- </script>
512
- </body>
513
- </html>
514
- ```
515
-
516
- ## 🎯 Advanced Features
517
-
518
- ### Multiple Connection Points
519
-
520
- ```javascript
521
- // Node with both left and right connection points
522
- connector.registerNode("centerNode", element, {
523
- dotPositions: ["left", "right"]
524
- });
525
-
526
- // Node with only left connection point
527
- connector.registerNode("endNode", element, {
528
- dotPositions: ["left"],
529
- info: {
530
- id: "456",
531
- name: "pear",
532
- desc: "this is a yellow pear"
533
- }
534
- });
535
-
536
- // Node with only right connection point
537
- connector.registerNode("startNode", element, {
538
- dotPositions: ["right"]
539
- });
540
- ```
541
-
542
- ### Silent Operations (No Callbacks)
543
-
544
- Sometimes you may want to perform operations without triggering callbacks, such as when initializing connections from saved data or bulk operations.
545
-
546
- ```javascript
547
- // Silent connection creation (won't trigger onConnect callback)
548
- const node1 = connector.nodes[0];
549
- const node2 = connector.nodes[1];
550
- connector.createConnection(node1, node2, null, null, { silent: true });
551
-
552
- // Silent disconnection (won't trigger onDisconnect callback)
553
- connector.disconnect("connection-id", { silent: true });
554
-
555
- // Silent destroy (won't trigger callbacks, default behavior)
556
- connector.destroy(); // Default is silent
557
- connector.destroy({ silent: false }); // Non-silent destroy (triggers callbacks)
558
-
559
- // Example: Restore connections from saved data without triggering callbacks
560
- const savedConnections = [
561
- { from: "node1", to: "node2", fromDot: "right", toDot: "left" },
562
- { from: "node2", to: "node3", fromDot: "right", toDot: "left" }
563
- ];
564
-
565
- savedConnections.forEach((conn) => {
566
- const fromNode = connector.nodes.find((n) => n.id === conn.from);
567
- const toNode = connector.nodes.find((n) => n.id === conn.to);
568
- const fromDot = fromNode?.dots[conn.fromDot];
569
- const toDot = toNode?.dots[conn.toDot];
570
-
571
- if (fromNode && toNode && fromDot && toDot) {
572
- connector.createConnection(fromNode, toNode, fromDot, toDot, { silent: true });
573
- }
574
- });
575
- ```
576
-
577
- ### Node Info and Connection Data
578
-
579
- You can attach custom information to nodes using the `info` parameter, which will be available in connection callbacks.
580
-
581
- ```javascript
582
- // Register node with custom info
583
- const items = [
584
- { id: "node1", name: "Apple", desc: "This is a red apple", type: "fruit" },
585
- { id: "node2", name: "Pear", desc: "This is a yellow pear", type: "fruit" }
586
- ];
587
-
588
- items.forEach((item) => {
589
- const nodeElement = document.getElementById(item.id);
590
- connector.registerNode(item.id, nodeElement, {
591
- dotPositions: ["left"],
592
- info: item // Attach custom info to the node
593
- });
594
- });
595
-
596
- // Access node info in connection callbacks
597
- const connector = new Connector({
598
- container: container,
599
- onConnect: async (connection) => {
600
- console.log("Connection created:", connection);
601
- console.log("From node info:", connection.fromInfo);
602
- // { id: "node1", name: "Apple", desc: "This is a red apple", type: "fruit" }
603
- console.log("To node info:", connection.toInfo);
604
- // { id: "node2", name: "Pear", desc: "This is a yellow pear", type: "fruit" }
605
-
606
- // You can use the info for saving to database, validation, etc.
607
- await saveConnection({
608
- from: connection.from,
609
- to: connection.to,
610
- fromInfo: connection.fromInfo,
611
- toInfo: connection.toInfo
612
- });
613
- },
614
-
615
- onDisconnect: (connection) => {
616
- console.log("Connection removed:", connection);
617
- console.log("From node info:", connection.fromInfo);
618
- console.log("To node info:", connection.toInfo);
619
- }
620
- });
621
- ```
622
-
623
- ### Custom Styling
624
-
625
- ```javascript
626
- const connector = new Connector({
627
- container: container,
628
- lineColor: "#FF6B6B", // Red connections
629
- lineWidth: 3, // Thicker lines
630
- dotSize: 16, // Larger dots
631
- dotColor: "#4ECDC4", // Teal dots
632
- deleteButtonSize: 24 // Larger delete button
633
- });
634
- ```
635
-
636
- ### Event Handling
637
-
638
- ```javascript
639
- const connector = new Connector({
640
- container: container,
641
-
642
- onConnect: (connection) => {
643
- console.log("New connection:", connection);
644
- // { from: 'node1', to: 'node2', fromDot: 'right', toDot: 'left' }
645
-
646
- // Save to database, update state, etc.
647
- saveConnection(connection);
648
- },
649
-
650
- onDisconnect: (connection) => {
651
- console.log("Connection removed:", connection);
652
-
653
- // Update database, state, etc.
654
- removeConnection(connection);
655
- }
656
- });
657
- ```
658
-
659
- ## 🔧 Browser Support
660
-
661
- - Chrome (latest)
662
- - Firefox (latest)
663
- - Safari (latest)
664
- - Edge (latest)
665
-
666
- ## 📝 License
667
-
668
- MIT License
669
-
670
- ## 🌟 Show Your Support
671
-
672
- Give a ⭐️ on [GitHub](https://github.com/Tem-man/power-link) https://github.com/Tem-man/power-link if this project helped you!
673
-
674
- ## 🤝 Contributing
675
-
676
- If you have any questions or need help, please open an issue on GitHub.
677
-
678
- ---
679
-
680
- Made with ❤️ by the power-link team
1
+ # power-link
2
+
3
+ [![npm version](https://img.shields.io/npm/v/power-link.svg)](https://www.npmjs.com/package/power-link)
4
+ [![license](https://img.shields.io/npm/l/power-link.svg)](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
+ ![Node Link Connector Demo](https://github.com/Tem-man/power-link/blob/main/public/images/screen-shot.png)
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
+ ## 🤝 Contributing
795
+
796
+ Contributions, issues and feature requests are welcome!
797
+
798
+ ## 📮 Support
799
+
800
+ If you have any questions or need help, please open an issue on GitHub.
801
+
802
+ ## 🌟 Show Your Support
803
+
804
+ Give a ⭐️ if this project helped you!
805
+
806
+ ---
807
+
808
+ Made with ❤️ by the power-link team