@yh-ui/flow 1.0.52 → 1.0.54
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/dist/Flow.vue +183 -34
- package/dist/core/useKeyboard.cjs +3 -1
- package/dist/core/useKeyboard.mjs +3 -1
- package/dist/plugins/plugins/layout.cjs +30 -7
- package/dist/plugins/plugins/layout.mjs +50 -12
- package/dist/renderer/EdgeRenderer.vue +32 -3
- package/dist/renderer/NodeRenderer.vue +25 -1
- package/dist/utils/edge.cjs +120 -1
- package/dist/utils/edge.d.ts +14 -1
- package/dist/utils/edge.mjs +134 -1
- package/dist/utils/validation.cjs +1 -8
- package/dist/utils/validation.mjs +1 -4
- package/package.json +8 -4
package/dist/Flow.vue
CHANGED
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
</template>
|
|
121
121
|
|
|
122
122
|
<script setup>
|
|
123
|
-
import { ref, computed, watch, onMounted, onBeforeUnmount, shallowRef, useId } from "vue";
|
|
123
|
+
import { ref, computed, watch, onMounted, onBeforeUnmount, shallowRef, useId, toRaw } from "vue";
|
|
124
124
|
if (typeof window !== "undefined") window.__YH_FLOW_VERSION__ = "1.0.1";
|
|
125
125
|
import EdgeRenderer from "./renderer/EdgeRenderer.vue";
|
|
126
126
|
import EdgeHandlesRenderer from "./renderer/EdgeHandlesRenderer.vue";
|
|
@@ -135,7 +135,8 @@ import {
|
|
|
135
135
|
createControlsPlugin,
|
|
136
136
|
createSnapPlugin,
|
|
137
137
|
createExportPlugin,
|
|
138
|
-
createLayoutPlugin
|
|
138
|
+
createLayoutPlugin,
|
|
139
|
+
createHistoryPlugin
|
|
139
140
|
} from "./plugins/plugins";
|
|
140
141
|
import { useViewport } from "./core/useViewport";
|
|
141
142
|
import { useNodes } from "./core/useNodes";
|
|
@@ -243,8 +244,10 @@ const usePlugin = (plugin) => {
|
|
|
243
244
|
registeredPlugins.value = pluginManager.getPlugins();
|
|
244
245
|
};
|
|
245
246
|
const removePlugin = (pluginId) => {
|
|
246
|
-
pluginManager.
|
|
247
|
-
|
|
247
|
+
if (pluginManager.hasPlugin(pluginId)) {
|
|
248
|
+
pluginManager.unregister(pluginId);
|
|
249
|
+
registeredPlugins.value = pluginManager.getPlugins();
|
|
250
|
+
}
|
|
248
251
|
};
|
|
249
252
|
const nodesManager = useNodes(viewportRef, {
|
|
250
253
|
nodes: nodesRef,
|
|
@@ -259,12 +262,51 @@ const selectionManager = useSelection({
|
|
|
259
262
|
nodes: nodesRef,
|
|
260
263
|
edges: edgesRef
|
|
261
264
|
});
|
|
262
|
-
const
|
|
265
|
+
const localHistoryManager = useHistory(nodesRef, edgesRef, {
|
|
263
266
|
maxHistory: props.maxHistory || 50,
|
|
264
267
|
onHistoryChange: (canUndo, canRedo) => {
|
|
265
268
|
emit("historyChange", { canUndo, canRedo });
|
|
266
269
|
}
|
|
267
270
|
});
|
|
271
|
+
const getHistoryPlugin = () => {
|
|
272
|
+
return pluginManager.getPlugin("history");
|
|
273
|
+
};
|
|
274
|
+
const historyManager = {
|
|
275
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
276
|
+
push: (state) => {
|
|
277
|
+
const plugin = getHistoryPlugin();
|
|
278
|
+
if (plugin) {
|
|
279
|
+
plugin.saveSnapshot();
|
|
280
|
+
} else {
|
|
281
|
+
localHistoryManager.push(state);
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
undo: () => {
|
|
285
|
+
const plugin = getHistoryPlugin();
|
|
286
|
+
if (plugin) {
|
|
287
|
+
plugin.undo();
|
|
288
|
+
} else {
|
|
289
|
+
localHistoryManager.undo();
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
redo: () => {
|
|
293
|
+
const plugin = getHistoryPlugin();
|
|
294
|
+
if (plugin) {
|
|
295
|
+
plugin.redo();
|
|
296
|
+
} else {
|
|
297
|
+
localHistoryManager.redo();
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
clear: () => {
|
|
301
|
+
const plugin = getHistoryPlugin();
|
|
302
|
+
if (plugin) {
|
|
303
|
+
plugin.clearHistory();
|
|
304
|
+
} else {
|
|
305
|
+
localHistoryManager.clear();
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
historyManager.push({ nodes: nodesRef.value, edges: edgesRef.value });
|
|
268
310
|
const alignmentManager = useAlignment({
|
|
269
311
|
nodes: nodesRef,
|
|
270
312
|
snapThreshold: snapThreshold.value
|
|
@@ -392,6 +434,63 @@ const handleMouseMove = (event) => {
|
|
|
392
434
|
}
|
|
393
435
|
}
|
|
394
436
|
};
|
|
437
|
+
const getNodeHandles = (node, type) => {
|
|
438
|
+
if (node.handleBounds) {
|
|
439
|
+
const handles = [];
|
|
440
|
+
if (node.handleBounds.top) handles.push(...node.handleBounds.top);
|
|
441
|
+
if (node.handleBounds.right) handles.push(...node.handleBounds.right);
|
|
442
|
+
if (node.handleBounds.bottom) handles.push(...node.handleBounds.bottom);
|
|
443
|
+
if (node.handleBounds.left) handles.push(...node.handleBounds.left);
|
|
444
|
+
return handles.filter((h) => h.type === type);
|
|
445
|
+
}
|
|
446
|
+
if (node.type === "group") return [];
|
|
447
|
+
if (node.type === "input") {
|
|
448
|
+
return type === "source" ? [{ id: void 0, type: "source", position: "right" }] : [];
|
|
449
|
+
}
|
|
450
|
+
if (node.type === "output") {
|
|
451
|
+
return type === "target" ? [{ id: void 0, type: "target", position: "left" }] : [];
|
|
452
|
+
}
|
|
453
|
+
if (node.type === "bpmn-start") {
|
|
454
|
+
return type === "source" ? [{ id: void 0, type: "source", position: "right" }] : [];
|
|
455
|
+
}
|
|
456
|
+
if (node.type === "bpmn-end") {
|
|
457
|
+
return type === "target" ? [{ id: void 0, type: "target", position: "left" }] : [];
|
|
458
|
+
}
|
|
459
|
+
if (node.type === "bpmn-task" || node.type === "bpmn-service-task" || node.type === "bpmn-user-task") {
|
|
460
|
+
if (type === "source") return [{ id: void 0, type: "source", position: "right" }];
|
|
461
|
+
return [{ id: void 0, type: "target", position: "left" }];
|
|
462
|
+
}
|
|
463
|
+
if (node.type === "bpmn-exclusive-gateway" || node.type === "bpmn-parallel-gateway" || node.type === "bpmn-inclusive-gateway") {
|
|
464
|
+
if (type === "source") {
|
|
465
|
+
return [
|
|
466
|
+
{ id: void 0, type: "source", position: "right" },
|
|
467
|
+
{ id: void 0, type: "source", position: "bottom" }
|
|
468
|
+
];
|
|
469
|
+
}
|
|
470
|
+
return [{ id: void 0, type: "target", position: "left" }];
|
|
471
|
+
}
|
|
472
|
+
if (node.type === "ai-start") {
|
|
473
|
+
return type === "source" ? [{ id: void 0, type: "source", position: "right" }] : [];
|
|
474
|
+
}
|
|
475
|
+
if (node.type === "ai-end") {
|
|
476
|
+
return type === "target" ? [{ id: void 0, type: "target", position: "left" }] : [];
|
|
477
|
+
}
|
|
478
|
+
if (node.type === "ai-llm" || node.type === "ai-prompt" || node.type === "ai-agent" || node.type === "ai-tool" || node.type === "ai-memory") {
|
|
479
|
+
if (type === "source") return [{ id: void 0, type: "source", position: "right" }];
|
|
480
|
+
return [{ id: void 0, type: "target", position: "left" }];
|
|
481
|
+
}
|
|
482
|
+
if (node.type === "ai-condition") {
|
|
483
|
+
if (type === "source") {
|
|
484
|
+
return [
|
|
485
|
+
{ id: void 0, type: "source", position: "right" },
|
|
486
|
+
{ id: void 0, type: "source", position: "bottom" }
|
|
487
|
+
];
|
|
488
|
+
}
|
|
489
|
+
return [{ id: void 0, type: "target", position: "left" }];
|
|
490
|
+
}
|
|
491
|
+
if (type === "source") return [{ id: void 0, type: "source", position: "right" }];
|
|
492
|
+
return [{ id: void 0, type: "target", position: "left" }];
|
|
493
|
+
};
|
|
395
494
|
const handleMouseUp = (event) => {
|
|
396
495
|
isPanning.value = false;
|
|
397
496
|
if (isSelecting.value) {
|
|
@@ -411,17 +510,37 @@ const handleMouseUp = (event) => {
|
|
|
411
510
|
if (targetNode) {
|
|
412
511
|
const sourceNodeId = updatingEdge.value ? updatingEdge.value.handleType === "source" ? targetNode.id : updatingEdge.value.edge.source : connectionStart.value.nodeId;
|
|
413
512
|
const targetNodeId = updatingEdge.value ? updatingEdge.value.handleType === "target" ? targetNode.id : updatingEdge.value.edge.target : targetNode.id;
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
513
|
+
let dropHandleId = void 0;
|
|
514
|
+
let dropHandleType = void 0;
|
|
515
|
+
const element = document.elementFromPoint(event.clientX, event.clientY);
|
|
516
|
+
const handleEl = element?.closest(".yh-flow-handle");
|
|
517
|
+
if (handleEl) {
|
|
518
|
+
dropHandleId = handleEl.getAttribute("data-handle-id") || void 0;
|
|
519
|
+
dropHandleType = handleEl.getAttribute("data-handle-type") || void 0;
|
|
520
|
+
}
|
|
521
|
+
let finalSourceHandle = void 0;
|
|
522
|
+
let finalTargetHandle = void 0;
|
|
523
|
+
if (updatingEdge.value) {
|
|
524
|
+
const { edge, handleType } = updatingEdge.value;
|
|
525
|
+
if (handleType === "source") {
|
|
526
|
+
finalSourceHandle = dropHandleType === "source" ? dropHandleId : getNodeHandles(targetNode, "source")[0]?.id || void 0;
|
|
527
|
+
finalTargetHandle = edge.targetHandle || void 0;
|
|
528
|
+
} else {
|
|
529
|
+
finalSourceHandle = edge.sourceHandle || void 0;
|
|
530
|
+
finalTargetHandle = dropHandleType === "target" ? dropHandleId : getNodeHandles(targetNode, "target")[0]?.id || void 0;
|
|
423
531
|
}
|
|
424
|
-
|
|
532
|
+
} else {
|
|
533
|
+
finalSourceHandle = connectionStart.value?.handleId || void 0;
|
|
534
|
+
finalTargetHandle = dropHandleType === "target" ? dropHandleId : getNodeHandles(targetNode, "target")[0]?.id || void 0;
|
|
535
|
+
}
|
|
536
|
+
const sourceNode = nodesRef.value.find((n) => n.id === sourceNodeId);
|
|
537
|
+
const targetNodeObj = nodesRef.value.find((n) => n.id === targetNodeId);
|
|
538
|
+
const validationResult = validateConnection(sourceNode, targetNodeObj, {
|
|
539
|
+
source: sourceNodeId,
|
|
540
|
+
target: targetNodeId,
|
|
541
|
+
sourceHandle: finalSourceHandle,
|
|
542
|
+
targetHandle: finalTargetHandle
|
|
543
|
+
});
|
|
425
544
|
if (!validationResult.isValid) {
|
|
426
545
|
console.warn("Invalid connection:", validationResult.message);
|
|
427
546
|
isConnecting.value = false;
|
|
@@ -430,12 +549,12 @@ const handleMouseUp = (event) => {
|
|
|
430
549
|
return;
|
|
431
550
|
}
|
|
432
551
|
if (updatingEdge.value) {
|
|
433
|
-
const { edge
|
|
552
|
+
const { edge } = updatingEdge.value;
|
|
434
553
|
const connection = {
|
|
435
|
-
source:
|
|
436
|
-
target:
|
|
437
|
-
sourceHandle:
|
|
438
|
-
targetHandle:
|
|
554
|
+
source: sourceNodeId,
|
|
555
|
+
target: targetNodeId,
|
|
556
|
+
sourceHandle: finalSourceHandle,
|
|
557
|
+
targetHandle: finalTargetHandle
|
|
439
558
|
};
|
|
440
559
|
edgesManager.updateEdge(edge.id, connection);
|
|
441
560
|
emit("edgeUpdate", { edge, connection });
|
|
@@ -443,10 +562,10 @@ const handleMouseUp = (event) => {
|
|
|
443
562
|
} else {
|
|
444
563
|
const newEdge = {
|
|
445
564
|
id: `edge-${Date.now()}`,
|
|
446
|
-
source:
|
|
447
|
-
target:
|
|
448
|
-
sourceHandle:
|
|
449
|
-
targetHandle:
|
|
565
|
+
source: sourceNodeId,
|
|
566
|
+
target: targetNodeId,
|
|
567
|
+
sourceHandle: finalSourceHandle,
|
|
568
|
+
targetHandle: finalTargetHandle,
|
|
450
569
|
type: "bezier"
|
|
451
570
|
};
|
|
452
571
|
edgesManager.addEdge(newEdge);
|
|
@@ -741,16 +860,19 @@ pluginManager.init(flowInstance);
|
|
|
741
860
|
defineExpose(flowInstance);
|
|
742
861
|
watch(
|
|
743
862
|
() => props.nodes,
|
|
744
|
-
(
|
|
745
|
-
if (
|
|
746
|
-
|
|
863
|
+
(nodes) => {
|
|
864
|
+
if (toRaw(nodesRef.value) !== toRaw(nodes)) {
|
|
865
|
+
nodesRef.value = nodes;
|
|
866
|
+
}
|
|
747
867
|
},
|
|
748
868
|
{ deep: true }
|
|
749
869
|
);
|
|
750
870
|
watch(
|
|
751
871
|
() => props.edges,
|
|
752
|
-
(
|
|
753
|
-
edgesRef.value
|
|
872
|
+
(edges) => {
|
|
873
|
+
if (toRaw(edgesRef.value) !== toRaw(edges)) {
|
|
874
|
+
edgesRef.value = edges;
|
|
875
|
+
}
|
|
754
876
|
},
|
|
755
877
|
{ deep: true }
|
|
756
878
|
);
|
|
@@ -806,13 +928,14 @@ watch(
|
|
|
806
928
|
{ deep: true }
|
|
807
929
|
);
|
|
808
930
|
let handleKeyDown;
|
|
931
|
+
let resizeObserver = null;
|
|
809
932
|
onMounted(() => {
|
|
810
933
|
document.addEventListener("mousemove", handleMouseMove);
|
|
811
934
|
document.addEventListener("mouseup", handleMouseUp);
|
|
812
935
|
if (containerRef.value) {
|
|
813
936
|
containerWidth.value = containerRef.value.clientWidth;
|
|
814
937
|
containerHeight.value = containerRef.value.clientHeight;
|
|
815
|
-
|
|
938
|
+
resizeObserver = new ResizeObserver((entries) => {
|
|
816
939
|
for (const entry of entries) {
|
|
817
940
|
containerWidth.value = entry.contentRect.width;
|
|
818
941
|
containerHeight.value = entry.contentRect.height;
|
|
@@ -955,16 +1078,38 @@ onMounted(() => {
|
|
|
955
1078
|
}
|
|
956
1079
|
}
|
|
957
1080
|
);
|
|
1081
|
+
watch(
|
|
1082
|
+
() => [props.history, props.maxHistory],
|
|
1083
|
+
([history, maxHistory]) => {
|
|
1084
|
+
removePlugin("history");
|
|
1085
|
+
if (history) {
|
|
1086
|
+
usePlugin(
|
|
1087
|
+
createHistoryPlugin({
|
|
1088
|
+
enabled: true,
|
|
1089
|
+
maxHistory: maxHistory || 50,
|
|
1090
|
+
enableKeyboard: false,
|
|
1091
|
+
onHistoryChange: (canUndo, canRedo) => {
|
|
1092
|
+
emit("historyChange", { canUndo, canRedo });
|
|
1093
|
+
}
|
|
1094
|
+
})
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
1097
|
+
},
|
|
1098
|
+
{ immediate: true }
|
|
1099
|
+
);
|
|
958
1100
|
if (props.keyboardShortcuts) {
|
|
959
1101
|
const keyboard = useKeyboard({
|
|
960
1102
|
enabled: true,
|
|
961
1103
|
onDelete: () => {
|
|
962
1104
|
const selectedNodes = nodesRef.value.filter((n) => n.selected);
|
|
963
1105
|
const selectedEdges = edgesRef.value.filter((e) => e.selected);
|
|
964
|
-
selectedNodes.
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1106
|
+
if (selectedNodes.length > 0 || selectedEdges.length > 0) {
|
|
1107
|
+
historyManager.push({ nodes: nodesRef.value, edges: edgesRef.value });
|
|
1108
|
+
selectedNodes.forEach((node) => nodesManager.removeNode(node.id));
|
|
1109
|
+
selectedEdges.forEach((edge) => edgesManager.removeEdge(edge.id));
|
|
1110
|
+
emit("update:nodes", nodesRef.value);
|
|
1111
|
+
emit("update:edges", edgesRef.value);
|
|
1112
|
+
}
|
|
968
1113
|
},
|
|
969
1114
|
onUndo: () => historyManager.undo(),
|
|
970
1115
|
onRedo: () => historyManager.redo(),
|
|
@@ -983,6 +1128,10 @@ onMounted(() => {
|
|
|
983
1128
|
}
|
|
984
1129
|
});
|
|
985
1130
|
onBeforeUnmount(() => {
|
|
1131
|
+
if (resizeObserver) {
|
|
1132
|
+
resizeObserver.disconnect();
|
|
1133
|
+
resizeObserver = null;
|
|
1134
|
+
}
|
|
986
1135
|
if (props.keyboardShortcuts && handleKeyDown) {
|
|
987
1136
|
document.removeEventListener("keydown", handleKeyDown);
|
|
988
1137
|
}
|
|
@@ -19,7 +19,9 @@ function useKeyboard(options = {}) {
|
|
|
19
19
|
if (!enabled) return;
|
|
20
20
|
const key = event.key;
|
|
21
21
|
const ctrl = event.ctrlKey || event.metaKey;
|
|
22
|
-
|
|
22
|
+
const target = event.target;
|
|
23
|
+
const isEditable = target && (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "SELECT" || target.isContentEditable === true);
|
|
24
|
+
if ((key === "Delete" || key === "Backspace") && !isEditable) {
|
|
23
25
|
event.preventDefault();
|
|
24
26
|
onDelete?.();
|
|
25
27
|
}
|
|
@@ -13,7 +13,9 @@ export function useKeyboard(options = {}) {
|
|
|
13
13
|
if (!enabled) return;
|
|
14
14
|
const key = event.key;
|
|
15
15
|
const ctrl = event.ctrlKey || event.metaKey;
|
|
16
|
-
|
|
16
|
+
const target = event.target;
|
|
17
|
+
const isEditable = target && (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "SELECT" || target.isContentEditable === true);
|
|
18
|
+
if ((key === "Delete" || key === "Backspace") && !isEditable) {
|
|
17
19
|
event.preventDefault();
|
|
18
20
|
onDelete?.();
|
|
19
21
|
}
|
|
@@ -20,7 +20,15 @@ const defaultOptions = {
|
|
|
20
20
|
workerUrl: ""
|
|
21
21
|
};
|
|
22
22
|
async function applyDagreLayout(nodes, edges, options) {
|
|
23
|
-
|
|
23
|
+
let dagreLib;
|
|
24
|
+
try {
|
|
25
|
+
dagreLib = await Promise.resolve().then(() => require("dagre"));
|
|
26
|
+
} catch {
|
|
27
|
+
throw new Error('[YH-UI Flow] Layout engine "dagre" is not installed. Please install "dagre" to use the dagre layout algorithm.');
|
|
28
|
+
}
|
|
29
|
+
if (!dagreLib) {
|
|
30
|
+
throw new Error('[YH-UI Flow] Layout engine "dagre" is not installed. Please install "dagre" to use the dagre layout algorithm.');
|
|
31
|
+
}
|
|
24
32
|
const dagre = dagreLib.default || dagreLib;
|
|
25
33
|
const graphlib = dagreLib.graphlib || dagre.graphlib;
|
|
26
34
|
const g = new graphlib.Graph();
|
|
@@ -63,11 +71,18 @@ async function applyElkLayout(nodes, edges, options) {
|
|
|
63
71
|
const elkPath = "elkjs";
|
|
64
72
|
let elkLib;
|
|
65
73
|
try {
|
|
66
|
-
|
|
67
|
-
|
|
74
|
+
try {
|
|
75
|
+
elkLib = await Promise.resolve(`${/* @vite-ignore */
|
|
76
|
+
bundledPath}`).then(s => require(s));
|
|
77
|
+
} catch {
|
|
78
|
+
elkLib = await Promise.resolve(`${/* @vite-ignore */
|
|
79
|
+
elkPath}`).then(s => require(s));
|
|
80
|
+
}
|
|
68
81
|
} catch {
|
|
69
|
-
|
|
70
|
-
|
|
82
|
+
throw new Error('[YH-UI Flow] Layout engine "elkjs" is not installed. Please install "elkjs" to use the elk layout algorithm.');
|
|
83
|
+
}
|
|
84
|
+
if (!elkLib) {
|
|
85
|
+
throw new Error('[YH-UI Flow] Layout engine "elkjs" is not installed. Please install "elkjs" to use the elk layout algorithm.');
|
|
71
86
|
}
|
|
72
87
|
const ELK = elkLib.default || elkLib;
|
|
73
88
|
const elk = new ELK();
|
|
@@ -116,8 +131,16 @@ async function applyElkLayout(nodes, edges, options) {
|
|
|
116
131
|
}
|
|
117
132
|
async function applyForceLayout(nodes, edges, options, flowInstance) {
|
|
118
133
|
const d3ForcePath = "d3-force";
|
|
119
|
-
|
|
120
|
-
|
|
134
|
+
let d3ForceLib;
|
|
135
|
+
try {
|
|
136
|
+
d3ForceLib = await Promise.resolve(`${/* @vite-ignore */
|
|
137
|
+
d3ForcePath}`).then(s => require(s));
|
|
138
|
+
} catch {
|
|
139
|
+
throw new Error('[YH-UI Flow] Layout engine "d3-force" is not installed. Please install "d3-force" to use the force layout algorithm.');
|
|
140
|
+
}
|
|
141
|
+
if (!d3ForceLib) {
|
|
142
|
+
throw new Error('[YH-UI Flow] Layout engine "d3-force" is not installed. Please install "d3-force" to use the force layout algorithm.');
|
|
143
|
+
}
|
|
121
144
|
const d3Force = d3ForceLib.default || d3ForceLib;
|
|
122
145
|
const forceNodes = nodes.map(node => ({
|
|
123
146
|
id: node.id,
|
|
@@ -12,7 +12,22 @@ const defaultOptions = {
|
|
|
12
12
|
workerUrl: ""
|
|
13
13
|
};
|
|
14
14
|
async function applyDagreLayout(nodes, edges, options) {
|
|
15
|
-
|
|
15
|
+
let dagreLib;
|
|
16
|
+
try {
|
|
17
|
+
dagreLib = await import(
|
|
18
|
+
/* @vite-ignore */
|
|
19
|
+
"dagre"
|
|
20
|
+
);
|
|
21
|
+
} catch {
|
|
22
|
+
throw new Error(
|
|
23
|
+
'[YH-UI Flow] Layout engine "dagre" is not installed. Please install "dagre" to use the dagre layout algorithm.'
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
if (!dagreLib) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
'[YH-UI Flow] Layout engine "dagre" is not installed. Please install "dagre" to use the dagre layout algorithm.'
|
|
29
|
+
);
|
|
30
|
+
}
|
|
16
31
|
const dagre = dagreLib.default || dagreLib;
|
|
17
32
|
const graphlib = dagreLib.graphlib || dagre.graphlib;
|
|
18
33
|
const g = new graphlib.Graph();
|
|
@@ -52,14 +67,25 @@ async function applyElkLayout(nodes, edges, options) {
|
|
|
52
67
|
const elkPath = "elkjs";
|
|
53
68
|
let elkLib;
|
|
54
69
|
try {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
70
|
+
try {
|
|
71
|
+
elkLib = await import(
|
|
72
|
+
/* @vite-ignore */
|
|
73
|
+
bundledPath
|
|
74
|
+
);
|
|
75
|
+
} catch {
|
|
76
|
+
elkLib = await import(
|
|
77
|
+
/* @vite-ignore */
|
|
78
|
+
elkPath
|
|
79
|
+
);
|
|
80
|
+
}
|
|
59
81
|
} catch {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
82
|
+
throw new Error(
|
|
83
|
+
'[YH-UI Flow] Layout engine "elkjs" is not installed. Please install "elkjs" to use the elk layout algorithm.'
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
if (!elkLib) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
'[YH-UI Flow] Layout engine "elkjs" is not installed. Please install "elkjs" to use the elk layout algorithm.'
|
|
63
89
|
);
|
|
64
90
|
}
|
|
65
91
|
const ELK = elkLib.default || elkLib;
|
|
@@ -106,10 +132,22 @@ async function applyElkLayout(nodes, edges, options) {
|
|
|
106
132
|
}
|
|
107
133
|
async function applyForceLayout(nodes, edges, options, flowInstance) {
|
|
108
134
|
const d3ForcePath = "d3-force";
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
135
|
+
let d3ForceLib;
|
|
136
|
+
try {
|
|
137
|
+
d3ForceLib = await import(
|
|
138
|
+
/* @vite-ignore */
|
|
139
|
+
d3ForcePath
|
|
140
|
+
);
|
|
141
|
+
} catch {
|
|
142
|
+
throw new Error(
|
|
143
|
+
'[YH-UI Flow] Layout engine "d3-force" is not installed. Please install "d3-force" to use the force layout algorithm.'
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
if (!d3ForceLib) {
|
|
147
|
+
throw new Error(
|
|
148
|
+
'[YH-UI Flow] Layout engine "d3-force" is not installed. Please install "d3-force" to use the force layout algorithm.'
|
|
149
|
+
);
|
|
150
|
+
}
|
|
113
151
|
const d3Force = d3ForceLib.default || d3ForceLib;
|
|
114
152
|
const forceNodes = nodes.map((node) => ({
|
|
115
153
|
id: node.id,
|
|
@@ -14,7 +14,11 @@
|
|
|
14
14
|
>
|
|
15
15
|
<defs>
|
|
16
16
|
<!-- Dynamic masks to create a true gap in the line behind the label -->
|
|
17
|
-
<mask
|
|
17
|
+
<mask
|
|
18
|
+
v-for="ed in edgeData.filter(e => e.edge.label)"
|
|
19
|
+
:key="`mask-${ed.edge.id}`"
|
|
20
|
+
:id="getMaskId(ed.edge.id)"
|
|
21
|
+
>
|
|
18
22
|
<rect x="-5000" y="-5000" width="10000" height="10000" fill="white" />
|
|
19
23
|
<rect
|
|
20
24
|
:x="ed.labelX - ed.labelWidth / 2 - 4"
|
|
@@ -61,8 +65,28 @@
|
|
|
61
65
|
style="cursor: pointer; pointer-events: all"
|
|
62
66
|
/>
|
|
63
67
|
|
|
64
|
-
<!-- Visible Path -->
|
|
68
|
+
<!-- Visible Path with Mask (when label exists) -->
|
|
65
69
|
<path
|
|
70
|
+
v-if="ed.edge.label"
|
|
71
|
+
:d="ed.path"
|
|
72
|
+
:stroke="ed.stroke"
|
|
73
|
+
:stroke-width="ed.strokeWidth"
|
|
74
|
+
fill="none"
|
|
75
|
+
:class="{
|
|
76
|
+
'yh-flow-edge-path': true,
|
|
77
|
+
'is-animated': ed.edge.animated
|
|
78
|
+
}"
|
|
79
|
+
:mask="`url(#${getMaskId(ed.edge.id)})`"
|
|
80
|
+
:style="{
|
|
81
|
+
pointerEvents: 'none',
|
|
82
|
+
transition: 'stroke 0.2s, stroke-width 0.2s',
|
|
83
|
+
stroke: ed.stroke
|
|
84
|
+
}"
|
|
85
|
+
/>
|
|
86
|
+
|
|
87
|
+
<!-- Visible Path without Mask (when no label exists) -->
|
|
88
|
+
<path
|
|
89
|
+
v-else
|
|
66
90
|
:d="ed.path"
|
|
67
91
|
:stroke="ed.stroke"
|
|
68
92
|
:stroke-width="ed.strokeWidth"
|
|
@@ -71,7 +95,6 @@
|
|
|
71
95
|
'yh-flow-edge-path': true,
|
|
72
96
|
'is-animated': ed.edge.animated
|
|
73
97
|
}"
|
|
74
|
-
:mask="ed.edge.label ? `url(#${getMaskId(ed.edge.id)})` : void 0"
|
|
75
98
|
:style="{
|
|
76
99
|
pointerEvents: 'none',
|
|
77
100
|
transition: 'stroke 0.2s, stroke-width 0.2s',
|
|
@@ -151,6 +174,12 @@ const getLabelStyle = (edge) => {
|
|
|
151
174
|
return styles;
|
|
152
175
|
};
|
|
153
176
|
const edgeData = computed(() => {
|
|
177
|
+
console.log(
|
|
178
|
+
"EdgeRenderer computed edgeData running. Nodes count:",
|
|
179
|
+
props.nodes.length,
|
|
180
|
+
"Node 1 pos:",
|
|
181
|
+
props.nodes.find((n) => n.id === "1")?.position
|
|
182
|
+
);
|
|
154
183
|
const result = [];
|
|
155
184
|
for (const edge of props.edges) {
|
|
156
185
|
if (!edge || edge.hidden) continue;
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
</template>
|
|
65
65
|
|
|
66
66
|
<script setup>
|
|
67
|
-
import { computed, ref, onMounted, onBeforeUnmount } from "vue";
|
|
67
|
+
import { computed, ref, onMounted, onBeforeUnmount, watch } from "vue";
|
|
68
68
|
import {
|
|
69
69
|
getCustomNodeTemplate,
|
|
70
70
|
getCustomNode,
|
|
@@ -373,6 +373,30 @@ onBeforeUnmount(() => {
|
|
|
373
373
|
}
|
|
374
374
|
nodeElements.clear();
|
|
375
375
|
});
|
|
376
|
+
watch(
|
|
377
|
+
() => props.nodes,
|
|
378
|
+
(newNodes) => {
|
|
379
|
+
let hasChanges = false;
|
|
380
|
+
newNodes.forEach((node) => {
|
|
381
|
+
if (!node.measured) {
|
|
382
|
+
const el = nodeElements.get(node.id);
|
|
383
|
+
if (el) {
|
|
384
|
+
const rect = el.getBoundingClientRect();
|
|
385
|
+
const width = Math.round(rect.width);
|
|
386
|
+
const height = Math.round(rect.height);
|
|
387
|
+
if (width > 0 && height > 0) {
|
|
388
|
+
node.measured = { width, height };
|
|
389
|
+
hasChanges = true;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
if (hasChanges) {
|
|
395
|
+
emit("nodes-measured");
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
{ flush: "post" }
|
|
399
|
+
);
|
|
376
400
|
const setNodeRef = (el, nodeId) => {
|
|
377
401
|
if (el) {
|
|
378
402
|
nodeElements.set(nodeId, el);
|
package/dist/utils/edge.cjs
CHANGED
|
@@ -8,6 +8,7 @@ exports.getEdgeCenter = getEdgeCenter;
|
|
|
8
8
|
exports.getEdgePath = getEdgePath;
|
|
9
9
|
exports.getEdgePosition = getEdgePosition;
|
|
10
10
|
exports.getHandlePosition = getHandlePosition;
|
|
11
|
+
exports.getSelfLoopPath = getSelfLoopPath;
|
|
11
12
|
exports.getSmoothStepPath = getSmoothStepPath;
|
|
12
13
|
exports.getStepPath = getStepPath;
|
|
13
14
|
exports.getStraightPath = getStraightPath;
|
|
@@ -51,6 +52,44 @@ function getHandlePosition(node, handlePosition = "right", _handleId) {
|
|
|
51
52
|
}
|
|
52
53
|
width = width || 150;
|
|
53
54
|
height = height || 40;
|
|
55
|
+
if (node.handleBounds && node.handleBounds[handlePosition]) {
|
|
56
|
+
const handles = node.handleBounds[handlePosition] || [];
|
|
57
|
+
if (handles.length > 0) {
|
|
58
|
+
let handleIndex = -1;
|
|
59
|
+
if (_handleId) {
|
|
60
|
+
handleIndex = handles.findIndex(h => h.id === _handleId);
|
|
61
|
+
}
|
|
62
|
+
if (handleIndex === -1) {
|
|
63
|
+
handleIndex = 0;
|
|
64
|
+
}
|
|
65
|
+
const handle = handles[handleIndex];
|
|
66
|
+
if (handle) {
|
|
67
|
+
if (handle.x !== void 0 && handle.y !== void 0) {
|
|
68
|
+
return {
|
|
69
|
+
x: x + handle.x,
|
|
70
|
+
y: y + handle.y
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const N = handles.length;
|
|
74
|
+
const i = handleIndex;
|
|
75
|
+
if (handlePosition === "left" || handlePosition === "right") {
|
|
76
|
+
const hX = handlePosition === "left" ? 0 : width;
|
|
77
|
+
const hY = height / (N + 1) * (i + 1);
|
|
78
|
+
return {
|
|
79
|
+
x: x + hX,
|
|
80
|
+
y: y + hY
|
|
81
|
+
};
|
|
82
|
+
} else {
|
|
83
|
+
const hX = width / (N + 1) * (i + 1);
|
|
84
|
+
const hY = handlePosition === "top" ? 0 : height;
|
|
85
|
+
return {
|
|
86
|
+
x: x + hX,
|
|
87
|
+
y: y + hY
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
54
93
|
switch (handlePosition) {
|
|
55
94
|
case "top":
|
|
56
95
|
return {
|
|
@@ -170,7 +209,37 @@ function getSmoothStepPath(params) {
|
|
|
170
209
|
return [`M${sourceX},${sourceY}`, `L${sourceX},${midY - sign1Y * r}`, `Q${sourceX},${midY} ${sourceX + sign2X * r},${midY}`, `L${targetX - sign2X * r},${midY}`, `Q${targetX},${midY} ${targetX},${midY + sign1Y * r}`, `L${targetX},${targetY}`].join(" ");
|
|
171
210
|
}
|
|
172
211
|
}
|
|
212
|
+
function getSelfLoopPath(sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, nodeWidth, nodeHeight) {
|
|
213
|
+
const w = nodeWidth ?? 150;
|
|
214
|
+
const h = nodeHeight ?? 50;
|
|
215
|
+
const dx = targetX - sourceX;
|
|
216
|
+
const dy = targetY - sourceY;
|
|
217
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
218
|
+
if (dist < 5) {
|
|
219
|
+
const loopSize = 40;
|
|
220
|
+
if (sourcePosition === "right") {
|
|
221
|
+
return `M${sourceX},${sourceY} C${sourceX + loopSize},${sourceY - loopSize} ${sourceX + loopSize},${sourceY + loopSize} ${sourceX},${sourceY}`;
|
|
222
|
+
} else if (sourcePosition === "left") {
|
|
223
|
+
return `M${sourceX},${sourceY} C${sourceX - loopSize},${sourceY - loopSize} ${sourceX - loopSize},${sourceY + loopSize} ${sourceX},${sourceY}`;
|
|
224
|
+
} else if (sourcePosition === "top") {
|
|
225
|
+
return `M${sourceX},${sourceY} C${sourceX - loopSize},${sourceY - loopSize} ${sourceX + loopSize},${sourceY - loopSize} ${sourceX},${sourceY}`;
|
|
226
|
+
} else {
|
|
227
|
+
return `M${sourceX},${sourceY} C${sourceX - loopSize},${sourceY + loopSize} ${sourceX + loopSize},${sourceY + loopSize} ${sourceX},${sourceY}`;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const offset = Math.max(w, h, 60) * 0.5;
|
|
231
|
+
let c1x = sourceX;
|
|
232
|
+
let c1y = sourceY;
|
|
233
|
+
if (sourcePosition === "right") c1x += offset;else if (sourcePosition === "left") c1x -= offset;else if (sourcePosition === "top") c1y -= offset;else if (sourcePosition === "bottom") c1y += offset;
|
|
234
|
+
let c2x = targetX;
|
|
235
|
+
let c2y = targetY;
|
|
236
|
+
if (targetPosition === "right") c2x += offset;else if (targetPosition === "left") c2x -= offset;else if (targetPosition === "top") c2y -= offset;else if (targetPosition === "bottom") c2y += offset;
|
|
237
|
+
return `M${sourceX},${sourceY} C${c1x},${c1y} ${c2x},${c2y} ${targetX},${targetY}`;
|
|
238
|
+
}
|
|
173
239
|
function getEdgePath(type, params) {
|
|
240
|
+
if (params.isSelfLoop) {
|
|
241
|
+
return getSelfLoopPath(params.sourceX, params.sourceY, params.targetX, params.targetY, params.sourcePosition, params.targetPosition, params.nodeWidth, params.nodeHeight);
|
|
242
|
+
}
|
|
174
243
|
switch (type) {
|
|
175
244
|
case "bezier":
|
|
176
245
|
case "default":
|
|
@@ -191,8 +260,58 @@ function getEdgeCenter(params) {
|
|
|
191
260
|
sourceY,
|
|
192
261
|
targetX,
|
|
193
262
|
targetY,
|
|
194
|
-
type = "bezier"
|
|
263
|
+
type = "bezier",
|
|
264
|
+
isSelfLoop,
|
|
265
|
+
nodeWidth,
|
|
266
|
+
nodeHeight,
|
|
267
|
+
sourcePosition,
|
|
268
|
+
targetPosition
|
|
195
269
|
} = params;
|
|
270
|
+
if (isSelfLoop) {
|
|
271
|
+
const w = nodeWidth ?? 150;
|
|
272
|
+
const h = nodeHeight ?? 50;
|
|
273
|
+
const dx = targetX - sourceX;
|
|
274
|
+
const dy = targetY - sourceY;
|
|
275
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
276
|
+
let c1x = sourceX,
|
|
277
|
+
c1y = sourceY,
|
|
278
|
+
c2x = targetX,
|
|
279
|
+
c2y = targetY;
|
|
280
|
+
if (dist < 5) {
|
|
281
|
+
const loopSize = 40;
|
|
282
|
+
if (sourcePosition === "right") {
|
|
283
|
+
c1x = sourceX + loopSize;
|
|
284
|
+
c1y = sourceY - loopSize;
|
|
285
|
+
c2x = sourceX + loopSize;
|
|
286
|
+
c2y = sourceY + loopSize;
|
|
287
|
+
} else if (sourcePosition === "left") {
|
|
288
|
+
c1x = sourceX - loopSize;
|
|
289
|
+
c1y = sourceY - loopSize;
|
|
290
|
+
c2x = sourceX - loopSize;
|
|
291
|
+
c2y = sourceY + loopSize;
|
|
292
|
+
} else if (sourcePosition === "top") {
|
|
293
|
+
c1x = sourceX - loopSize;
|
|
294
|
+
c1y = sourceY - loopSize;
|
|
295
|
+
c2x = sourceX + loopSize;
|
|
296
|
+
c2y = sourceY - loopSize;
|
|
297
|
+
} else {
|
|
298
|
+
c1x = sourceX - loopSize;
|
|
299
|
+
c1y = sourceY + loopSize;
|
|
300
|
+
c2x = sourceX + loopSize;
|
|
301
|
+
c2y = sourceY + loopSize;
|
|
302
|
+
}
|
|
303
|
+
} else {
|
|
304
|
+
const offset = Math.max(w, h, 60) * 0.5;
|
|
305
|
+
if (sourcePosition === "right") c1x += offset;else if (sourcePosition === "left") c1x -= offset;else if (sourcePosition === "top") c1y -= offset;else if (sourcePosition === "bottom") c1y += offset;
|
|
306
|
+
if (targetPosition === "right") c2x += offset;else if (targetPosition === "left") c2x -= offset;else if (targetPosition === "top") c2y -= offset;else if (targetPosition === "bottom") c2y += offset;
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
x: 0.125 * sourceX + 0.375 * c1x + 0.375 * c2x + 0.125 * targetX,
|
|
310
|
+
y: 0.125 * sourceY + 0.375 * c1y + 0.375 * c2y + 0.125 * targetY,
|
|
311
|
+
ox: 0,
|
|
312
|
+
oy: 0
|
|
313
|
+
};
|
|
314
|
+
}
|
|
196
315
|
if (type === "bezier" || type === "default") {
|
|
197
316
|
const curvature = params.curvature ?? 0.25;
|
|
198
317
|
const srcDir = getDir(params.sourcePosition);
|
package/dist/utils/edge.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Position, EdgeType, NodeStyle } from '../types';
|
|
1
|
+
import type { Position, EdgeType, NodeStyle, NodeHandle } from '../types';
|
|
2
2
|
export interface EdgePathParams {
|
|
3
3
|
sourceX: number;
|
|
4
4
|
sourceY: number;
|
|
@@ -8,6 +8,9 @@ export interface EdgePathParams {
|
|
|
8
8
|
targetPosition: Position;
|
|
9
9
|
/** 控制曲率,0~1,默认 0.25 */
|
|
10
10
|
curvature?: number;
|
|
11
|
+
isSelfLoop?: boolean;
|
|
12
|
+
nodeWidth?: number;
|
|
13
|
+
nodeHeight?: number;
|
|
11
14
|
}
|
|
12
15
|
/**
|
|
13
16
|
* 获取连接点的位置坐标
|
|
@@ -24,6 +27,12 @@ export declare function getHandlePosition(node: {
|
|
|
24
27
|
width: number;
|
|
25
28
|
height: number;
|
|
26
29
|
};
|
|
30
|
+
handleBounds?: {
|
|
31
|
+
top?: NodeHandle[];
|
|
32
|
+
right?: NodeHandle[];
|
|
33
|
+
bottom?: NodeHandle[];
|
|
34
|
+
left?: NodeHandle[];
|
|
35
|
+
};
|
|
27
36
|
}, handlePosition?: Position, _handleId?: string | null): {
|
|
28
37
|
x: number;
|
|
29
38
|
y: number;
|
|
@@ -54,6 +63,10 @@ export declare function getStepPath(params: EdgePathParams): string;
|
|
|
54
63
|
* 生成平滑阶梯线路径(带圆角转折的 step)
|
|
55
64
|
*/
|
|
56
65
|
export declare function getSmoothStepPath(params: EdgePathParams): string;
|
|
66
|
+
/**
|
|
67
|
+
* 生成自环曲线路径
|
|
68
|
+
*/
|
|
69
|
+
export declare function getSelfLoopPath(sourceX: number, sourceY: number, targetX: number, targetY: number, sourcePosition: Position, targetPosition: Position, nodeWidth?: number, nodeHeight?: number): string;
|
|
57
70
|
/**
|
|
58
71
|
* 根据类型获取连线路径
|
|
59
72
|
*/
|
package/dist/utils/edge.mjs
CHANGED
|
@@ -25,6 +25,35 @@ export function getHandlePosition(node, handlePosition = "right", _handleId) {
|
|
|
25
25
|
}
|
|
26
26
|
width = width || 150;
|
|
27
27
|
height = height || 40;
|
|
28
|
+
if (node.handleBounds && node.handleBounds[handlePosition]) {
|
|
29
|
+
const handles = node.handleBounds[handlePosition] || [];
|
|
30
|
+
if (handles.length > 0) {
|
|
31
|
+
let handleIndex = -1;
|
|
32
|
+
if (_handleId) {
|
|
33
|
+
handleIndex = handles.findIndex((h) => h.id === _handleId);
|
|
34
|
+
}
|
|
35
|
+
if (handleIndex === -1) {
|
|
36
|
+
handleIndex = 0;
|
|
37
|
+
}
|
|
38
|
+
const handle = handles[handleIndex];
|
|
39
|
+
if (handle) {
|
|
40
|
+
if (handle.x !== void 0 && handle.y !== void 0) {
|
|
41
|
+
return { x: x + handle.x, y: y + handle.y };
|
|
42
|
+
}
|
|
43
|
+
const N = handles.length;
|
|
44
|
+
const i = handleIndex;
|
|
45
|
+
if (handlePosition === "left" || handlePosition === "right") {
|
|
46
|
+
const hX = handlePosition === "left" ? 0 : width;
|
|
47
|
+
const hY = height / (N + 1) * (i + 1);
|
|
48
|
+
return { x: x + hX, y: y + hY };
|
|
49
|
+
} else {
|
|
50
|
+
const hX = width / (N + 1) * (i + 1);
|
|
51
|
+
const hY = handlePosition === "top" ? 0 : height;
|
|
52
|
+
return { x: x + hX, y: y + hY };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
28
57
|
switch (handlePosition) {
|
|
29
58
|
case "top":
|
|
30
59
|
return { x: x + width / 2, y };
|
|
@@ -119,7 +148,52 @@ export function getSmoothStepPath(params) {
|
|
|
119
148
|
].join(" ");
|
|
120
149
|
}
|
|
121
150
|
}
|
|
151
|
+
export function getSelfLoopPath(sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, nodeWidth, nodeHeight) {
|
|
152
|
+
const w = nodeWidth ?? 150;
|
|
153
|
+
const h = nodeHeight ?? 50;
|
|
154
|
+
const dx = targetX - sourceX;
|
|
155
|
+
const dy = targetY - sourceY;
|
|
156
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
157
|
+
if (dist < 5) {
|
|
158
|
+
const loopSize = 40;
|
|
159
|
+
if (sourcePosition === "right") {
|
|
160
|
+
return `M${sourceX},${sourceY} C${sourceX + loopSize},${sourceY - loopSize} ${sourceX + loopSize},${sourceY + loopSize} ${sourceX},${sourceY}`;
|
|
161
|
+
} else if (sourcePosition === "left") {
|
|
162
|
+
return `M${sourceX},${sourceY} C${sourceX - loopSize},${sourceY - loopSize} ${sourceX - loopSize},${sourceY + loopSize} ${sourceX},${sourceY}`;
|
|
163
|
+
} else if (sourcePosition === "top") {
|
|
164
|
+
return `M${sourceX},${sourceY} C${sourceX - loopSize},${sourceY - loopSize} ${sourceX + loopSize},${sourceY - loopSize} ${sourceX},${sourceY}`;
|
|
165
|
+
} else {
|
|
166
|
+
return `M${sourceX},${sourceY} C${sourceX - loopSize},${sourceY + loopSize} ${sourceX + loopSize},${sourceY + loopSize} ${sourceX},${sourceY}`;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const offset = Math.max(w, h, 60) * 0.5;
|
|
170
|
+
let c1x = sourceX;
|
|
171
|
+
let c1y = sourceY;
|
|
172
|
+
if (sourcePosition === "right") c1x += offset;
|
|
173
|
+
else if (sourcePosition === "left") c1x -= offset;
|
|
174
|
+
else if (sourcePosition === "top") c1y -= offset;
|
|
175
|
+
else if (sourcePosition === "bottom") c1y += offset;
|
|
176
|
+
let c2x = targetX;
|
|
177
|
+
let c2y = targetY;
|
|
178
|
+
if (targetPosition === "right") c2x += offset;
|
|
179
|
+
else if (targetPosition === "left") c2x -= offset;
|
|
180
|
+
else if (targetPosition === "top") c2y -= offset;
|
|
181
|
+
else if (targetPosition === "bottom") c2y += offset;
|
|
182
|
+
return `M${sourceX},${sourceY} C${c1x},${c1y} ${c2x},${c2y} ${targetX},${targetY}`;
|
|
183
|
+
}
|
|
122
184
|
export function getEdgePath(type, params) {
|
|
185
|
+
if (params.isSelfLoop) {
|
|
186
|
+
return getSelfLoopPath(
|
|
187
|
+
params.sourceX,
|
|
188
|
+
params.sourceY,
|
|
189
|
+
params.targetX,
|
|
190
|
+
params.targetY,
|
|
191
|
+
params.sourcePosition,
|
|
192
|
+
params.targetPosition,
|
|
193
|
+
params.nodeWidth,
|
|
194
|
+
params.nodeHeight
|
|
195
|
+
);
|
|
196
|
+
}
|
|
123
197
|
switch (type) {
|
|
124
198
|
case "bezier":
|
|
125
199
|
case "default":
|
|
@@ -135,7 +209,66 @@ export function getEdgePath(type, params) {
|
|
|
135
209
|
}
|
|
136
210
|
}
|
|
137
211
|
export function getEdgeCenter(params) {
|
|
138
|
-
const {
|
|
212
|
+
const {
|
|
213
|
+
sourceX,
|
|
214
|
+
sourceY,
|
|
215
|
+
targetX,
|
|
216
|
+
targetY,
|
|
217
|
+
type = "bezier",
|
|
218
|
+
isSelfLoop,
|
|
219
|
+
nodeWidth,
|
|
220
|
+
nodeHeight,
|
|
221
|
+
sourcePosition,
|
|
222
|
+
targetPosition
|
|
223
|
+
} = params;
|
|
224
|
+
if (isSelfLoop) {
|
|
225
|
+
const w = nodeWidth ?? 150;
|
|
226
|
+
const h = nodeHeight ?? 50;
|
|
227
|
+
const dx = targetX - sourceX;
|
|
228
|
+
const dy = targetY - sourceY;
|
|
229
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
230
|
+
let c1x = sourceX, c1y = sourceY, c2x = targetX, c2y = targetY;
|
|
231
|
+
if (dist < 5) {
|
|
232
|
+
const loopSize = 40;
|
|
233
|
+
if (sourcePosition === "right") {
|
|
234
|
+
c1x = sourceX + loopSize;
|
|
235
|
+
c1y = sourceY - loopSize;
|
|
236
|
+
c2x = sourceX + loopSize;
|
|
237
|
+
c2y = sourceY + loopSize;
|
|
238
|
+
} else if (sourcePosition === "left") {
|
|
239
|
+
c1x = sourceX - loopSize;
|
|
240
|
+
c1y = sourceY - loopSize;
|
|
241
|
+
c2x = sourceX - loopSize;
|
|
242
|
+
c2y = sourceY + loopSize;
|
|
243
|
+
} else if (sourcePosition === "top") {
|
|
244
|
+
c1x = sourceX - loopSize;
|
|
245
|
+
c1y = sourceY - loopSize;
|
|
246
|
+
c2x = sourceX + loopSize;
|
|
247
|
+
c2y = sourceY - loopSize;
|
|
248
|
+
} else {
|
|
249
|
+
c1x = sourceX - loopSize;
|
|
250
|
+
c1y = sourceY + loopSize;
|
|
251
|
+
c2x = sourceX + loopSize;
|
|
252
|
+
c2y = sourceY + loopSize;
|
|
253
|
+
}
|
|
254
|
+
} else {
|
|
255
|
+
const offset = Math.max(w, h, 60) * 0.5;
|
|
256
|
+
if (sourcePosition === "right") c1x += offset;
|
|
257
|
+
else if (sourcePosition === "left") c1x -= offset;
|
|
258
|
+
else if (sourcePosition === "top") c1y -= offset;
|
|
259
|
+
else if (sourcePosition === "bottom") c1y += offset;
|
|
260
|
+
if (targetPosition === "right") c2x += offset;
|
|
261
|
+
else if (targetPosition === "left") c2x -= offset;
|
|
262
|
+
else if (targetPosition === "top") c2y -= offset;
|
|
263
|
+
else if (targetPosition === "bottom") c2y += offset;
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
x: 0.125 * sourceX + 0.375 * c1x + 0.375 * c2x + 0.125 * targetX,
|
|
267
|
+
y: 0.125 * sourceY + 0.375 * c1y + 0.375 * c2y + 0.125 * targetY,
|
|
268
|
+
ox: 0,
|
|
269
|
+
oy: 0
|
|
270
|
+
};
|
|
271
|
+
}
|
|
139
272
|
if (type === "bezier" || type === "default") {
|
|
140
273
|
const curvature = params.curvature ?? 0.25;
|
|
141
274
|
const srcDir = getDir(params.sourcePosition);
|
|
@@ -56,14 +56,7 @@ function isValidConnection(sourceNode, targetNode, connection) {
|
|
|
56
56
|
}
|
|
57
57
|
if (connection.source === connection.target) {
|
|
58
58
|
return {
|
|
59
|
-
isValid:
|
|
60
|
-
message: "Cannot connect to the same node"
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
if (connection.target === connection.source) {
|
|
64
|
-
return {
|
|
65
|
-
isValid: false,
|
|
66
|
-
message: "Cannot create self-loop"
|
|
59
|
+
isValid: true
|
|
67
60
|
};
|
|
68
61
|
}
|
|
69
62
|
return {
|
|
@@ -41,10 +41,7 @@ export function isValidConnection(sourceNode, targetNode, connection) {
|
|
|
41
41
|
return { isValid: false, message: "Target node not found" };
|
|
42
42
|
}
|
|
43
43
|
if (connection.source === connection.target) {
|
|
44
|
-
return { isValid:
|
|
45
|
-
}
|
|
46
|
-
if (connection.target === connection.source) {
|
|
47
|
-
return { isValid: false, message: "Cannot create self-loop" };
|
|
44
|
+
return { isValid: true };
|
|
48
45
|
}
|
|
49
46
|
return { isValid: true };
|
|
50
47
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yh-ui/flow",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.54",
|
|
4
4
|
"description": "YH-UI High-performance Flow Chart Component",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"lint": "eslint ."
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@yh-ui/utils": "^1.0.
|
|
36
|
-
"@yh-ui/hooks": "^1.0.
|
|
35
|
+
"@yh-ui/utils": "^1.0.54",
|
|
36
|
+
"@yh-ui/hooks": "^1.0.54"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"vue": "^3.5.35",
|
|
@@ -44,7 +44,8 @@
|
|
|
44
44
|
"vue": "^3.5.35",
|
|
45
45
|
"dagre": ">=0.8.5",
|
|
46
46
|
"elkjs": ">=0.9.0",
|
|
47
|
-
"d3-force": ">=3.0.0"
|
|
47
|
+
"d3-force": ">=3.0.0",
|
|
48
|
+
"html-to-image": ">=1.11.0"
|
|
48
49
|
},
|
|
49
50
|
"peerDependenciesMeta": {
|
|
50
51
|
"dagre": {
|
|
@@ -55,6 +56,9 @@
|
|
|
55
56
|
},
|
|
56
57
|
"d3-force": {
|
|
57
58
|
"optional": true
|
|
59
|
+
},
|
|
60
|
+
"html-to-image": {
|
|
61
|
+
"optional": true
|
|
58
62
|
}
|
|
59
63
|
},
|
|
60
64
|
"publishConfig": {
|