@yh-ui/flow 1.0.50 → 1.0.52
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.d.vue.ts +25 -2
- package/dist/Flow.vue +47 -12
- package/dist/Flow.vue.d.ts +25 -2
- package/dist/core/useAlignment.cjs +32 -12
- package/dist/core/useAlignment.mjs +49 -19
- package/dist/core/useNodeDistribution.cjs +16 -16
- package/dist/core/useNodeDistribution.mjs +30 -16
- package/dist/core/useSelection.cjs +6 -3
- package/dist/core/useSelection.mjs +6 -3
- package/dist/core/useViewport.cjs +4 -4
- package/dist/core/useViewport.d.ts +8 -0
- package/dist/core/useViewport.mjs +4 -4
- package/dist/plugins/plugins/layout.cjs +6 -6
- package/dist/plugins/plugins/layout.mjs +6 -6
- package/dist/plugins/plugins/node-group.cjs +10 -2
- package/dist/plugins/plugins/node-group.mjs +10 -2
- package/dist/renderer/AlignmentLines.vue +26 -16
- package/dist/renderer/EdgeHandlesRenderer.vue +11 -2
- package/dist/renderer/EdgeRenderer.vue +5 -3
- package/dist/renderer/Minimap.vue +28 -18
- package/dist/renderer/NodeRenderer.d.vue.ts +2 -0
- package/dist/renderer/NodeRenderer.vue +74 -13
- package/dist/renderer/NodeRenderer.vue.d.ts +2 -0
- package/dist/types/index.d.ts +27 -0
- package/dist/utils/custom-types.cjs +21 -0
- package/dist/utils/custom-types.d.ts +25 -0
- package/dist/utils/custom-types.mjs +17 -0
- package/dist/utils/edge.cjs +2 -2
- package/dist/utils/edge.d.ts +4 -0
- package/dist/utils/edge.mjs +2 -2
- package/package.json +18 -4
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ref, computed } from "vue";
|
|
2
|
+
import { getNodeAbsolutePosition } from "../utils/custom-types.mjs";
|
|
2
3
|
export function useSelection(options) {
|
|
3
4
|
const { nodes, edges, onSelectionChange } = options;
|
|
4
5
|
const selectionRect = ref(null);
|
|
@@ -23,10 +24,12 @@ export function useSelection(options) {
|
|
|
23
24
|
width: maxX - minX,
|
|
24
25
|
height: maxY - minY
|
|
25
26
|
};
|
|
27
|
+
const nodeMap = new Map(nodes.value.map((n) => [n.id, n]));
|
|
26
28
|
nodes.value = nodes.value.map((node) => {
|
|
27
|
-
const nodeWidth = node.width
|
|
28
|
-
const nodeHeight = node.height
|
|
29
|
-
const
|
|
29
|
+
const nodeWidth = node.measured?.width ?? node.width ?? 200;
|
|
30
|
+
const nodeHeight = node.measured?.height ?? node.height ?? 50;
|
|
31
|
+
const absPos = getNodeAbsolutePosition(node, nodeMap);
|
|
32
|
+
const isInside = absPos.x >= minX && absPos.y >= minY && absPos.x + nodeWidth <= maxX && absPos.y + nodeHeight <= maxY;
|
|
30
33
|
return { ...node, selected: isInside };
|
|
31
34
|
});
|
|
32
35
|
};
|
|
@@ -86,8 +86,8 @@ function useViewport(initialTransform, options = {}) {
|
|
|
86
86
|
let maxX = -Infinity;
|
|
87
87
|
let maxY = -Infinity;
|
|
88
88
|
for (const node of nodes) {
|
|
89
|
-
const w = node.width
|
|
90
|
-
const h = node.height
|
|
89
|
+
const w = node.measured?.width ?? node.width ?? 200;
|
|
90
|
+
const h = node.measured?.height ?? node.height ?? 50;
|
|
91
91
|
minX = Math.min(minX, node.position.x);
|
|
92
92
|
minY = Math.min(minY, node.position.y);
|
|
93
93
|
maxX = Math.max(maxX, node.position.x + w);
|
|
@@ -112,8 +112,8 @@ function useViewport(initialTransform, options = {}) {
|
|
|
112
112
|
let centerX = 0;
|
|
113
113
|
let centerY = 0;
|
|
114
114
|
for (const node of nodes) {
|
|
115
|
-
const w = node.width
|
|
116
|
-
const h = node.height
|
|
115
|
+
const w = node.measured?.width ?? node.width ?? 200;
|
|
116
|
+
const h = node.measured?.height ?? node.height ?? 50;
|
|
117
117
|
centerX += node.position.x + w / 2;
|
|
118
118
|
centerY += node.position.y + h / 2;
|
|
119
119
|
}
|
|
@@ -28,6 +28,10 @@ export interface UseViewportReturn {
|
|
|
28
28
|
};
|
|
29
29
|
width?: number;
|
|
30
30
|
height?: number;
|
|
31
|
+
measured?: {
|
|
32
|
+
width: number;
|
|
33
|
+
height: number;
|
|
34
|
+
};
|
|
31
35
|
}[], options?: {
|
|
32
36
|
padding?: number;
|
|
33
37
|
}) => void;
|
|
@@ -41,6 +45,10 @@ export interface UseViewportReturn {
|
|
|
41
45
|
};
|
|
42
46
|
width?: number;
|
|
43
47
|
height?: number;
|
|
48
|
+
measured?: {
|
|
49
|
+
width: number;
|
|
50
|
+
height: number;
|
|
51
|
+
};
|
|
44
52
|
}[], options?: {
|
|
45
53
|
padding?: number;
|
|
46
54
|
}) => void;
|
|
@@ -58,8 +58,8 @@ export function useViewport(initialTransform, options = {}) {
|
|
|
58
58
|
let maxX = -Infinity;
|
|
59
59
|
let maxY = -Infinity;
|
|
60
60
|
for (const node of nodes) {
|
|
61
|
-
const w = node.width
|
|
62
|
-
const h = node.height
|
|
61
|
+
const w = node.measured?.width ?? node.width ?? 200;
|
|
62
|
+
const h = node.measured?.height ?? node.height ?? 50;
|
|
63
63
|
minX = Math.min(minX, node.position.x);
|
|
64
64
|
minY = Math.min(minY, node.position.y);
|
|
65
65
|
maxX = Math.max(maxX, node.position.x + w);
|
|
@@ -80,8 +80,8 @@ export function useViewport(initialTransform, options = {}) {
|
|
|
80
80
|
let centerX = 0;
|
|
81
81
|
let centerY = 0;
|
|
82
82
|
for (const node of nodes) {
|
|
83
|
-
const w = node.width
|
|
84
|
-
const h = node.height
|
|
83
|
+
const w = node.measured?.width ?? node.width ?? 200;
|
|
84
|
+
const h = node.measured?.height ?? node.height ?? 50;
|
|
85
85
|
centerX += node.position.x + w / 2;
|
|
86
86
|
centerY += node.position.y + h / 2;
|
|
87
87
|
}
|
|
@@ -34,8 +34,8 @@ async function applyDagreLayout(nodes, edges, options) {
|
|
|
34
34
|
g.setDefaultEdgeLabel(() => ({}));
|
|
35
35
|
nodes.forEach(node => {
|
|
36
36
|
g.setNode(node.id, {
|
|
37
|
-
width: node.width
|
|
38
|
-
height: node.height
|
|
37
|
+
width: node.measured?.width ?? node.width ?? 150,
|
|
38
|
+
height: node.measured?.height ?? node.height ?? 50
|
|
39
39
|
});
|
|
40
40
|
});
|
|
41
41
|
edges.forEach(edge => {
|
|
@@ -48,8 +48,8 @@ async function applyDagreLayout(nodes, edges, options) {
|
|
|
48
48
|
return {
|
|
49
49
|
...node,
|
|
50
50
|
position: {
|
|
51
|
-
x: layoutNode.x - (node.width
|
|
52
|
-
y: layoutNode.y - (node.height
|
|
51
|
+
x: layoutNode.x - (node.measured?.width ?? node.width ?? 150) / 2,
|
|
52
|
+
y: layoutNode.y - (node.measured?.height ?? node.height ?? 50) / 2
|
|
53
53
|
}
|
|
54
54
|
};
|
|
55
55
|
});
|
|
@@ -88,8 +88,8 @@ async function applyElkLayout(nodes, edges, options) {
|
|
|
88
88
|
},
|
|
89
89
|
children: nodes.map(node => ({
|
|
90
90
|
id: node.id,
|
|
91
|
-
width: node.width
|
|
92
|
-
height: node.height
|
|
91
|
+
width: node.measured?.width ?? node.width ?? 150,
|
|
92
|
+
height: node.measured?.height ?? node.height ?? 50
|
|
93
93
|
})),
|
|
94
94
|
edges: edges.map((edge, index) => ({
|
|
95
95
|
id: edge.id || `edge-${index}`,
|
|
@@ -26,8 +26,8 @@ async function applyDagreLayout(nodes, edges, options) {
|
|
|
26
26
|
g.setDefaultEdgeLabel(() => ({}));
|
|
27
27
|
nodes.forEach((node) => {
|
|
28
28
|
g.setNode(node.id, {
|
|
29
|
-
width: node.width
|
|
30
|
-
height: node.height
|
|
29
|
+
width: node.measured?.width ?? node.width ?? 150,
|
|
30
|
+
height: node.measured?.height ?? node.height ?? 50
|
|
31
31
|
});
|
|
32
32
|
});
|
|
33
33
|
edges.forEach((edge) => {
|
|
@@ -40,8 +40,8 @@ async function applyDagreLayout(nodes, edges, options) {
|
|
|
40
40
|
return {
|
|
41
41
|
...node,
|
|
42
42
|
position: {
|
|
43
|
-
x: layoutNode.x - (node.width
|
|
44
|
-
y: layoutNode.y - (node.height
|
|
43
|
+
x: layoutNode.x - (node.measured?.width ?? node.width ?? 150) / 2,
|
|
44
|
+
y: layoutNode.y - (node.measured?.height ?? node.height ?? 50) / 2
|
|
45
45
|
}
|
|
46
46
|
};
|
|
47
47
|
});
|
|
@@ -81,8 +81,8 @@ async function applyElkLayout(nodes, edges, options) {
|
|
|
81
81
|
},
|
|
82
82
|
children: nodes.map((node) => ({
|
|
83
83
|
id: node.id,
|
|
84
|
-
width: node.width
|
|
85
|
-
height: node.height
|
|
84
|
+
width: node.measured?.width ?? node.width ?? 150,
|
|
85
|
+
height: node.measured?.height ?? node.height ?? 50
|
|
86
86
|
})),
|
|
87
87
|
edges: edges.map((edge, index) => ({
|
|
88
88
|
id: edge.id || `edge-${index}`,
|
|
@@ -24,8 +24,8 @@ function computeBoundingBox(nodes, paddingX, paddingY) {
|
|
|
24
24
|
maxX = -Infinity,
|
|
25
25
|
maxY = -Infinity;
|
|
26
26
|
nodes.forEach(node => {
|
|
27
|
-
const w = node.width
|
|
28
|
-
const h = node.height
|
|
27
|
+
const w = node.measured?.width ?? node.width ?? 150;
|
|
28
|
+
const h = node.measured?.height ?? node.height ?? 50;
|
|
29
29
|
minX = Math.min(minX, node.position.x);
|
|
30
30
|
minY = Math.min(minY, node.position.y);
|
|
31
31
|
maxX = Math.max(maxX, node.position.x + w);
|
|
@@ -165,7 +165,15 @@ function createNodeGroupPlugin(options = {}) {
|
|
|
165
165
|
}
|
|
166
166
|
});
|
|
167
167
|
if (hidden) {
|
|
168
|
+
const groupNode = flow.getNode(groupId);
|
|
169
|
+
const currentW = groupNode?.width ?? groupNode?.style?.width ?? 200;
|
|
170
|
+
const currentH = groupNode?.height ?? groupNode?.style?.height ?? 150;
|
|
168
171
|
flow.updateNode(groupId, {
|
|
172
|
+
data: {
|
|
173
|
+
...(groupNode?.data || {}),
|
|
174
|
+
_origWidth: currentW,
|
|
175
|
+
_origHeight: currentH
|
|
176
|
+
},
|
|
169
177
|
style: {
|
|
170
178
|
width: 160,
|
|
171
179
|
height: 50,
|
|
@@ -10,8 +10,8 @@ function computeBoundingBox(nodes, paddingX, paddingY) {
|
|
|
10
10
|
if (nodes.length === 0) return { x: 0, y: 0, width: 200, height: 150 };
|
|
11
11
|
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
12
12
|
nodes.forEach((node) => {
|
|
13
|
-
const w = node.width
|
|
14
|
-
const h = node.height
|
|
13
|
+
const w = node.measured?.width ?? node.width ?? 150;
|
|
14
|
+
const h = node.measured?.height ?? node.height ?? 50;
|
|
15
15
|
minX = Math.min(minX, node.position.x);
|
|
16
16
|
minY = Math.min(minY, node.position.y);
|
|
17
17
|
maxX = Math.max(maxX, node.position.x + w);
|
|
@@ -139,7 +139,15 @@ export function createNodeGroupPlugin(options = {}) {
|
|
|
139
139
|
}
|
|
140
140
|
});
|
|
141
141
|
if (hidden) {
|
|
142
|
+
const groupNode = flow.getNode(groupId);
|
|
143
|
+
const currentW = groupNode?.width ?? groupNode?.style?.width ?? 200;
|
|
144
|
+
const currentH = groupNode?.height ?? groupNode?.style?.height ?? 150;
|
|
142
145
|
flow.updateNode(groupId, {
|
|
146
|
+
data: {
|
|
147
|
+
...groupNode?.data || {},
|
|
148
|
+
_origWidth: currentW,
|
|
149
|
+
_origHeight: currentH
|
|
150
|
+
},
|
|
143
151
|
style: { width: 160, height: 50, minWidth: 160, minHeight: 50 },
|
|
144
152
|
width: 160,
|
|
145
153
|
height: 50
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
<script setup>
|
|
33
33
|
import { computed } from "vue";
|
|
34
34
|
import { useFlowContext } from "../core/FlowContext";
|
|
35
|
+
import { getNodeAbsolutePosition } from "../utils/custom-types";
|
|
35
36
|
const props = defineProps({
|
|
36
37
|
snapThreshold: { type: Number, required: false }
|
|
37
38
|
});
|
|
@@ -42,6 +43,9 @@ const draggingPosition = flowInstance.draggingPosition;
|
|
|
42
43
|
const containerWidth = computed(() => flowInstance.$el?.clientWidth || 800);
|
|
43
44
|
const containerHeight = computed(() => flowInstance.$el?.clientHeight || 600);
|
|
44
45
|
const SNAP_THRESHOLD = props.snapThreshold ?? 10;
|
|
46
|
+
const nodeMap = computed(() => {
|
|
47
|
+
return new Map(nodes.value.map((n) => [n.id, n]));
|
|
48
|
+
});
|
|
45
49
|
const horizontalLines = computed(() => {
|
|
46
50
|
const dNodeId = draggingNodeId.value;
|
|
47
51
|
const dPos = draggingPosition.value;
|
|
@@ -49,20 +53,23 @@ const horizontalLines = computed(() => {
|
|
|
49
53
|
const lines = [];
|
|
50
54
|
const draggingNode = nodes.value.find((n) => n.id === dNodeId);
|
|
51
55
|
if (!draggingNode) return [];
|
|
52
|
-
const nodeHeight = draggingNode.height
|
|
56
|
+
const nodeHeight = draggingNode.measured?.height ?? draggingNode.height ?? 50;
|
|
53
57
|
const otherNodes = nodes.value.filter((n) => n.id !== dNodeId && !n.hidden);
|
|
58
|
+
const m = nodeMap.value;
|
|
59
|
+
const draggingNodeAbs = getNodeAbsolutePosition(draggingNode, m);
|
|
54
60
|
const positions = [
|
|
55
|
-
{ y:
|
|
56
|
-
{ y:
|
|
57
|
-
{ y:
|
|
61
|
+
{ y: draggingNodeAbs.y, key: "top" },
|
|
62
|
+
{ y: draggingNodeAbs.y + nodeHeight / 2, key: "center" },
|
|
63
|
+
{ y: draggingNodeAbs.y + nodeHeight, key: "bottom" }
|
|
58
64
|
];
|
|
59
65
|
for (const pos of positions) {
|
|
60
66
|
for (const node of otherNodes) {
|
|
61
|
-
const nHeight = node.height
|
|
67
|
+
const nHeight = node.measured?.height ?? node.height ?? 50;
|
|
68
|
+
const otherAbs = getNodeAbsolutePosition(node, m);
|
|
62
69
|
const nodePositions = [
|
|
63
|
-
{ y:
|
|
64
|
-
{ y:
|
|
65
|
-
{ y:
|
|
70
|
+
{ y: otherAbs.y, key: "top" },
|
|
71
|
+
{ y: otherAbs.y + nHeight / 2, key: "center" },
|
|
72
|
+
{ y: otherAbs.y + nHeight, key: "bottom" }
|
|
66
73
|
];
|
|
67
74
|
for (const np of nodePositions) {
|
|
68
75
|
if (Math.abs(pos.y - np.y) < SNAP_THRESHOLD) {
|
|
@@ -81,20 +88,23 @@ const verticalLines = computed(() => {
|
|
|
81
88
|
const lines = [];
|
|
82
89
|
const draggingNode = nodes.value.find((n) => n.id === dNodeId);
|
|
83
90
|
if (!draggingNode) return [];
|
|
84
|
-
const nodeWidth = draggingNode.width
|
|
91
|
+
const nodeWidth = draggingNode.measured?.width ?? draggingNode.width ?? 200;
|
|
85
92
|
const otherNodes = nodes.value.filter((n) => n.id !== dNodeId && !n.hidden);
|
|
93
|
+
const m = nodeMap.value;
|
|
94
|
+
const draggingNodeAbs = getNodeAbsolutePosition(draggingNode, m);
|
|
86
95
|
const positions = [
|
|
87
|
-
{ x:
|
|
88
|
-
{ x:
|
|
89
|
-
{ x:
|
|
96
|
+
{ x: draggingNodeAbs.x, key: "left" },
|
|
97
|
+
{ x: draggingNodeAbs.x + nodeWidth / 2, key: "center" },
|
|
98
|
+
{ x: draggingNodeAbs.x + nodeWidth, key: "right" }
|
|
90
99
|
];
|
|
91
100
|
for (const pos of positions) {
|
|
92
101
|
for (const node of otherNodes) {
|
|
93
|
-
const nWidth = node.width
|
|
102
|
+
const nWidth = node.measured?.width ?? node.width ?? 200;
|
|
103
|
+
const otherAbs = getNodeAbsolutePosition(node, m);
|
|
94
104
|
const nodePositions = [
|
|
95
|
-
{ x:
|
|
96
|
-
{ x:
|
|
97
|
-
{ x:
|
|
105
|
+
{ x: otherAbs.x, key: "left" },
|
|
106
|
+
{ x: otherAbs.x + nWidth / 2, key: "center" },
|
|
107
|
+
{ x: otherAbs.x + nWidth, key: "right" }
|
|
98
108
|
];
|
|
99
109
|
for (const np of nodePositions) {
|
|
100
110
|
if (Math.abs(pos.x - np.x) < SNAP_THRESHOLD) {
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
<script setup>
|
|
41
41
|
import { computed } from "vue";
|
|
42
42
|
import { getHandlePosition } from "../utils/edge";
|
|
43
|
+
import { getNodeAbsolutePosition } from "../utils/custom-types";
|
|
43
44
|
const props = defineProps({
|
|
44
45
|
edges: { type: Array, required: true },
|
|
45
46
|
nodes: { type: Array, required: true }
|
|
@@ -59,8 +60,16 @@ const selectedUpdatableEdges = computed(() => {
|
|
|
59
60
|
if (!sourceNode || !targetNode) continue;
|
|
60
61
|
const sPosDesc = edge.sourceHandle || "right";
|
|
61
62
|
const tPosDesc = edge.targetHandle || "left";
|
|
62
|
-
const
|
|
63
|
-
|
|
63
|
+
const sAbsNode = {
|
|
64
|
+
...sourceNode,
|
|
65
|
+
position: getNodeAbsolutePosition(sourceNode, nodeMap.value)
|
|
66
|
+
};
|
|
67
|
+
const tAbsNode = {
|
|
68
|
+
...targetNode,
|
|
69
|
+
position: getNodeAbsolutePosition(targetNode, nodeMap.value)
|
|
70
|
+
};
|
|
71
|
+
const sPos = getHandlePosition(sAbsNode, sPosDesc, edge.sourceHandle);
|
|
72
|
+
const tPos = getHandlePosition(tAbsNode, tPosDesc, edge.targetHandle);
|
|
64
73
|
result.push({
|
|
65
74
|
edge,
|
|
66
75
|
sourceX: sPos.x,
|
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
<script setup>
|
|
112
112
|
import { computed } from "vue";
|
|
113
113
|
import { getEdgePath, getEdgeCenter, getHandlePosition } from "../utils/edge";
|
|
114
|
-
import { getCustomEdge } from "../utils/custom-types";
|
|
114
|
+
import { getCustomEdge, getNodeAbsolutePosition } from "../utils/custom-types";
|
|
115
115
|
const props = defineProps({
|
|
116
116
|
flowId: { type: String, required: true },
|
|
117
117
|
edges: { type: Array, required: true },
|
|
@@ -160,8 +160,10 @@ const edgeData = computed(() => {
|
|
|
160
160
|
if (!sourceNode || !targetNode) continue;
|
|
161
161
|
const sPosDesc = edge.sourceHandle || "right";
|
|
162
162
|
const tPosDesc = edge.targetHandle || "left";
|
|
163
|
-
const
|
|
164
|
-
const
|
|
163
|
+
const sAbsNode = { ...sourceNode, position: getNodeAbsolutePosition(sourceNode, nodeMap.value) };
|
|
164
|
+
const tAbsNode = { ...targetNode, position: getNodeAbsolutePosition(targetNode, nodeMap.value) };
|
|
165
|
+
const sPos = getHandlePosition(sAbsNode, sPosDesc, edge.sourceHandle);
|
|
166
|
+
const tPos = getHandlePosition(tAbsNode, tPosDesc, edge.targetHandle);
|
|
165
167
|
const pathParams = {
|
|
166
168
|
sourceX: sPos.x,
|
|
167
169
|
sourceY: sPos.y,
|
|
@@ -94,6 +94,7 @@
|
|
|
94
94
|
<script setup>
|
|
95
95
|
import { ref, computed, watch, onMounted, onBeforeUnmount } from "vue";
|
|
96
96
|
import { useFlowContext } from "../core/FlowContext";
|
|
97
|
+
import { getNodeAbsolutePosition } from "../utils/custom-types";
|
|
97
98
|
const props = defineProps({
|
|
98
99
|
nodeColor: { type: [String, Function], required: false },
|
|
99
100
|
nodeStrokeColor: { type: [String, Function], required: false },
|
|
@@ -120,6 +121,11 @@ const flowInstance = useFlowContext();
|
|
|
120
121
|
const nodes = flowInstance.nodes;
|
|
121
122
|
const edges = flowInstance.edges;
|
|
122
123
|
const viewport = flowInstance.viewport;
|
|
124
|
+
const nodeMap = computed(() => {
|
|
125
|
+
const m = /* @__PURE__ */ new Map();
|
|
126
|
+
nodes.value.forEach((n) => m.set(n.id, n));
|
|
127
|
+
return m;
|
|
128
|
+
});
|
|
123
129
|
let _mapScale = 1;
|
|
124
130
|
let _minX = 0;
|
|
125
131
|
let _minY = 0;
|
|
@@ -129,15 +135,17 @@ const computeGraphBounds = () => {
|
|
|
129
135
|
const currentNodes = nodes.value;
|
|
130
136
|
if (!currentNodes.length) return { minX: 0, minY: 0, maxX: 1, maxY: 1 };
|
|
131
137
|
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
138
|
+
const m = nodeMap.value;
|
|
132
139
|
for (let i = 0; i < currentNodes.length; i++) {
|
|
133
140
|
const n = currentNodes[i];
|
|
134
141
|
if (n.hidden) continue;
|
|
135
|
-
const nw = n.width
|
|
136
|
-
const nh = n.height
|
|
137
|
-
|
|
138
|
-
if (
|
|
139
|
-
if (
|
|
140
|
-
if (
|
|
142
|
+
const nw = n.measured?.width ?? n.width ?? 120;
|
|
143
|
+
const nh = n.measured?.height ?? n.height ?? 50;
|
|
144
|
+
const absPos = getNodeAbsolutePosition(n, m);
|
|
145
|
+
if (absPos.x < minX) minX = absPos.x;
|
|
146
|
+
if (absPos.y < minY) minY = absPos.y;
|
|
147
|
+
if (absPos.x + nw > maxX) maxX = absPos.x + nw;
|
|
148
|
+
if (absPos.y + nh > maxY) maxY = absPos.y + nh;
|
|
141
149
|
}
|
|
142
150
|
if (minX === Infinity) return { minX: 0, minY: 0, maxX: 1, maxY: 1 };
|
|
143
151
|
return { minX, minY, maxX, maxY };
|
|
@@ -163,8 +171,7 @@ const drawCanvas = () => {
|
|
|
163
171
|
updateVpRect(viewport.value);
|
|
164
172
|
return;
|
|
165
173
|
}
|
|
166
|
-
const
|
|
167
|
-
nodes.value.forEach((n) => nodeMap.set(n.id, n));
|
|
174
|
+
const nodeMapVal = nodeMap.value;
|
|
168
175
|
const rootStyles = getComputedStyle(canvas);
|
|
169
176
|
const themeEdgeColor = rootStyles.getPropertyValue("--flow-edge-stroke").trim() || "#94a3b8";
|
|
170
177
|
const themeMinimapNodeColor = rootStyles.getPropertyValue("--flow-minimap-node-color").trim() || "#cbd5e1";
|
|
@@ -175,16 +182,18 @@ const drawCanvas = () => {
|
|
|
175
182
|
ctx.beginPath();
|
|
176
183
|
edges.value.forEach((e) => {
|
|
177
184
|
if (e.hidden) return;
|
|
178
|
-
const src =
|
|
179
|
-
const tgt =
|
|
185
|
+
const src = nodeMapVal.get(e.source);
|
|
186
|
+
const tgt = nodeMapVal.get(e.target);
|
|
180
187
|
if (!src || !tgt) return;
|
|
188
|
+
const srcAbs = getNodeAbsolutePosition(src, nodeMapVal);
|
|
189
|
+
const tgtAbs = getNodeAbsolutePosition(tgt, nodeMapVal);
|
|
181
190
|
ctx.moveTo(
|
|
182
|
-
toMX(
|
|
183
|
-
toMY(
|
|
191
|
+
toMX(srcAbs.x + (src.measured?.width ?? src.width ?? 120) / 2),
|
|
192
|
+
toMY(srcAbs.y + (src.measured?.height ?? src.height ?? 50) / 2)
|
|
184
193
|
);
|
|
185
194
|
ctx.lineTo(
|
|
186
|
-
toMX(
|
|
187
|
-
toMY(
|
|
195
|
+
toMX(tgtAbs.x + (tgt.measured?.width ?? tgt.width ?? 120) / 2),
|
|
196
|
+
toMY(tgtAbs.y + (tgt.measured?.height ?? tgt.height ?? 50) / 2)
|
|
188
197
|
);
|
|
189
198
|
});
|
|
190
199
|
ctx.stroke();
|
|
@@ -196,10 +205,11 @@ const drawCanvas = () => {
|
|
|
196
205
|
color = typeof props.nodeColor === "function" ? props.nodeColor(n) : props.nodeColor;
|
|
197
206
|
}
|
|
198
207
|
ctx.fillStyle = color;
|
|
199
|
-
const
|
|
200
|
-
const
|
|
201
|
-
const
|
|
202
|
-
const
|
|
208
|
+
const absPos = getNodeAbsolutePosition(n, nodeMapVal);
|
|
209
|
+
const nw = Math.max((n.measured?.width ?? n.width ?? 120) * _mapScale, 3);
|
|
210
|
+
const nh = Math.max((n.measured?.height ?? n.height ?? 50) * _mapScale, 3);
|
|
211
|
+
const nx = toMX(absPos.x);
|
|
212
|
+
const ny = toMY(absPos.y);
|
|
203
213
|
ctx.fillRect(nx, ny, nw, nh);
|
|
204
214
|
if (props.nodeStrokeWidth || props.nodeStrokeColor) {
|
|
205
215
|
let strokeColor = "#94a3b8";
|
|
@@ -30,6 +30,7 @@ declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}
|
|
|
30
30
|
"node-drag-end": (event: MouseEvent, node: Node<import("../types").NodeData>) => any;
|
|
31
31
|
"connect-start": (event: MouseEvent, nodeId: string, handleId: string, handleType: HandleType) => any;
|
|
32
32
|
"node-select-toggle": (nodeId: string) => any;
|
|
33
|
+
"nodes-measured": () => any;
|
|
33
34
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
34
35
|
"onNode-click"?: ((event: MouseEvent, node: Node<import("../types").NodeData>) => any) | undefined;
|
|
35
36
|
"onNode-dblclick"?: ((event: MouseEvent, node: Node<import("../types").NodeData>) => any) | undefined;
|
|
@@ -42,6 +43,7 @@ declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}
|
|
|
42
43
|
"onNode-drag-end"?: ((event: MouseEvent, node: Node<import("../types").NodeData>) => any) | undefined;
|
|
43
44
|
"onConnect-start"?: ((event: MouseEvent, nodeId: string, handleId: string, handleType: HandleType) => any) | undefined;
|
|
44
45
|
"onNode-select-toggle"?: ((nodeId: string) => any) | undefined;
|
|
46
|
+
"onNodes-measured"?: (() => any) | undefined;
|
|
45
47
|
}>, {
|
|
46
48
|
draggable: boolean;
|
|
47
49
|
connectable: boolean;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
v-for="node in visibleNodes"
|
|
5
5
|
:key="node.id"
|
|
6
6
|
:id="getNodeDomId(node.id)"
|
|
7
|
+
:ref="el => setNodeRef(el, node.id)"
|
|
7
8
|
class="yh-flow-node"
|
|
8
9
|
:class="{
|
|
9
10
|
'is-selected': node.selected,
|
|
@@ -63,8 +64,12 @@
|
|
|
63
64
|
</template>
|
|
64
65
|
|
|
65
66
|
<script setup>
|
|
66
|
-
import { computed, ref } from "vue";
|
|
67
|
-
import {
|
|
67
|
+
import { computed, ref, onMounted, onBeforeUnmount } from "vue";
|
|
68
|
+
import {
|
|
69
|
+
getCustomNodeTemplate,
|
|
70
|
+
getCustomNode,
|
|
71
|
+
getNodeAbsolutePosition
|
|
72
|
+
} from "../utils/custom-types";
|
|
68
73
|
import DiamondNode from "../components/nodes/DiamondNode.vue";
|
|
69
74
|
import DatabaseNode from "../components/nodes/DatabaseNode.vue";
|
|
70
75
|
const props = defineProps({
|
|
@@ -76,7 +81,7 @@ const props = defineProps({
|
|
|
76
81
|
connectable: { type: Boolean, required: false, default: true },
|
|
77
82
|
readonly: { type: Boolean, required: false, default: false }
|
|
78
83
|
});
|
|
79
|
-
const emit = defineEmits(["node-click", "node-dblclick", "node-contextmenu", "node-drag-start", "node-drag", "node-drag-end", "connect-start", "node-select-toggle"]);
|
|
84
|
+
const emit = defineEmits(["node-click", "node-dblclick", "node-contextmenu", "node-drag-start", "node-drag", "node-drag-end", "connect-start", "node-select-toggle", "nodes-measured"]);
|
|
80
85
|
const getComponent = (type) => {
|
|
81
86
|
if (type === "diamond") return DiamondNode;
|
|
82
87
|
if (type === "database") return DatabaseNode;
|
|
@@ -89,24 +94,33 @@ const visibleNodes = computed(() => {
|
|
|
89
94
|
return props.nodes.filter((n) => !n.hidden);
|
|
90
95
|
});
|
|
91
96
|
const getNodeDomId = (nodeId) => `${props.flowId}-node-${nodeId}`;
|
|
97
|
+
const nodeMap = computed(() => {
|
|
98
|
+
const m = /* @__PURE__ */ new Map();
|
|
99
|
+
props.nodes.forEach((n) => m.set(n.id, n));
|
|
100
|
+
return m;
|
|
101
|
+
});
|
|
92
102
|
const getNodeStyle = (node) => {
|
|
93
|
-
const width = node.width || 150;
|
|
94
|
-
const height = node.height || 40;
|
|
95
103
|
let zIndex = node.zIndex || 10;
|
|
96
104
|
if (node.type === "group") {
|
|
97
105
|
zIndex = node.selected ? 2 : 1;
|
|
98
106
|
} else {
|
|
99
107
|
zIndex = node.selected ? 100 : Math.max(10, zIndex);
|
|
100
108
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
height: `${height}px`,
|
|
109
|
+
const absPos = getNodeAbsolutePosition(node, nodeMap.value);
|
|
110
|
+
const style = {
|
|
111
|
+
transform: `translate(${absPos.x}px, ${absPos.y}px)`,
|
|
105
112
|
zIndex,
|
|
106
113
|
"--flow-node-label-color": node.labelColor,
|
|
107
114
|
"--flow-node-description-color": node.descriptionColor,
|
|
108
115
|
...node.style
|
|
109
116
|
};
|
|
117
|
+
if (node.width !== void 0 && node.width !== null) {
|
|
118
|
+
style.width = `${node.width}px`;
|
|
119
|
+
}
|
|
120
|
+
if (node.height !== void 0 && node.height !== null) {
|
|
121
|
+
style.height = `${node.height}px`;
|
|
122
|
+
}
|
|
123
|
+
return style;
|
|
110
124
|
};
|
|
111
125
|
const getComponentProps = (node) => {
|
|
112
126
|
const {
|
|
@@ -278,16 +292,18 @@ const handleMouseMove = (event) => {
|
|
|
278
292
|
const dx = (event.clientX - dragStartPos.value.x) / props.transform.zoom;
|
|
279
293
|
const dy = (event.clientY - dragStartPos.value.y) / props.transform.zoom;
|
|
280
294
|
draggingNodes.value.forEach((nodeId) => {
|
|
295
|
+
const node = props.nodes.find((n) => n.id === nodeId);
|
|
296
|
+
if (!node) return;
|
|
297
|
+
if (node.parentId && draggingNodes.value.includes(node.parentId)) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
281
300
|
const startPos = nodesStartPositions.value.get(nodeId);
|
|
282
301
|
if (startPos) {
|
|
283
302
|
const newPosition = {
|
|
284
303
|
x: startPos.x + dx,
|
|
285
304
|
y: startPos.y + dy
|
|
286
305
|
};
|
|
287
|
-
|
|
288
|
-
if (node) {
|
|
289
|
-
emit("node-drag", event, node, newPosition);
|
|
290
|
-
}
|
|
306
|
+
emit("node-drag", event, node, newPosition);
|
|
291
307
|
}
|
|
292
308
|
});
|
|
293
309
|
};
|
|
@@ -324,6 +340,51 @@ const handleNodeContextMenu = (event, node) => {
|
|
|
324
340
|
const handleConnectStart = (event, node, handle) => {
|
|
325
341
|
emit("connect-start", event, node.id, handle.id || handle.position || "", handle.type);
|
|
326
342
|
};
|
|
343
|
+
const nodeElements = /* @__PURE__ */ new Map();
|
|
344
|
+
let resizeObserver = null;
|
|
345
|
+
onMounted(() => {
|
|
346
|
+
resizeObserver = new ResizeObserver((entries) => {
|
|
347
|
+
let hasChanges = false;
|
|
348
|
+
for (const entry of entries) {
|
|
349
|
+
const target = entry.target;
|
|
350
|
+
const nodeId = target.getAttribute("data-node-id");
|
|
351
|
+
if (nodeId) {
|
|
352
|
+
const rect = target.getBoundingClientRect();
|
|
353
|
+
const width = Math.round(rect.width);
|
|
354
|
+
const height = Math.round(rect.height);
|
|
355
|
+
const node = props.nodes.find((n) => n.id === nodeId);
|
|
356
|
+
if (node) {
|
|
357
|
+
if (!node.measured || node.measured.width !== width || node.measured.height !== height) {
|
|
358
|
+
node.measured = { width, height };
|
|
359
|
+
hasChanges = true;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (hasChanges) {
|
|
365
|
+
emit("nodes-measured");
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
nodeElements.forEach((el) => resizeObserver?.observe(el));
|
|
369
|
+
});
|
|
370
|
+
onBeforeUnmount(() => {
|
|
371
|
+
if (resizeObserver) {
|
|
372
|
+
resizeObserver.disconnect();
|
|
373
|
+
}
|
|
374
|
+
nodeElements.clear();
|
|
375
|
+
});
|
|
376
|
+
const setNodeRef = (el, nodeId) => {
|
|
377
|
+
if (el) {
|
|
378
|
+
nodeElements.set(nodeId, el);
|
|
379
|
+
resizeObserver?.observe(el);
|
|
380
|
+
} else {
|
|
381
|
+
const existingEl = nodeElements.get(nodeId);
|
|
382
|
+
if (existingEl) {
|
|
383
|
+
resizeObserver?.unobserve(existingEl);
|
|
384
|
+
nodeElements.delete(nodeId);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
};
|
|
327
388
|
</script>
|
|
328
389
|
|
|
329
390
|
<style scoped>
|
|
@@ -30,6 +30,7 @@ declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}
|
|
|
30
30
|
"node-drag-end": (event: MouseEvent, node: Node<import("../types").NodeData>) => any;
|
|
31
31
|
"connect-start": (event: MouseEvent, nodeId: string, handleId: string, handleType: HandleType) => any;
|
|
32
32
|
"node-select-toggle": (nodeId: string) => any;
|
|
33
|
+
"nodes-measured": () => any;
|
|
33
34
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
34
35
|
"onNode-click"?: ((event: MouseEvent, node: Node<import("../types").NodeData>) => any) | undefined;
|
|
35
36
|
"onNode-dblclick"?: ((event: MouseEvent, node: Node<import("../types").NodeData>) => any) | undefined;
|
|
@@ -42,6 +43,7 @@ declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}
|
|
|
42
43
|
"onNode-drag-end"?: ((event: MouseEvent, node: Node<import("../types").NodeData>) => any) | undefined;
|
|
43
44
|
"onConnect-start"?: ((event: MouseEvent, nodeId: string, handleId: string, handleType: HandleType) => any) | undefined;
|
|
44
45
|
"onNode-select-toggle"?: ((nodeId: string) => any) | undefined;
|
|
46
|
+
"onNodes-measured"?: (() => any) | undefined;
|
|
45
47
|
}>, {
|
|
46
48
|
draggable: boolean;
|
|
47
49
|
connectable: boolean;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -54,6 +54,10 @@ export interface Node<Data = NodeData> {
|
|
|
54
54
|
dragging?: boolean;
|
|
55
55
|
width?: number;
|
|
56
56
|
height?: number;
|
|
57
|
+
measured?: {
|
|
58
|
+
width: number;
|
|
59
|
+
height: number;
|
|
60
|
+
};
|
|
57
61
|
parentId?: string;
|
|
58
62
|
zIndex?: number;
|
|
59
63
|
extent?: 'parent' | string;
|
|
@@ -262,6 +266,29 @@ export interface FlowInstance {
|
|
|
262
266
|
/** Screenshot: pass options or legacy HTMLElement as container. Returns result with dataUrl/blob. */
|
|
263
267
|
exportImage?: (options?: ScreenshotOptions | HTMLElement) => Promise<ScreenshotResult>;
|
|
264
268
|
applyLayout?: (options?: unknown) => void | Promise<void>;
|
|
269
|
+
groupSelectedNodes?: (label?: string) => string | null;
|
|
270
|
+
ungroupNodes?: (groupId: string) => void;
|
|
271
|
+
toggleGroupCollapse?: (groupId: string) => void;
|
|
272
|
+
isGroupCollapsed?: (groupId: string) => boolean;
|
|
273
|
+
getGroupChildren?: (groupId: string) => Node[];
|
|
274
|
+
getGroupRegistry?: () => Map<string, {
|
|
275
|
+
groupId: string;
|
|
276
|
+
childIds: string[];
|
|
277
|
+
collapsed: boolean;
|
|
278
|
+
originalPositions: Record<string, {
|
|
279
|
+
x: number;
|
|
280
|
+
y: number;
|
|
281
|
+
}>;
|
|
282
|
+
}>;
|
|
283
|
+
undo?: () => void;
|
|
284
|
+
redo?: () => void;
|
|
285
|
+
saveSnapshot?: (description?: string) => void;
|
|
286
|
+
canUndo?: Ref<boolean>;
|
|
287
|
+
canRedo?: Ref<boolean>;
|
|
288
|
+
historyLength?: Ref<number>;
|
|
289
|
+
clearHistory?: () => void;
|
|
290
|
+
getHistory?: () => unknown[];
|
|
291
|
+
jumpToStep?: (index: number) => void;
|
|
265
292
|
}
|
|
266
293
|
/** 截图范围:当前视口 或 整图(自动 fitView 后截取再恢复视口) */
|
|
267
294
|
export type ScreenshotMode = 'viewport' | 'full';
|