power-link 2.0.5 → 2.1.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 +49 -0
- package/dist/index.d.mts +53 -1
- package/dist/index.d.ts +53 -1
- package/dist/index.global.js +66 -1
- package/dist/index.js +66 -1
- package/dist/index.mjs +66 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,6 +21,8 @@ visit [online demo](https://tem-man.github.io/power-link)
|
|
|
21
21
|
- 📦 **Zero Dependencies** - Pure JavaScript, no framework required
|
|
22
22
|
- 🎭 **Multiple Connection Points** - Support for left and right connection dots
|
|
23
23
|
- 🔌 **Event Callbacks** - Listen to connection and disconnection events
|
|
24
|
+
- 🖱️ **Context Menu Hooks** - `onNodeContextMenu` and `onCanvasContextMenu` for custom right-click menus
|
|
25
|
+
- 📐 **Coordinate Conversion** - `clientToCanvas()` for converting viewport coordinates to canvas space
|
|
24
26
|
|
|
25
27
|
## 📦 Installation
|
|
26
28
|
|
|
@@ -145,6 +147,8 @@ connector.registerNode("node2", node2, {
|
|
|
145
147
|
| `onNodeMove` | Function | `() => {}` | Callback when a node is dragged (receives `{ id, x, y }`) |
|
|
146
148
|
| `onNodeSelect` | Function | `() => {}` | Callback when a node is selected/deselected (receives node info or `null`) |
|
|
147
149
|
| `onNodeDelete` | Function | `() => {}` | Callback when a node is deleted (receives `{ id, info }`) |
|
|
150
|
+
| `onNodeContextMenu` | Function | - | Callback when right-clicking a node (receives `{ id, info, clientX, clientY }`). Library auto prevents default menu. |
|
|
151
|
+
| `onCanvasContextMenu` | Function | - | Callback when right-clicking empty canvas (receives `{ clientX, clientY, canvasX, canvasY }`). `canvasX/Y` are pre-converted coordinates. |
|
|
148
152
|
|
|
149
153
|
### Methods
|
|
150
154
|
|
|
@@ -338,6 +342,37 @@ const viewState = connector.getViewState();
|
|
|
338
342
|
console.log(viewState); // { scale: 1, translateX: 0, translateY: 0 }
|
|
339
343
|
```
|
|
340
344
|
|
|
345
|
+
#### `clientToCanvas(clientX, clientY)`
|
|
346
|
+
|
|
347
|
+
Convert viewport (client) coordinates to canvas (contentWrapper) coordinates. Automatically accounts for current zoom and pan.
|
|
348
|
+
|
|
349
|
+
Useful for: pasting nodes at cursor position, positioning tooltips, handling external drag-and-drop, etc.
|
|
350
|
+
|
|
351
|
+
**Parameters:**
|
|
352
|
+
|
|
353
|
+
- `clientX` (Number): X coordinate in viewport (e.g. `MouseEvent.clientX`)
|
|
354
|
+
- `clientY` (Number): Y coordinate in viewport (e.g. `MouseEvent.clientY`)
|
|
355
|
+
|
|
356
|
+
**Returns:** `{ x, y }` - Canvas coordinates
|
|
357
|
+
|
|
358
|
+
**Example:**
|
|
359
|
+
|
|
360
|
+
```javascript
|
|
361
|
+
// Paste node at mouse cursor position
|
|
362
|
+
canvas.addEventListener('click', (e) => {
|
|
363
|
+
const pos = connector.clientToCanvas(e.clientX, e.clientY);
|
|
364
|
+
addNodeAt(pos.x, pos.y);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// Or with Ctrl+V paste
|
|
368
|
+
document.addEventListener('keydown', (e) => {
|
|
369
|
+
if (e.ctrlKey && e.key === 'v') {
|
|
370
|
+
const pos = connector.clientToCanvas(lastMouseX, lastMouseY);
|
|
371
|
+
pasteNode(pos.x, pos.y);
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
```
|
|
375
|
+
|
|
341
376
|
#### `setZoom(scale)`
|
|
342
377
|
|
|
343
378
|
Set the zoom level (centered on canvas).
|
|
@@ -914,6 +949,20 @@ const connector = new Connector({
|
|
|
914
949
|
// Remove node from your state management
|
|
915
950
|
// In Vue: nodes.value = nodes.value.filter(n => n.id !== id);
|
|
916
951
|
// In React: setNodes(nodes.filter(n => n.id !== id));
|
|
952
|
+
},
|
|
953
|
+
|
|
954
|
+
// Right-click on a node - show custom context menu
|
|
955
|
+
onNodeContextMenu: ({ id, info, clientX, clientY }) => {
|
|
956
|
+
console.log("Node right-clicked:", id, "at", clientX, clientY);
|
|
957
|
+
// Show your custom menu at (clientX, clientY)
|
|
958
|
+
// e.g. showContextMenu(clientX, clientY, { id, info });
|
|
959
|
+
},
|
|
960
|
+
|
|
961
|
+
// Right-click on empty canvas - e.g. paste menu
|
|
962
|
+
onCanvasContextMenu: ({ clientX, clientY, canvasX, canvasY }) => {
|
|
963
|
+
console.log("Canvas right-clicked at", clientX, clientY, "→ canvas", canvasX, canvasY);
|
|
964
|
+
// canvasX/Y are pre-converted for paste position, tooltip placement, etc.
|
|
965
|
+
// e.g. showPasteMenu(clientX, clientY, { x: canvasX, y: canvasY });
|
|
917
966
|
}
|
|
918
967
|
});
|
|
919
968
|
```
|
package/dist/index.d.mts
CHANGED
|
@@ -91,6 +91,28 @@ interface NodeDeleteInfo {
|
|
|
91
91
|
id: string;
|
|
92
92
|
info?: Record<string, unknown>;
|
|
93
93
|
}
|
|
94
|
+
/** 节点右键菜单信息 */
|
|
95
|
+
interface NodeContextMenuInfo {
|
|
96
|
+
/** 被右键的节点 id */
|
|
97
|
+
id: string;
|
|
98
|
+
/** 节点附加信息 */
|
|
99
|
+
info?: Record<string, unknown>;
|
|
100
|
+
/** 鼠标在视口中的 X 坐标 */
|
|
101
|
+
clientX: number;
|
|
102
|
+
/** 鼠标在视口中的 Y 坐标 */
|
|
103
|
+
clientY: number;
|
|
104
|
+
}
|
|
105
|
+
/** 画布空白区域右键菜单信息 */
|
|
106
|
+
interface CanvasContextMenuInfo {
|
|
107
|
+
/** 鼠标在视口中的 X 坐标 */
|
|
108
|
+
clientX: number;
|
|
109
|
+
/** 鼠标在视口中的 Y 坐标 */
|
|
110
|
+
clientY: number;
|
|
111
|
+
/** 鼠标对应的画布(contentWrapper)X 坐标(已换算缩放/平移) */
|
|
112
|
+
canvasX: number;
|
|
113
|
+
/** 鼠标对应的画布(contentWrapper)Y 坐标(已换算缩放/平移) */
|
|
114
|
+
canvasY: number;
|
|
115
|
+
}
|
|
94
116
|
/** 构造函数选项 */
|
|
95
117
|
interface ConnectorOptions extends Partial<ConnectorConfig> {
|
|
96
118
|
container: HTMLElement;
|
|
@@ -102,6 +124,16 @@ interface ConnectorOptions extends Partial<ConnectorConfig> {
|
|
|
102
124
|
onNodeSelect?: (info: NodeSelectInfo | null) => void;
|
|
103
125
|
/** 节点被删除时触发 */
|
|
104
126
|
onNodeDelete?: (info: NodeDeleteInfo) => void;
|
|
127
|
+
/**
|
|
128
|
+
* 右键单击某个节点时触发。
|
|
129
|
+
* 库会自动 preventDefault + stopPropagation,用户只需响应回调渲染自定义菜单。
|
|
130
|
+
*/
|
|
131
|
+
onNodeContextMenu?: (info: NodeContextMenuInfo) => void;
|
|
132
|
+
/**
|
|
133
|
+
* 右键单击画布空白区域时触发(节点上的右键不会冒泡到此)。
|
|
134
|
+
* info 中同时提供视口坐标(clientX/Y)和已换算的画布坐标(canvasX/Y)。
|
|
135
|
+
*/
|
|
136
|
+
onCanvasContextMenu?: (info: CanvasContextMenuInfo) => void;
|
|
105
137
|
config?: Partial<ConnectorConfig>;
|
|
106
138
|
}
|
|
107
139
|
/** 节点注册选项 */
|
|
@@ -259,6 +291,26 @@ declare class Connector {
|
|
|
259
291
|
* @param nodeFactory 可选,原生 JS 场景下的节点元素工厂
|
|
260
292
|
*/
|
|
261
293
|
import(data: ExportData, nodeFactory?: NodeFactory): Promise<void>;
|
|
294
|
+
/**
|
|
295
|
+
* 将视口(client)坐标转换为画布(contentWrapper)坐标
|
|
296
|
+
*
|
|
297
|
+
* 已自动计算当前缩放比例与平移量,可在任何需要把屏幕位置映射到画布的场景中使用:
|
|
298
|
+
* 粘贴节点、从外部拖入元素、定位自定义 tooltip 等。
|
|
299
|
+
*
|
|
300
|
+
* @param clientX - 鼠标/触点在视口中的 X 坐标(如 MouseEvent.clientX)
|
|
301
|
+
* @param clientY - 鼠标/触点在视口中的 Y 坐标(如 MouseEvent.clientY)
|
|
302
|
+
* @returns 对应的画布坐标 `{ x, y }`
|
|
303
|
+
*
|
|
304
|
+
* @example
|
|
305
|
+
* canvas.addEventListener('click', (e) => {
|
|
306
|
+
* const pos = connector.clientToCanvas(e.clientX, e.clientY);
|
|
307
|
+
* console.log('Canvas position:', pos.x, pos.y);
|
|
308
|
+
* });
|
|
309
|
+
*/
|
|
310
|
+
clientToCanvas(clientX: number, clientY: number): {
|
|
311
|
+
x: number;
|
|
312
|
+
y: number;
|
|
313
|
+
};
|
|
262
314
|
/**
|
|
263
315
|
* 设置缩放比例(以画布中心为基准)
|
|
264
316
|
*/
|
|
@@ -299,4 +351,4 @@ declare class Connector {
|
|
|
299
351
|
* @description 导出 Connector 类及相关类型定义
|
|
300
352
|
*/
|
|
301
353
|
|
|
302
|
-
export { type Connection, type ConnectionInfo, Connector, type ConnectorConfig, type ConnectorNode, type ConnectorOptions, type Dot, type DotPosition, type ExportConnectionData, type ExportData, type ExportNodeData, type NodeDeleteInfo, type NodeFactory, type NodeMoveInfo, type NodeSelectInfo, type Point, type RegisterNodeOptions, type SilentOptions, type ViewState, Connector as default };
|
|
354
|
+
export { type CanvasContextMenuInfo, type Connection, type ConnectionInfo, Connector, type ConnectorConfig, type ConnectorNode, type ConnectorOptions, type Dot, type DotPosition, type ExportConnectionData, type ExportData, type ExportNodeData, type NodeContextMenuInfo, type NodeDeleteInfo, type NodeFactory, type NodeMoveInfo, type NodeSelectInfo, type Point, type RegisterNodeOptions, type SilentOptions, type ViewState, Connector as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -91,6 +91,28 @@ interface NodeDeleteInfo {
|
|
|
91
91
|
id: string;
|
|
92
92
|
info?: Record<string, unknown>;
|
|
93
93
|
}
|
|
94
|
+
/** 节点右键菜单信息 */
|
|
95
|
+
interface NodeContextMenuInfo {
|
|
96
|
+
/** 被右键的节点 id */
|
|
97
|
+
id: string;
|
|
98
|
+
/** 节点附加信息 */
|
|
99
|
+
info?: Record<string, unknown>;
|
|
100
|
+
/** 鼠标在视口中的 X 坐标 */
|
|
101
|
+
clientX: number;
|
|
102
|
+
/** 鼠标在视口中的 Y 坐标 */
|
|
103
|
+
clientY: number;
|
|
104
|
+
}
|
|
105
|
+
/** 画布空白区域右键菜单信息 */
|
|
106
|
+
interface CanvasContextMenuInfo {
|
|
107
|
+
/** 鼠标在视口中的 X 坐标 */
|
|
108
|
+
clientX: number;
|
|
109
|
+
/** 鼠标在视口中的 Y 坐标 */
|
|
110
|
+
clientY: number;
|
|
111
|
+
/** 鼠标对应的画布(contentWrapper)X 坐标(已换算缩放/平移) */
|
|
112
|
+
canvasX: number;
|
|
113
|
+
/** 鼠标对应的画布(contentWrapper)Y 坐标(已换算缩放/平移) */
|
|
114
|
+
canvasY: number;
|
|
115
|
+
}
|
|
94
116
|
/** 构造函数选项 */
|
|
95
117
|
interface ConnectorOptions extends Partial<ConnectorConfig> {
|
|
96
118
|
container: HTMLElement;
|
|
@@ -102,6 +124,16 @@ interface ConnectorOptions extends Partial<ConnectorConfig> {
|
|
|
102
124
|
onNodeSelect?: (info: NodeSelectInfo | null) => void;
|
|
103
125
|
/** 节点被删除时触发 */
|
|
104
126
|
onNodeDelete?: (info: NodeDeleteInfo) => void;
|
|
127
|
+
/**
|
|
128
|
+
* 右键单击某个节点时触发。
|
|
129
|
+
* 库会自动 preventDefault + stopPropagation,用户只需响应回调渲染自定义菜单。
|
|
130
|
+
*/
|
|
131
|
+
onNodeContextMenu?: (info: NodeContextMenuInfo) => void;
|
|
132
|
+
/**
|
|
133
|
+
* 右键单击画布空白区域时触发(节点上的右键不会冒泡到此)。
|
|
134
|
+
* info 中同时提供视口坐标(clientX/Y)和已换算的画布坐标(canvasX/Y)。
|
|
135
|
+
*/
|
|
136
|
+
onCanvasContextMenu?: (info: CanvasContextMenuInfo) => void;
|
|
105
137
|
config?: Partial<ConnectorConfig>;
|
|
106
138
|
}
|
|
107
139
|
/** 节点注册选项 */
|
|
@@ -259,6 +291,26 @@ declare class Connector {
|
|
|
259
291
|
* @param nodeFactory 可选,原生 JS 场景下的节点元素工厂
|
|
260
292
|
*/
|
|
261
293
|
import(data: ExportData, nodeFactory?: NodeFactory): Promise<void>;
|
|
294
|
+
/**
|
|
295
|
+
* 将视口(client)坐标转换为画布(contentWrapper)坐标
|
|
296
|
+
*
|
|
297
|
+
* 已自动计算当前缩放比例与平移量,可在任何需要把屏幕位置映射到画布的场景中使用:
|
|
298
|
+
* 粘贴节点、从外部拖入元素、定位自定义 tooltip 等。
|
|
299
|
+
*
|
|
300
|
+
* @param clientX - 鼠标/触点在视口中的 X 坐标(如 MouseEvent.clientX)
|
|
301
|
+
* @param clientY - 鼠标/触点在视口中的 Y 坐标(如 MouseEvent.clientY)
|
|
302
|
+
* @returns 对应的画布坐标 `{ x, y }`
|
|
303
|
+
*
|
|
304
|
+
* @example
|
|
305
|
+
* canvas.addEventListener('click', (e) => {
|
|
306
|
+
* const pos = connector.clientToCanvas(e.clientX, e.clientY);
|
|
307
|
+
* console.log('Canvas position:', pos.x, pos.y);
|
|
308
|
+
* });
|
|
309
|
+
*/
|
|
310
|
+
clientToCanvas(clientX: number, clientY: number): {
|
|
311
|
+
x: number;
|
|
312
|
+
y: number;
|
|
313
|
+
};
|
|
262
314
|
/**
|
|
263
315
|
* 设置缩放比例(以画布中心为基准)
|
|
264
316
|
*/
|
|
@@ -299,4 +351,4 @@ declare class Connector {
|
|
|
299
351
|
* @description 导出 Connector 类及相关类型定义
|
|
300
352
|
*/
|
|
301
353
|
|
|
302
|
-
export { type Connection, type ConnectionInfo, Connector, type ConnectorConfig, type ConnectorNode, type ConnectorOptions, type Dot, type DotPosition, type ExportConnectionData, type ExportData, type ExportNodeData, type NodeDeleteInfo, type NodeFactory, type NodeMoveInfo, type NodeSelectInfo, type Point, type RegisterNodeOptions, type SilentOptions, type ViewState, Connector as default };
|
|
354
|
+
export { type CanvasContextMenuInfo, type Connection, type ConnectionInfo, Connector, type ConnectorConfig, type ConnectorNode, type ConnectorOptions, type Dot, type DotPosition, type ExportConnectionData, type ExportData, type ExportNodeData, type NodeContextMenuInfo, type NodeDeleteInfo, type NodeFactory, type NodeMoveInfo, type NodeSelectInfo, type Point, type RegisterNodeOptions, type SilentOptions, type ViewState, Connector as default };
|
package/dist/index.global.js
CHANGED
|
@@ -388,6 +388,23 @@ var PowerLink = (function (exports) {
|
|
|
388
388
|
if (target.closest && target.closest(".connector-delete-btn")) return;
|
|
389
389
|
this.deselectNode();
|
|
390
390
|
}, "handleContainerMouseDown");
|
|
391
|
+
/**
|
|
392
|
+
* 画布空白区域右键:节点的 contextmenu 会 stopPropagation,
|
|
393
|
+
* 因此只有点击空白处时此处才会触发
|
|
394
|
+
*/
|
|
395
|
+
this.handleContainerContextMenu = /* @__PURE__ */ __name((e) => {
|
|
396
|
+
if (!this.ctx.onCanvasContextMenu) return;
|
|
397
|
+
e.preventDefault();
|
|
398
|
+
const rect = this.ctx.container.getBoundingClientRect();
|
|
399
|
+
const { scale, translateX, translateY } = this.ctx.viewState;
|
|
400
|
+
const info = {
|
|
401
|
+
clientX: e.clientX,
|
|
402
|
+
clientY: e.clientY,
|
|
403
|
+
canvasX: (e.clientX - rect.left - translateX) / scale,
|
|
404
|
+
canvasY: (e.clientY - rect.top - translateY) / scale
|
|
405
|
+
};
|
|
406
|
+
this.ctx.onCanvasContextMenu(info);
|
|
407
|
+
}, "handleContainerContextMenu");
|
|
391
408
|
this.ctx = ctx;
|
|
392
409
|
this.positionHelper = positionHelper;
|
|
393
410
|
}
|
|
@@ -406,6 +423,7 @@ var PowerLink = (function (exports) {
|
|
|
406
423
|
init() {
|
|
407
424
|
document.addEventListener("keydown", this.handleKeyDown);
|
|
408
425
|
this.ctx.container.addEventListener("mousedown", this.handleContainerMouseDown);
|
|
426
|
+
this.ctx.container.addEventListener("contextmenu", this.handleContainerContextMenu);
|
|
409
427
|
}
|
|
410
428
|
// ==================== 节点注册 ====================
|
|
411
429
|
/**
|
|
@@ -465,6 +483,7 @@ var PowerLink = (function (exports) {
|
|
|
465
483
|
} else {
|
|
466
484
|
this.bindNodeClickEvents(node);
|
|
467
485
|
}
|
|
486
|
+
this.bindNodeContextMenuEvents(node);
|
|
468
487
|
return node;
|
|
469
488
|
}
|
|
470
489
|
// ==================== 触点 ====================
|
|
@@ -519,6 +538,24 @@ var PowerLink = (function (exports) {
|
|
|
519
538
|
document.addEventListener("mouseup", this.handleMouseUp);
|
|
520
539
|
});
|
|
521
540
|
}
|
|
541
|
+
/**
|
|
542
|
+
* 绑定节点右键菜单事件
|
|
543
|
+
* 仅在用户提供 onNodeContextMenu 回调时拦截事件,否则不干预浏览器默认行为
|
|
544
|
+
*/
|
|
545
|
+
bindNodeContextMenuEvents(node) {
|
|
546
|
+
node.element.addEventListener("contextmenu", (e) => {
|
|
547
|
+
if (!this.ctx.onNodeContextMenu) return;
|
|
548
|
+
e.preventDefault();
|
|
549
|
+
e.stopPropagation();
|
|
550
|
+
const info = {
|
|
551
|
+
id: node.id,
|
|
552
|
+
info: node.info,
|
|
553
|
+
clientX: e.clientX,
|
|
554
|
+
clientY: e.clientY
|
|
555
|
+
};
|
|
556
|
+
this.ctx.onNodeContextMenu(info);
|
|
557
|
+
});
|
|
558
|
+
}
|
|
522
559
|
/**
|
|
523
560
|
* 绑定节点拖拽事件(同时包含点击选中逻辑)
|
|
524
561
|
*/
|
|
@@ -685,6 +722,7 @@ var PowerLink = (function (exports) {
|
|
|
685
722
|
destroy() {
|
|
686
723
|
document.removeEventListener("keydown", this.handleKeyDown);
|
|
687
724
|
this.ctx.container.removeEventListener("mousedown", this.handleContainerMouseDown);
|
|
725
|
+
this.ctx.container.removeEventListener("contextmenu", this.handleContainerContextMenu);
|
|
688
726
|
this.ctx.nodes.forEach((node) => {
|
|
689
727
|
if (node.dots) {
|
|
690
728
|
Object.values(node.dots).forEach((dot) => {
|
|
@@ -1206,7 +1244,9 @@ var PowerLink = (function (exports) {
|
|
|
1206
1244
|
onNodeSelect: options.onNodeSelect || (() => {
|
|
1207
1245
|
}),
|
|
1208
1246
|
onNodeDelete: options.onNodeDelete || (() => {
|
|
1209
|
-
})
|
|
1247
|
+
}),
|
|
1248
|
+
onNodeContextMenu: options.onNodeContextMenu,
|
|
1249
|
+
onCanvasContextMenu: options.onCanvasContextMenu
|
|
1210
1250
|
};
|
|
1211
1251
|
this.positionHelper = new PositionHelper(this.ctx);
|
|
1212
1252
|
this.viewManager = new ViewManager(this.ctx, this.positionHelper);
|
|
@@ -1447,6 +1487,31 @@ var PowerLink = (function (exports) {
|
|
|
1447
1487
|
this.setViewState(data.viewState);
|
|
1448
1488
|
}
|
|
1449
1489
|
}
|
|
1490
|
+
// ==================== 坐标工具 API ====================
|
|
1491
|
+
/**
|
|
1492
|
+
* 将视口(client)坐标转换为画布(contentWrapper)坐标
|
|
1493
|
+
*
|
|
1494
|
+
* 已自动计算当前缩放比例与平移量,可在任何需要把屏幕位置映射到画布的场景中使用:
|
|
1495
|
+
* 粘贴节点、从外部拖入元素、定位自定义 tooltip 等。
|
|
1496
|
+
*
|
|
1497
|
+
* @param clientX - 鼠标/触点在视口中的 X 坐标(如 MouseEvent.clientX)
|
|
1498
|
+
* @param clientY - 鼠标/触点在视口中的 Y 坐标(如 MouseEvent.clientY)
|
|
1499
|
+
* @returns 对应的画布坐标 `{ x, y }`
|
|
1500
|
+
*
|
|
1501
|
+
* @example
|
|
1502
|
+
* canvas.addEventListener('click', (e) => {
|
|
1503
|
+
* const pos = connector.clientToCanvas(e.clientX, e.clientY);
|
|
1504
|
+
* console.log('Canvas position:', pos.x, pos.y);
|
|
1505
|
+
* });
|
|
1506
|
+
*/
|
|
1507
|
+
clientToCanvas(clientX, clientY) {
|
|
1508
|
+
const rect = this.ctx.container.getBoundingClientRect();
|
|
1509
|
+
const { scale, translateX, translateY } = this.ctx.viewState;
|
|
1510
|
+
return {
|
|
1511
|
+
x: (clientX - rect.left - translateX) / scale,
|
|
1512
|
+
y: (clientY - rect.top - translateY) / scale
|
|
1513
|
+
};
|
|
1514
|
+
}
|
|
1450
1515
|
// ==================== 视图 API ====================
|
|
1451
1516
|
/**
|
|
1452
1517
|
* 设置缩放比例(以画布中心为基准)
|
package/dist/index.js
CHANGED
|
@@ -389,6 +389,23 @@ var _NodeManager = class _NodeManager {
|
|
|
389
389
|
if (target.closest && target.closest(".connector-delete-btn")) return;
|
|
390
390
|
this.deselectNode();
|
|
391
391
|
}, "handleContainerMouseDown");
|
|
392
|
+
/**
|
|
393
|
+
* 画布空白区域右键:节点的 contextmenu 会 stopPropagation,
|
|
394
|
+
* 因此只有点击空白处时此处才会触发
|
|
395
|
+
*/
|
|
396
|
+
this.handleContainerContextMenu = /* @__PURE__ */ __name((e) => {
|
|
397
|
+
if (!this.ctx.onCanvasContextMenu) return;
|
|
398
|
+
e.preventDefault();
|
|
399
|
+
const rect = this.ctx.container.getBoundingClientRect();
|
|
400
|
+
const { scale, translateX, translateY } = this.ctx.viewState;
|
|
401
|
+
const info = {
|
|
402
|
+
clientX: e.clientX,
|
|
403
|
+
clientY: e.clientY,
|
|
404
|
+
canvasX: (e.clientX - rect.left - translateX) / scale,
|
|
405
|
+
canvasY: (e.clientY - rect.top - translateY) / scale
|
|
406
|
+
};
|
|
407
|
+
this.ctx.onCanvasContextMenu(info);
|
|
408
|
+
}, "handleContainerContextMenu");
|
|
392
409
|
this.ctx = ctx;
|
|
393
410
|
this.positionHelper = positionHelper;
|
|
394
411
|
}
|
|
@@ -407,6 +424,7 @@ var _NodeManager = class _NodeManager {
|
|
|
407
424
|
init() {
|
|
408
425
|
document.addEventListener("keydown", this.handleKeyDown);
|
|
409
426
|
this.ctx.container.addEventListener("mousedown", this.handleContainerMouseDown);
|
|
427
|
+
this.ctx.container.addEventListener("contextmenu", this.handleContainerContextMenu);
|
|
410
428
|
}
|
|
411
429
|
// ==================== 节点注册 ====================
|
|
412
430
|
/**
|
|
@@ -466,6 +484,7 @@ var _NodeManager = class _NodeManager {
|
|
|
466
484
|
} else {
|
|
467
485
|
this.bindNodeClickEvents(node);
|
|
468
486
|
}
|
|
487
|
+
this.bindNodeContextMenuEvents(node);
|
|
469
488
|
return node;
|
|
470
489
|
}
|
|
471
490
|
// ==================== 触点 ====================
|
|
@@ -520,6 +539,24 @@ var _NodeManager = class _NodeManager {
|
|
|
520
539
|
document.addEventListener("mouseup", this.handleMouseUp);
|
|
521
540
|
});
|
|
522
541
|
}
|
|
542
|
+
/**
|
|
543
|
+
* 绑定节点右键菜单事件
|
|
544
|
+
* 仅在用户提供 onNodeContextMenu 回调时拦截事件,否则不干预浏览器默认行为
|
|
545
|
+
*/
|
|
546
|
+
bindNodeContextMenuEvents(node) {
|
|
547
|
+
node.element.addEventListener("contextmenu", (e) => {
|
|
548
|
+
if (!this.ctx.onNodeContextMenu) return;
|
|
549
|
+
e.preventDefault();
|
|
550
|
+
e.stopPropagation();
|
|
551
|
+
const info = {
|
|
552
|
+
id: node.id,
|
|
553
|
+
info: node.info,
|
|
554
|
+
clientX: e.clientX,
|
|
555
|
+
clientY: e.clientY
|
|
556
|
+
};
|
|
557
|
+
this.ctx.onNodeContextMenu(info);
|
|
558
|
+
});
|
|
559
|
+
}
|
|
523
560
|
/**
|
|
524
561
|
* 绑定节点拖拽事件(同时包含点击选中逻辑)
|
|
525
562
|
*/
|
|
@@ -686,6 +723,7 @@ var _NodeManager = class _NodeManager {
|
|
|
686
723
|
destroy() {
|
|
687
724
|
document.removeEventListener("keydown", this.handleKeyDown);
|
|
688
725
|
this.ctx.container.removeEventListener("mousedown", this.handleContainerMouseDown);
|
|
726
|
+
this.ctx.container.removeEventListener("contextmenu", this.handleContainerContextMenu);
|
|
689
727
|
this.ctx.nodes.forEach((node) => {
|
|
690
728
|
if (node.dots) {
|
|
691
729
|
Object.values(node.dots).forEach((dot) => {
|
|
@@ -1207,7 +1245,9 @@ var _Connector = class _Connector {
|
|
|
1207
1245
|
onNodeSelect: options.onNodeSelect || (() => {
|
|
1208
1246
|
}),
|
|
1209
1247
|
onNodeDelete: options.onNodeDelete || (() => {
|
|
1210
|
-
})
|
|
1248
|
+
}),
|
|
1249
|
+
onNodeContextMenu: options.onNodeContextMenu,
|
|
1250
|
+
onCanvasContextMenu: options.onCanvasContextMenu
|
|
1211
1251
|
};
|
|
1212
1252
|
this.positionHelper = new PositionHelper(this.ctx);
|
|
1213
1253
|
this.viewManager = new ViewManager(this.ctx, this.positionHelper);
|
|
@@ -1448,6 +1488,31 @@ var _Connector = class _Connector {
|
|
|
1448
1488
|
this.setViewState(data.viewState);
|
|
1449
1489
|
}
|
|
1450
1490
|
}
|
|
1491
|
+
// ==================== 坐标工具 API ====================
|
|
1492
|
+
/**
|
|
1493
|
+
* 将视口(client)坐标转换为画布(contentWrapper)坐标
|
|
1494
|
+
*
|
|
1495
|
+
* 已自动计算当前缩放比例与平移量,可在任何需要把屏幕位置映射到画布的场景中使用:
|
|
1496
|
+
* 粘贴节点、从外部拖入元素、定位自定义 tooltip 等。
|
|
1497
|
+
*
|
|
1498
|
+
* @param clientX - 鼠标/触点在视口中的 X 坐标(如 MouseEvent.clientX)
|
|
1499
|
+
* @param clientY - 鼠标/触点在视口中的 Y 坐标(如 MouseEvent.clientY)
|
|
1500
|
+
* @returns 对应的画布坐标 `{ x, y }`
|
|
1501
|
+
*
|
|
1502
|
+
* @example
|
|
1503
|
+
* canvas.addEventListener('click', (e) => {
|
|
1504
|
+
* const pos = connector.clientToCanvas(e.clientX, e.clientY);
|
|
1505
|
+
* console.log('Canvas position:', pos.x, pos.y);
|
|
1506
|
+
* });
|
|
1507
|
+
*/
|
|
1508
|
+
clientToCanvas(clientX, clientY) {
|
|
1509
|
+
const rect = this.ctx.container.getBoundingClientRect();
|
|
1510
|
+
const { scale, translateX, translateY } = this.ctx.viewState;
|
|
1511
|
+
return {
|
|
1512
|
+
x: (clientX - rect.left - translateX) / scale,
|
|
1513
|
+
y: (clientY - rect.top - translateY) / scale
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1451
1516
|
// ==================== 视图 API ====================
|
|
1452
1517
|
/**
|
|
1453
1518
|
* 设置缩放比例(以画布中心为基准)
|
package/dist/index.mjs
CHANGED
|
@@ -385,6 +385,23 @@ var _NodeManager = class _NodeManager {
|
|
|
385
385
|
if (target.closest && target.closest(".connector-delete-btn")) return;
|
|
386
386
|
this.deselectNode();
|
|
387
387
|
}, "handleContainerMouseDown");
|
|
388
|
+
/**
|
|
389
|
+
* 画布空白区域右键:节点的 contextmenu 会 stopPropagation,
|
|
390
|
+
* 因此只有点击空白处时此处才会触发
|
|
391
|
+
*/
|
|
392
|
+
this.handleContainerContextMenu = /* @__PURE__ */ __name((e) => {
|
|
393
|
+
if (!this.ctx.onCanvasContextMenu) return;
|
|
394
|
+
e.preventDefault();
|
|
395
|
+
const rect = this.ctx.container.getBoundingClientRect();
|
|
396
|
+
const { scale, translateX, translateY } = this.ctx.viewState;
|
|
397
|
+
const info = {
|
|
398
|
+
clientX: e.clientX,
|
|
399
|
+
clientY: e.clientY,
|
|
400
|
+
canvasX: (e.clientX - rect.left - translateX) / scale,
|
|
401
|
+
canvasY: (e.clientY - rect.top - translateY) / scale
|
|
402
|
+
};
|
|
403
|
+
this.ctx.onCanvasContextMenu(info);
|
|
404
|
+
}, "handleContainerContextMenu");
|
|
388
405
|
this.ctx = ctx;
|
|
389
406
|
this.positionHelper = positionHelper;
|
|
390
407
|
}
|
|
@@ -403,6 +420,7 @@ var _NodeManager = class _NodeManager {
|
|
|
403
420
|
init() {
|
|
404
421
|
document.addEventListener("keydown", this.handleKeyDown);
|
|
405
422
|
this.ctx.container.addEventListener("mousedown", this.handleContainerMouseDown);
|
|
423
|
+
this.ctx.container.addEventListener("contextmenu", this.handleContainerContextMenu);
|
|
406
424
|
}
|
|
407
425
|
// ==================== 节点注册 ====================
|
|
408
426
|
/**
|
|
@@ -462,6 +480,7 @@ var _NodeManager = class _NodeManager {
|
|
|
462
480
|
} else {
|
|
463
481
|
this.bindNodeClickEvents(node);
|
|
464
482
|
}
|
|
483
|
+
this.bindNodeContextMenuEvents(node);
|
|
465
484
|
return node;
|
|
466
485
|
}
|
|
467
486
|
// ==================== 触点 ====================
|
|
@@ -516,6 +535,24 @@ var _NodeManager = class _NodeManager {
|
|
|
516
535
|
document.addEventListener("mouseup", this.handleMouseUp);
|
|
517
536
|
});
|
|
518
537
|
}
|
|
538
|
+
/**
|
|
539
|
+
* 绑定节点右键菜单事件
|
|
540
|
+
* 仅在用户提供 onNodeContextMenu 回调时拦截事件,否则不干预浏览器默认行为
|
|
541
|
+
*/
|
|
542
|
+
bindNodeContextMenuEvents(node) {
|
|
543
|
+
node.element.addEventListener("contextmenu", (e) => {
|
|
544
|
+
if (!this.ctx.onNodeContextMenu) return;
|
|
545
|
+
e.preventDefault();
|
|
546
|
+
e.stopPropagation();
|
|
547
|
+
const info = {
|
|
548
|
+
id: node.id,
|
|
549
|
+
info: node.info,
|
|
550
|
+
clientX: e.clientX,
|
|
551
|
+
clientY: e.clientY
|
|
552
|
+
};
|
|
553
|
+
this.ctx.onNodeContextMenu(info);
|
|
554
|
+
});
|
|
555
|
+
}
|
|
519
556
|
/**
|
|
520
557
|
* 绑定节点拖拽事件(同时包含点击选中逻辑)
|
|
521
558
|
*/
|
|
@@ -682,6 +719,7 @@ var _NodeManager = class _NodeManager {
|
|
|
682
719
|
destroy() {
|
|
683
720
|
document.removeEventListener("keydown", this.handleKeyDown);
|
|
684
721
|
this.ctx.container.removeEventListener("mousedown", this.handleContainerMouseDown);
|
|
722
|
+
this.ctx.container.removeEventListener("contextmenu", this.handleContainerContextMenu);
|
|
685
723
|
this.ctx.nodes.forEach((node) => {
|
|
686
724
|
if (node.dots) {
|
|
687
725
|
Object.values(node.dots).forEach((dot) => {
|
|
@@ -1203,7 +1241,9 @@ var _Connector = class _Connector {
|
|
|
1203
1241
|
onNodeSelect: options.onNodeSelect || (() => {
|
|
1204
1242
|
}),
|
|
1205
1243
|
onNodeDelete: options.onNodeDelete || (() => {
|
|
1206
|
-
})
|
|
1244
|
+
}),
|
|
1245
|
+
onNodeContextMenu: options.onNodeContextMenu,
|
|
1246
|
+
onCanvasContextMenu: options.onCanvasContextMenu
|
|
1207
1247
|
};
|
|
1208
1248
|
this.positionHelper = new PositionHelper(this.ctx);
|
|
1209
1249
|
this.viewManager = new ViewManager(this.ctx, this.positionHelper);
|
|
@@ -1444,6 +1484,31 @@ var _Connector = class _Connector {
|
|
|
1444
1484
|
this.setViewState(data.viewState);
|
|
1445
1485
|
}
|
|
1446
1486
|
}
|
|
1487
|
+
// ==================== 坐标工具 API ====================
|
|
1488
|
+
/**
|
|
1489
|
+
* 将视口(client)坐标转换为画布(contentWrapper)坐标
|
|
1490
|
+
*
|
|
1491
|
+
* 已自动计算当前缩放比例与平移量,可在任何需要把屏幕位置映射到画布的场景中使用:
|
|
1492
|
+
* 粘贴节点、从外部拖入元素、定位自定义 tooltip 等。
|
|
1493
|
+
*
|
|
1494
|
+
* @param clientX - 鼠标/触点在视口中的 X 坐标(如 MouseEvent.clientX)
|
|
1495
|
+
* @param clientY - 鼠标/触点在视口中的 Y 坐标(如 MouseEvent.clientY)
|
|
1496
|
+
* @returns 对应的画布坐标 `{ x, y }`
|
|
1497
|
+
*
|
|
1498
|
+
* @example
|
|
1499
|
+
* canvas.addEventListener('click', (e) => {
|
|
1500
|
+
* const pos = connector.clientToCanvas(e.clientX, e.clientY);
|
|
1501
|
+
* console.log('Canvas position:', pos.x, pos.y);
|
|
1502
|
+
* });
|
|
1503
|
+
*/
|
|
1504
|
+
clientToCanvas(clientX, clientY) {
|
|
1505
|
+
const rect = this.ctx.container.getBoundingClientRect();
|
|
1506
|
+
const { scale, translateX, translateY } = this.ctx.viewState;
|
|
1507
|
+
return {
|
|
1508
|
+
x: (clientX - rect.left - translateX) / scale,
|
|
1509
|
+
y: (clientY - rect.top - translateY) / scale
|
|
1510
|
+
};
|
|
1511
|
+
}
|
|
1447
1512
|
// ==================== 视图 API ====================
|
|
1448
1513
|
/**
|
|
1449
1514
|
* 设置缩放比例(以画布中心为基准)
|
package/package.json
CHANGED