@knotx/plugins-connection-line 0.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/LICENSE +21 -0
- package/dist/index.cjs +496 -0
- package/dist/index.d.cts +152 -0
- package/dist/index.d.mts +152 -0
- package/dist/index.d.ts +152 -0
- package/dist/index.mjs +490 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 格桑
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const jsxRuntime = require('@knotx/jsx/jsx-runtime');
|
|
4
|
+
const core = require('@knotx/core');
|
|
5
|
+
const decorators = require('@knotx/decorators');
|
|
6
|
+
const interact = require('interactjs');
|
|
7
|
+
const lodashEs = require('lodash-es');
|
|
8
|
+
const rxjs = require('rxjs');
|
|
9
|
+
require('@knotx/plugins-selection');
|
|
10
|
+
|
|
11
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
12
|
+
|
|
13
|
+
const interact__default = /*#__PURE__*/_interopDefaultCompat(interact);
|
|
14
|
+
|
|
15
|
+
var __create = Object.create;
|
|
16
|
+
var __defProp = Object.defineProperty;
|
|
17
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
18
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
19
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
20
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
21
|
+
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
|
|
22
|
+
var __typeError = (msg) => {
|
|
23
|
+
throw TypeError(msg);
|
|
24
|
+
};
|
|
25
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
26
|
+
var __spreadValues = (a, b) => {
|
|
27
|
+
for (var prop in b || (b = {}))
|
|
28
|
+
if (__hasOwnProp.call(b, prop))
|
|
29
|
+
__defNormalProp(a, prop, b[prop]);
|
|
30
|
+
if (__getOwnPropSymbols)
|
|
31
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
32
|
+
if (__propIsEnum.call(b, prop))
|
|
33
|
+
__defNormalProp(a, prop, b[prop]);
|
|
34
|
+
}
|
|
35
|
+
return a;
|
|
36
|
+
};
|
|
37
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
38
|
+
var __decoratorStart = (base) => {
|
|
39
|
+
var _a2;
|
|
40
|
+
return [, , , __create((_a2 = base == null ? void 0 : base[__knownSymbol("metadata")]) != null ? _a2 : null)];
|
|
41
|
+
};
|
|
42
|
+
var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
|
|
43
|
+
var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
|
|
44
|
+
var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) });
|
|
45
|
+
var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
|
|
46
|
+
var __runInitializers = (array, flags, self, value) => {
|
|
47
|
+
for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
|
|
48
|
+
return value;
|
|
49
|
+
};
|
|
50
|
+
var __decorateElement = (array, flags, name, decorators, target, extra) => {
|
|
51
|
+
var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
|
|
52
|
+
var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
|
|
53
|
+
var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
|
|
54
|
+
var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : { get [name]() {
|
|
55
|
+
return __privateGet(this, extra);
|
|
56
|
+
}, set [name](x) {
|
|
57
|
+
return __privateSet(this, extra, x);
|
|
58
|
+
} }, name));
|
|
59
|
+
k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
|
|
60
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
61
|
+
ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
|
|
62
|
+
if (k) {
|
|
63
|
+
ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => name in x };
|
|
64
|
+
if (k ^ 3) access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
|
|
65
|
+
if (k > 2) access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
|
|
66
|
+
}
|
|
67
|
+
it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? void 0 : { get: desc.get, set: desc.set } : target, ctx), done._ = 1;
|
|
68
|
+
if (k ^ 4 || it === void 0) __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
|
|
69
|
+
else if (typeof it !== "object" || it === null) __typeError("Object expected");
|
|
70
|
+
else __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
|
|
71
|
+
}
|
|
72
|
+
return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
|
|
73
|
+
};
|
|
74
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
75
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
76
|
+
var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
|
|
77
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
78
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
79
|
+
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
80
|
+
var _destroy_dec, _init_dec, _createNodesAndEdges_dec, _createEdges_dec, _createEdge_dec, _createNodeAndEdge_dec, _containerRender_dec, _registerNewNodeCreator_dec, _registerValidator_dec, _options_dec, _getConnectHandleAttributes_dec, _selectedNodeIds_dec, _canInteract_dec, _endInteraction_dec, _startInteraction_dec, _dispatchEdgeOperation_dec, _dispatchNodeOperation_dec, _getNode_dec, _a, _init;
|
|
81
|
+
const DEFAULT_OPTIONS = {
|
|
82
|
+
allowCreateNodeOnBlankArea: true,
|
|
83
|
+
newNodeType: "default",
|
|
84
|
+
edgeType: "bezier",
|
|
85
|
+
connectionLineClassName: "knotx-connection-line",
|
|
86
|
+
allowMultiDrag: true
|
|
87
|
+
};
|
|
88
|
+
class ConnectionLine extends (_a = core.BasePlugin, _getNode_dec = [decorators.inject.getNode()], _dispatchNodeOperation_dec = [decorators.inject.dispatchNodeOperation()], _dispatchEdgeOperation_dec = [decorators.inject.dispatchEdgeOperation()], _startInteraction_dec = [decorators.inject.startInteraction()], _endInteraction_dec = [decorators.inject.endInteraction()], _canInteract_dec = [decorators.inject.canInteract()], _selectedNodeIds_dec = [decorators.inject("selection", "selectedNodeIds")], _getConnectHandleAttributes_dec = [decorators.register("getConnectHandleAttributes")], _options_dec = [decorators.register("options")], _registerValidator_dec = [decorators.register("registerValidator")], _registerNewNodeCreator_dec = [decorators.register("registerNewNodeCreator")], _containerRender_dec = [decorators.layer(core.Layer.Nodes, 10)], _createNodeAndEdge_dec = [decorators.edgeOperator()], _createEdge_dec = [decorators.edgeOperator()], _createEdges_dec = [decorators.edgeOperator()], _createNodesAndEdges_dec = [decorators.edgeOperator()], _init_dec = [decorators.OnInit], _destroy_dec = [decorators.OnDestroy], _a) {
|
|
89
|
+
constructor(options = {}) {
|
|
90
|
+
super();
|
|
91
|
+
__runInitializers(_init, 5, this);
|
|
92
|
+
__publicField(this, "name", "connectionLine");
|
|
93
|
+
__publicField(this, "getNode", __runInitializers(_init, 8, this)), __runInitializers(_init, 11, this);
|
|
94
|
+
__publicField(this, "dispatchNodeOperation", __runInitializers(_init, 12, this)), __runInitializers(_init, 15, this);
|
|
95
|
+
__publicField(this, "dispatchEdgeOperation", __runInitializers(_init, 16, this)), __runInitializers(_init, 19, this);
|
|
96
|
+
__publicField(this, "startInteraction", __runInitializers(_init, 20, this)), __runInitializers(_init, 23, this);
|
|
97
|
+
__publicField(this, "endInteraction", __runInitializers(_init, 24, this)), __runInitializers(_init, 27, this);
|
|
98
|
+
__publicField(this, "canInteract", __runInitializers(_init, 28, this)), __runInitializers(_init, 31, this);
|
|
99
|
+
__publicField(this, "selectedNodeIds", __runInitializers(_init, 32, this, [])), __runInitializers(_init, 35, this);
|
|
100
|
+
__publicField(this, "getConnectHandleAttributes", __runInitializers(_init, 36, this, (nodeId, type, id = 0) => {
|
|
101
|
+
return {
|
|
102
|
+
"className": `${this.classNames.connector} ${this.classNames[type]}`,
|
|
103
|
+
"data-plugin-id": this.pluginId,
|
|
104
|
+
"data-node-id": nodeId,
|
|
105
|
+
"data-connector-id": String(id),
|
|
106
|
+
"data-connector-type": type
|
|
107
|
+
};
|
|
108
|
+
})), __runInitializers(_init, 39, this);
|
|
109
|
+
__publicField(this, "classNames", {
|
|
110
|
+
connector: core.bem("connection-line", "connector"),
|
|
111
|
+
source: core.bem("connection-line", "connector", "source"),
|
|
112
|
+
target: core.bem("connection-line", "connector", "target"),
|
|
113
|
+
active: core.bem("connection-line", "connector", "active")
|
|
114
|
+
});
|
|
115
|
+
// 临时连接线的状态集合,支持多条线同时拖拽
|
|
116
|
+
__publicField(this, "connectionLines", /* @__PURE__ */ new Map());
|
|
117
|
+
// 拖拽起始缩放比例
|
|
118
|
+
__publicField(this, "startScale", 1);
|
|
119
|
+
// 用于存储当前触发拖拽事件的DOM元素对应的节点ID
|
|
120
|
+
__publicField(this, "currentDragNodeId", null);
|
|
121
|
+
// 各种订阅和交互对象
|
|
122
|
+
__publicField(this, "subscription");
|
|
123
|
+
__publicField(this, "dragInteractable");
|
|
124
|
+
__publicField(this, "dropInteractable");
|
|
125
|
+
__publicField(this, "blankAreaDropInteractable");
|
|
126
|
+
// 验证器列表
|
|
127
|
+
__publicField(this, "validators$", new rxjs.BehaviorSubject([]));
|
|
128
|
+
// 新节点创建器列表
|
|
129
|
+
__publicField(this, "creators$", new rxjs.BehaviorSubject([]));
|
|
130
|
+
__publicField(this, "options", __runInitializers(_init, 40, this, DEFAULT_OPTIONS)), __runInitializers(_init, 43, this);
|
|
131
|
+
__publicField(this, "registerValidator", __runInitializers(_init, 44, this, (validator) => {
|
|
132
|
+
const validators = this.validators$.value;
|
|
133
|
+
validators.push(validator);
|
|
134
|
+
this.validators$.next(validators);
|
|
135
|
+
return () => {
|
|
136
|
+
const currentValidators = this.validators$.value;
|
|
137
|
+
const index = currentValidators.indexOf(validator);
|
|
138
|
+
if (index > -1) {
|
|
139
|
+
currentValidators.splice(index, 1);
|
|
140
|
+
this.validators$.next(currentValidators);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
})), __runInitializers(_init, 47, this);
|
|
144
|
+
__publicField(this, "registerNewNodeCreator", __runInitializers(_init, 48, this, (creator) => {
|
|
145
|
+
const creators = this.creators$.value;
|
|
146
|
+
creators.push(creator);
|
|
147
|
+
this.creators$.next(creators);
|
|
148
|
+
return () => {
|
|
149
|
+
const currentCreators = this.creators$.value;
|
|
150
|
+
const index = currentCreators.indexOf(creator);
|
|
151
|
+
if (index > -1) {
|
|
152
|
+
currentCreators.splice(index, 1);
|
|
153
|
+
this.creators$.next(currentCreators);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
})), __runInitializers(_init, 51, this);
|
|
157
|
+
this.options = lodashEs.merge(DEFAULT_OPTIONS, options);
|
|
158
|
+
}
|
|
159
|
+
containerRender() {
|
|
160
|
+
const size = this.getContainerSize();
|
|
161
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
162
|
+
"div",
|
|
163
|
+
{
|
|
164
|
+
className: core.bem("connection-line", "container"),
|
|
165
|
+
"data-plugin-id": this.pluginId,
|
|
166
|
+
style: {
|
|
167
|
+
position: "absolute",
|
|
168
|
+
left: 0,
|
|
169
|
+
top: 0,
|
|
170
|
+
width: size,
|
|
171
|
+
height: size,
|
|
172
|
+
overflow: "visible",
|
|
173
|
+
pointerEvents: "none"
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* 获取容器大小
|
|
180
|
+
*/
|
|
181
|
+
getContainerSize() {
|
|
182
|
+
return 1;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* 应用所有验证器,检查连接是否有效
|
|
186
|
+
*/
|
|
187
|
+
validateConnection(sourceNodeId, targetNodeId) {
|
|
188
|
+
const sourceNode = this.getNode(sourceNodeId);
|
|
189
|
+
if (!sourceNode)
|
|
190
|
+
return false;
|
|
191
|
+
let targetNode;
|
|
192
|
+
if (targetNodeId) {
|
|
193
|
+
targetNode = this.getNode(targetNodeId);
|
|
194
|
+
if (!targetNode)
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
const context = {
|
|
198
|
+
sourceNodeId,
|
|
199
|
+
sourceNode,
|
|
200
|
+
targetNodeId,
|
|
201
|
+
targetNode,
|
|
202
|
+
isValid: true
|
|
203
|
+
};
|
|
204
|
+
return this.validators$.value.reduce(
|
|
205
|
+
(isValid, validator) => isValid && validator(context),
|
|
206
|
+
true
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
getContainerElement() {
|
|
210
|
+
return document.querySelector(`.${core.bem("connection-line", "container")}[data-plugin-id="${this.pluginId}"]`);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* 获取需要为其创建连接线的源节点ID列表
|
|
214
|
+
* 根据当前拖拽的节点和选中的节点决定
|
|
215
|
+
*/
|
|
216
|
+
getSourceNodeIdsForDrag(currentNodeId) {
|
|
217
|
+
if (!this.options.allowMultiDrag || !this.selectedNodeIds.includes(currentNodeId)) {
|
|
218
|
+
return [currentNodeId];
|
|
219
|
+
}
|
|
220
|
+
return [...this.selectedNodeIds];
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* 创建临时连接线
|
|
224
|
+
*/
|
|
225
|
+
createConnectionLine(sourceNodeId, startPosition) {
|
|
226
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
227
|
+
svg.setAttribute("class", `${this.options.connectionLineClassName || "knotx-connection-line"} line-${sourceNodeId}`);
|
|
228
|
+
svg.style.position = "absolute";
|
|
229
|
+
svg.style.top = "0";
|
|
230
|
+
svg.style.left = "0";
|
|
231
|
+
svg.style.overflow = "visible";
|
|
232
|
+
svg.style.width = "100%";
|
|
233
|
+
svg.style.height = "100%";
|
|
234
|
+
svg.style.pointerEvents = "none";
|
|
235
|
+
svg.setAttribute("data-source-node-id", sourceNodeId);
|
|
236
|
+
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
237
|
+
path.setAttribute("fill", "none");
|
|
238
|
+
path.setAttribute("stroke", "currentColor");
|
|
239
|
+
path.setAttribute("stroke-width", `${2 / this.startScale}`);
|
|
240
|
+
path.setAttribute("stroke-dasharray", "5,5");
|
|
241
|
+
svg.appendChild(path);
|
|
242
|
+
const container = this.getContainerElement();
|
|
243
|
+
if (container)
|
|
244
|
+
container.appendChild(svg);
|
|
245
|
+
return {
|
|
246
|
+
sourceNodeId,
|
|
247
|
+
tempLine: svg,
|
|
248
|
+
startPosition: __spreadValues({}, startPosition),
|
|
249
|
+
currentPosition: __spreadValues({}, startPosition)
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* 更新临时连接线的路径
|
|
254
|
+
*/
|
|
255
|
+
updateConnectionLine(state) {
|
|
256
|
+
const path = state.tempLine.querySelector("path");
|
|
257
|
+
if (!path)
|
|
258
|
+
return;
|
|
259
|
+
const sourceX = state.startPosition.x;
|
|
260
|
+
const sourceY = state.startPosition.y;
|
|
261
|
+
const targetX = state.currentPosition.x;
|
|
262
|
+
const targetY = state.currentPosition.y;
|
|
263
|
+
const dx = Math.abs(targetX - sourceX) / 2;
|
|
264
|
+
const controlPoint1X = sourceX + dx;
|
|
265
|
+
const controlPoint1Y = sourceY;
|
|
266
|
+
const controlPoint2X = targetX - dx;
|
|
267
|
+
const controlPoint2Y = targetY;
|
|
268
|
+
const pathData = `M ${sourceX} ${sourceY} C ${controlPoint1X} ${controlPoint1Y}, ${controlPoint2X} ${controlPoint2Y}, ${targetX} ${targetY}`;
|
|
269
|
+
path.setAttribute("d", pathData);
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* 更新所有连接线的当前位置
|
|
273
|
+
*/
|
|
274
|
+
updateAllConnectionLines(dx, dy) {
|
|
275
|
+
const currentDragConnectionLine = this.connectionLines.get(this.currentDragNodeId);
|
|
276
|
+
if (!currentDragConnectionLine) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
currentDragConnectionLine.currentPosition.x += dx / this.startScale;
|
|
280
|
+
currentDragConnectionLine.currentPosition.y += dy / this.startScale;
|
|
281
|
+
this.connectionLines.forEach((state) => {
|
|
282
|
+
state.currentPosition.x = currentDragConnectionLine.currentPosition.x;
|
|
283
|
+
state.currentPosition.y = currentDragConnectionLine.currentPosition.y;
|
|
284
|
+
this.updateConnectionLine(state);
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* 清除所有临时连接线
|
|
289
|
+
*/
|
|
290
|
+
clearAllConnectionLines() {
|
|
291
|
+
this.connectionLines.forEach((state, _nodeId) => {
|
|
292
|
+
state.tempLine.remove();
|
|
293
|
+
});
|
|
294
|
+
this.connectionLines.clear();
|
|
295
|
+
}
|
|
296
|
+
createNodeAndEdge(sourceNodeIds, position) {
|
|
297
|
+
const newNodeId = core.generateId();
|
|
298
|
+
const newNode = {
|
|
299
|
+
id: newNodeId,
|
|
300
|
+
type: this.options.newNodeType,
|
|
301
|
+
position,
|
|
302
|
+
data: {}
|
|
303
|
+
};
|
|
304
|
+
const newEdges = sourceNodeIds.map((sourceNodeId) => ({
|
|
305
|
+
id: core.generateId(),
|
|
306
|
+
source: sourceNodeId,
|
|
307
|
+
target: newNodeId,
|
|
308
|
+
type: this.options.edgeType
|
|
309
|
+
}));
|
|
310
|
+
const createResult = this.creators$.value.reduce((result, creator) => {
|
|
311
|
+
var _a2;
|
|
312
|
+
return (_a2 = creator(sourceNodeIds, newNode, newEdges)) != null ? _a2 : result;
|
|
313
|
+
}, { node: newNode, edges: newEdges });
|
|
314
|
+
if (createResult === false) {
|
|
315
|
+
return [];
|
|
316
|
+
}
|
|
317
|
+
this.dispatchNodeOperation({
|
|
318
|
+
type: "add",
|
|
319
|
+
data: createResult.node
|
|
320
|
+
});
|
|
321
|
+
return [{
|
|
322
|
+
type: "batch",
|
|
323
|
+
operations: createResult.edges.map((edge) => ({
|
|
324
|
+
type: "add",
|
|
325
|
+
data: edge
|
|
326
|
+
}))
|
|
327
|
+
}];
|
|
328
|
+
}
|
|
329
|
+
createEdge(sourceNodeId, targetNodeId) {
|
|
330
|
+
return [{
|
|
331
|
+
type: "add",
|
|
332
|
+
data: {
|
|
333
|
+
id: core.generateId(),
|
|
334
|
+
source: sourceNodeId,
|
|
335
|
+
target: targetNodeId,
|
|
336
|
+
type: this.options.edgeType
|
|
337
|
+
}
|
|
338
|
+
}];
|
|
339
|
+
}
|
|
340
|
+
createEdges(sourceNodeIds, targetNodeId) {
|
|
341
|
+
if (sourceNodeIds.length === 0 || !targetNodeId) {
|
|
342
|
+
return [];
|
|
343
|
+
}
|
|
344
|
+
return [{
|
|
345
|
+
type: "batch",
|
|
346
|
+
operations: sourceNodeIds.map((sourceNodeId) => ({
|
|
347
|
+
type: "add",
|
|
348
|
+
data: {
|
|
349
|
+
id: core.generateId(),
|
|
350
|
+
source: sourceNodeId,
|
|
351
|
+
target: targetNodeId,
|
|
352
|
+
type: this.options.edgeType
|
|
353
|
+
}
|
|
354
|
+
}))
|
|
355
|
+
}];
|
|
356
|
+
}
|
|
357
|
+
createNodesAndEdges(sourceNodeIds, position) {
|
|
358
|
+
if (sourceNodeIds.length === 0) {
|
|
359
|
+
return [];
|
|
360
|
+
}
|
|
361
|
+
return this.createNodeAndEdge(sourceNodeIds, position);
|
|
362
|
+
}
|
|
363
|
+
init() {
|
|
364
|
+
this.dragInteractable = interact__default(`.${this.classNames.source}[data-plugin-id="${this.pluginId}"]`).draggable({
|
|
365
|
+
inertia: false,
|
|
366
|
+
autoScroll: true,
|
|
367
|
+
listeners: {
|
|
368
|
+
start: (event) => {
|
|
369
|
+
var _a2;
|
|
370
|
+
const element = event.target;
|
|
371
|
+
const currentNodeId = element.getAttribute("data-node-id");
|
|
372
|
+
if (!currentNodeId)
|
|
373
|
+
return;
|
|
374
|
+
this.currentDragNodeId = currentNodeId;
|
|
375
|
+
const sourceNodeIds = this.getSourceNodeIdsForDrag(currentNodeId);
|
|
376
|
+
const rect = element.getBoundingClientRect();
|
|
377
|
+
const x = (rect.left + rect.right) / 2;
|
|
378
|
+
const y = (rect.top + rect.bottom) / 2;
|
|
379
|
+
const { left: containerLeft, top: containerTop, width: containerWidth } = ((_a2 = this.getContainerElement()) == null ? void 0 : _a2.getBoundingClientRect()) || { left: 0, top: 0, width: 1 };
|
|
380
|
+
this.startScale = containerWidth / this.getContainerSize();
|
|
381
|
+
const startPosition = {
|
|
382
|
+
x: (x - containerLeft) / this.startScale,
|
|
383
|
+
y: (y - containerTop) / this.startScale
|
|
384
|
+
};
|
|
385
|
+
sourceNodeIds.forEach((sourceNodeId) => {
|
|
386
|
+
const node = this.getNode(sourceNodeId);
|
|
387
|
+
if (!node)
|
|
388
|
+
return;
|
|
389
|
+
let nodeStartPosition = __spreadValues({}, startPosition);
|
|
390
|
+
if (sourceNodeId !== currentNodeId) {
|
|
391
|
+
const connectHandleType = element.getAttribute("data-connector-type");
|
|
392
|
+
const connectHandleId = element.getAttribute("data-connector-id");
|
|
393
|
+
const connector = document.querySelector(`[data-plugin-id="${this.pluginId}"][data-node-id="${sourceNodeId}"][data-connector-type="${connectHandleType}"][data-connector-id="${connectHandleId}"]`);
|
|
394
|
+
if (connector) {
|
|
395
|
+
const connectorRect = connector.getBoundingClientRect();
|
|
396
|
+
const connectorX = (connectorRect.left + connectorRect.right) / 2;
|
|
397
|
+
const connectorY = (connectorRect.top + connectorRect.bottom) / 2;
|
|
398
|
+
nodeStartPosition = {
|
|
399
|
+
x: (connectorX - containerLeft) / this.startScale,
|
|
400
|
+
y: (connectorY - containerTop) / this.startScale
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
const state = this.createConnectionLine(sourceNodeId, nodeStartPosition);
|
|
405
|
+
this.connectionLines.set(sourceNodeId, state);
|
|
406
|
+
this.startInteraction(this.pluginId, "connect", core.InteractionPriority.EntityConnectDrag);
|
|
407
|
+
});
|
|
408
|
+
},
|
|
409
|
+
move: (event) => {
|
|
410
|
+
if (this.connectionLines.size === 0 || !this.currentDragNodeId || !this.canInteract(this.pluginId, "connect"))
|
|
411
|
+
return;
|
|
412
|
+
this.updateAllConnectionLines(event.dx, event.dy);
|
|
413
|
+
},
|
|
414
|
+
end: () => {
|
|
415
|
+
this.clearAllConnectionLines();
|
|
416
|
+
this.currentDragNodeId = null;
|
|
417
|
+
this.endInteraction(this.pluginId, "connect");
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
this.dropInteractable = interact__default(`.${this.classNames.target}[data-plugin-id="${this.pluginId}"]`).dropzone({
|
|
422
|
+
ondragenter: (event) => {
|
|
423
|
+
event.target.classList.add(this.classNames.active);
|
|
424
|
+
},
|
|
425
|
+
ondragleave: (event) => {
|
|
426
|
+
event.target.classList.remove(this.classNames.active);
|
|
427
|
+
},
|
|
428
|
+
ondrop: (event) => {
|
|
429
|
+
if (this.connectionLines.size === 0 || !this.currentDragNodeId)
|
|
430
|
+
return;
|
|
431
|
+
const element = event.target;
|
|
432
|
+
const nodeElement = element.closest(`[data-node-id]`);
|
|
433
|
+
if (!nodeElement)
|
|
434
|
+
return;
|
|
435
|
+
const targetNodeId = nodeElement.getAttribute("data-node-id");
|
|
436
|
+
if (!targetNodeId)
|
|
437
|
+
return;
|
|
438
|
+
const sourceNodeIds = Array.from(this.connectionLines.keys()).filter((id) => id !== targetNodeId);
|
|
439
|
+
if (sourceNodeIds.length === 0)
|
|
440
|
+
return;
|
|
441
|
+
const validSourceNodeIds = sourceNodeIds.filter(
|
|
442
|
+
(sourceNodeId) => this.validateConnection(sourceNodeId, targetNodeId)
|
|
443
|
+
);
|
|
444
|
+
if (validSourceNodeIds.length > 0) {
|
|
445
|
+
this.createEdges(validSourceNodeIds, targetNodeId);
|
|
446
|
+
}
|
|
447
|
+
event.target.classList.remove(this.classNames.active);
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
if (this.options.allowCreateNodeOnBlankArea) {
|
|
451
|
+
this.blankAreaDropInteractable = interact__default(`.${core.bem("canvas", "wrapper")}`).dropzone({
|
|
452
|
+
ondrop: (event) => {
|
|
453
|
+
const element = event.target;
|
|
454
|
+
const isNode = !!element.closest(`[data-node-id]`);
|
|
455
|
+
if (!isNode && this.connectionLines.size > 0 && this.options.allowCreateNodeOnBlankArea) {
|
|
456
|
+
const sourceNodeIds = Array.from(this.connectionLines.keys());
|
|
457
|
+
const firstLine = this.connectionLines.values().next().value;
|
|
458
|
+
if (firstLine) {
|
|
459
|
+
this.createNodesAndEdges(sourceNodeIds, firstLine.currentPosition);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
destroy() {
|
|
467
|
+
var _a2, _b, _c, _d;
|
|
468
|
+
(_a2 = this.dragInteractable) == null ? void 0 : _a2.unset();
|
|
469
|
+
(_b = this.dropInteractable) == null ? void 0 : _b.unset();
|
|
470
|
+
(_c = this.blankAreaDropInteractable) == null ? void 0 : _c.unset();
|
|
471
|
+
this.clearAllConnectionLines();
|
|
472
|
+
(_d = this.subscription) == null ? void 0 : _d.unsubscribe();
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
_init = __decoratorStart(_a);
|
|
476
|
+
__decorateElement(_init, 1, "containerRender", _containerRender_dec, ConnectionLine);
|
|
477
|
+
__decorateElement(_init, 1, "createNodeAndEdge", _createNodeAndEdge_dec, ConnectionLine);
|
|
478
|
+
__decorateElement(_init, 1, "createEdge", _createEdge_dec, ConnectionLine);
|
|
479
|
+
__decorateElement(_init, 1, "createEdges", _createEdges_dec, ConnectionLine);
|
|
480
|
+
__decorateElement(_init, 1, "createNodesAndEdges", _createNodesAndEdges_dec, ConnectionLine);
|
|
481
|
+
__decorateElement(_init, 1, "init", _init_dec, ConnectionLine);
|
|
482
|
+
__decorateElement(_init, 1, "destroy", _destroy_dec, ConnectionLine);
|
|
483
|
+
__decorateElement(_init, 5, "getNode", _getNode_dec, ConnectionLine);
|
|
484
|
+
__decorateElement(_init, 5, "dispatchNodeOperation", _dispatchNodeOperation_dec, ConnectionLine);
|
|
485
|
+
__decorateElement(_init, 5, "dispatchEdgeOperation", _dispatchEdgeOperation_dec, ConnectionLine);
|
|
486
|
+
__decorateElement(_init, 5, "startInteraction", _startInteraction_dec, ConnectionLine);
|
|
487
|
+
__decorateElement(_init, 5, "endInteraction", _endInteraction_dec, ConnectionLine);
|
|
488
|
+
__decorateElement(_init, 5, "canInteract", _canInteract_dec, ConnectionLine);
|
|
489
|
+
__decorateElement(_init, 5, "selectedNodeIds", _selectedNodeIds_dec, ConnectionLine);
|
|
490
|
+
__decorateElement(_init, 5, "getConnectHandleAttributes", _getConnectHandleAttributes_dec, ConnectionLine);
|
|
491
|
+
__decorateElement(_init, 5, "options", _options_dec, ConnectionLine);
|
|
492
|
+
__decorateElement(_init, 5, "registerValidator", _registerValidator_dec, ConnectionLine);
|
|
493
|
+
__decorateElement(_init, 5, "registerNewNodeCreator", _registerNewNodeCreator_dec, ConnectionLine);
|
|
494
|
+
__decoratorMetadata(_init, ConnectionLine);
|
|
495
|
+
|
|
496
|
+
exports.ConnectionLine = ConnectionLine;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { NodeData, EdgeData, BasePlugin, NodeOperation, EdgeOperation, InteractionPriority } from '@knotx/core';
|
|
2
|
+
|
|
3
|
+
interface ConnectionLineOptions {
|
|
4
|
+
/**
|
|
5
|
+
* 是否允许连接到画布空白处并创建新节点
|
|
6
|
+
* @default false
|
|
7
|
+
*/
|
|
8
|
+
allowCreateNodeOnBlankArea?: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* 当连接到画布空白处时创建的节点类型
|
|
11
|
+
* @default 'default'
|
|
12
|
+
*/
|
|
13
|
+
newNodeType?: string;
|
|
14
|
+
/**
|
|
15
|
+
* 新创建的边的类型
|
|
16
|
+
* @default 'bezier'
|
|
17
|
+
*/
|
|
18
|
+
edgeType?: string;
|
|
19
|
+
/**
|
|
20
|
+
* 临时连接线的类名
|
|
21
|
+
* @default 'knotx-connection-line'
|
|
22
|
+
*/
|
|
23
|
+
connectionLineClassName?: string;
|
|
24
|
+
/**
|
|
25
|
+
* 是否支持从多个选中节点同时拖拽连接线
|
|
26
|
+
* @default true
|
|
27
|
+
*/
|
|
28
|
+
allowMultiDrag?: boolean;
|
|
29
|
+
}
|
|
30
|
+
interface ConnectionValidateContext {
|
|
31
|
+
sourceNodeId: string;
|
|
32
|
+
sourceNode: NodeData;
|
|
33
|
+
targetNodeId?: string;
|
|
34
|
+
targetNode?: NodeData;
|
|
35
|
+
isValid: boolean;
|
|
36
|
+
}
|
|
37
|
+
type ConnectionValidator = (context: ConnectionValidateContext) => boolean;
|
|
38
|
+
declare module '@knotx/core' {
|
|
39
|
+
interface PluginData {
|
|
40
|
+
connectionLine: {
|
|
41
|
+
options: ConnectionLineOptions;
|
|
42
|
+
registerValidator: (validator: ConnectionValidator) => () => void;
|
|
43
|
+
registerNewNodeCreator: (creator: (sourceNodeIds: string[], node: NodeData, edges: EdgeData[]) => ({
|
|
44
|
+
node: NodeData;
|
|
45
|
+
edges: EdgeData[];
|
|
46
|
+
}) | false) => () => void;
|
|
47
|
+
/**
|
|
48
|
+
* 获取连接点的属性
|
|
49
|
+
* @param nodeId 节点ID
|
|
50
|
+
* @param type 连接点类型,可选值为 'source' 或 'target'
|
|
51
|
+
* @param index 连接点索引,多个连接点时使用
|
|
52
|
+
* @returns 连接点属性对象
|
|
53
|
+
*/
|
|
54
|
+
getConnectHandleAttributes: (nodeId: string, type: 'source' | 'target', index?: number) => Record<string, string>;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* 连接线插件
|
|
60
|
+
* 允许从指定DOM元素拖拽出连接线,连接到其他节点
|
|
61
|
+
*/
|
|
62
|
+
declare class ConnectionLine extends BasePlugin<'connectionLine'> {
|
|
63
|
+
name: "connectionLine";
|
|
64
|
+
getNode: (id: string) => NodeData | undefined;
|
|
65
|
+
dispatchNodeOperation: (operation: NodeOperation) => void;
|
|
66
|
+
dispatchEdgeOperation: (operation: EdgeOperation) => void;
|
|
67
|
+
startInteraction: (pluginId: string, type: string, priority: InteractionPriority) => void;
|
|
68
|
+
endInteraction: (pluginId: string, type: string) => void;
|
|
69
|
+
canInteract: (pluginId: string, type: string, autoStartPriority?: InteractionPriority) => boolean;
|
|
70
|
+
selectedNodeIds: string[];
|
|
71
|
+
getConnectHandleAttributes: (nodeId: string, type: "source" | "target", id?: number) => {
|
|
72
|
+
className: string;
|
|
73
|
+
'data-plugin-id': string;
|
|
74
|
+
'data-node-id': string;
|
|
75
|
+
'data-connector-id': string;
|
|
76
|
+
'data-connector-type': "source" | "target";
|
|
77
|
+
};
|
|
78
|
+
private classNames;
|
|
79
|
+
private connectionLines;
|
|
80
|
+
private startScale;
|
|
81
|
+
private currentDragNodeId;
|
|
82
|
+
private subscription?;
|
|
83
|
+
private dragInteractable?;
|
|
84
|
+
private dropInteractable?;
|
|
85
|
+
private blankAreaDropInteractable?;
|
|
86
|
+
private validators$;
|
|
87
|
+
private creators$;
|
|
88
|
+
constructor(options?: ConnectionLineOptions);
|
|
89
|
+
options: ConnectionLineOptions;
|
|
90
|
+
registerValidator: (validator: ConnectionValidator) => () => void;
|
|
91
|
+
registerNewNodeCreator: (creator: (sourceNodeIds: string[], node: NodeData, edges: EdgeData[]) => ({
|
|
92
|
+
node: NodeData;
|
|
93
|
+
edges: EdgeData[];
|
|
94
|
+
}) | false) => () => void;
|
|
95
|
+
containerRender(): JSX.Element;
|
|
96
|
+
/**
|
|
97
|
+
* 获取容器大小
|
|
98
|
+
*/
|
|
99
|
+
private getContainerSize;
|
|
100
|
+
/**
|
|
101
|
+
* 应用所有验证器,检查连接是否有效
|
|
102
|
+
*/
|
|
103
|
+
private validateConnection;
|
|
104
|
+
private getContainerElement;
|
|
105
|
+
/**
|
|
106
|
+
* 获取需要为其创建连接线的源节点ID列表
|
|
107
|
+
* 根据当前拖拽的节点和选中的节点决定
|
|
108
|
+
*/
|
|
109
|
+
private getSourceNodeIdsForDrag;
|
|
110
|
+
/**
|
|
111
|
+
* 创建临时连接线
|
|
112
|
+
*/
|
|
113
|
+
private createConnectionLine;
|
|
114
|
+
/**
|
|
115
|
+
* 更新临时连接线的路径
|
|
116
|
+
*/
|
|
117
|
+
private updateConnectionLine;
|
|
118
|
+
/**
|
|
119
|
+
* 更新所有连接线的当前位置
|
|
120
|
+
*/
|
|
121
|
+
private updateAllConnectionLines;
|
|
122
|
+
/**
|
|
123
|
+
* 清除所有临时连接线
|
|
124
|
+
*/
|
|
125
|
+
private clearAllConnectionLines;
|
|
126
|
+
/**
|
|
127
|
+
* 创建新节点
|
|
128
|
+
*/
|
|
129
|
+
createNodeAndEdge(sourceNodeIds: string[], position: {
|
|
130
|
+
x: number;
|
|
131
|
+
y: number;
|
|
132
|
+
}): EdgeOperation[];
|
|
133
|
+
/**
|
|
134
|
+
* 创建新的边
|
|
135
|
+
*/
|
|
136
|
+
createEdge(sourceNodeId: string, targetNodeId: string): EdgeOperation[];
|
|
137
|
+
/**
|
|
138
|
+
* 批量创建新的边
|
|
139
|
+
*/
|
|
140
|
+
createEdges(sourceNodeIds: string[], targetNodeId: string): EdgeOperation[];
|
|
141
|
+
/**
|
|
142
|
+
* 批量创建新节点和边
|
|
143
|
+
*/
|
|
144
|
+
createNodesAndEdges(sourceNodeIds: string[], position: {
|
|
145
|
+
x: number;
|
|
146
|
+
y: number;
|
|
147
|
+
}): EdgeOperation[];
|
|
148
|
+
init(): void;
|
|
149
|
+
destroy(): void;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export { ConnectionLine, type ConnectionLineOptions, type ConnectionValidateContext, type ConnectionValidator };
|