@vanduo-oss/vd3-flowchart 1.4.0 → 1.5.1
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/CHANGELOG.md +45 -0
- package/README.md +35 -5
- package/SKILL.md +9 -0
- package/dist/core.d.ts +14 -0
- package/dist/index.cjs +667 -156
- package/dist/index.cjs.map +3 -3
- package/dist/index.js +667 -156
- package/dist/index.js.map +3 -3
- package/dist/meta.json +13 -13
- package/dist/vd3-flowchart.css +50 -4
- package/dist/vue.d.ts +9 -0
- package/package.json +5 -2
- package/recipes/editor.vue +4 -2
package/dist/index.cjs
CHANGED
|
@@ -44,6 +44,8 @@ var DIRECTIONS = {
|
|
|
44
44
|
down: { main: "y", sign: 1 },
|
|
45
45
|
up: { main: "y", sign: -1 }
|
|
46
46
|
};
|
|
47
|
+
var DEFAULT_LEVEL_EDGE_GAP = 88;
|
|
48
|
+
var DEFAULT_SIBLING_EDGE_GAP = 64;
|
|
47
49
|
function num(value, fallback) {
|
|
48
50
|
const next = Number(value);
|
|
49
51
|
return Number.isFinite(next) ? next : fallback;
|
|
@@ -68,60 +70,182 @@ function pickRoots(nodes, indegree, optRoot) {
|
|
|
68
70
|
}
|
|
69
71
|
function spanningForest(allIds, roots, children) {
|
|
70
72
|
const visited = /* @__PURE__ */ new Set();
|
|
71
|
-
const
|
|
72
|
-
const tree = /* @__PURE__ */ new Map();
|
|
73
|
+
const active = /* @__PURE__ */ new Set();
|
|
73
74
|
const forestRoots = [];
|
|
74
|
-
const
|
|
75
|
+
const forwardParents = new Map(allIds.map((id) => [id, []]));
|
|
76
|
+
const dfs = (id) => {
|
|
75
77
|
visited.add(id);
|
|
76
|
-
|
|
77
|
-
const kids = [];
|
|
78
|
+
active.add(id);
|
|
78
79
|
(children.get(id) || []).forEach((child) => {
|
|
79
|
-
if (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
if (active.has(child)) return;
|
|
81
|
+
const list = forwardParents.get(child);
|
|
82
|
+
if (list && !list.includes(id)) list.push(id);
|
|
83
|
+
if (!visited.has(child)) dfs(child);
|
|
83
84
|
});
|
|
84
|
-
|
|
85
|
+
active.delete(id);
|
|
85
86
|
};
|
|
86
87
|
roots.forEach((root) => {
|
|
87
88
|
if (!visited.has(root)) {
|
|
88
89
|
forestRoots.push(root);
|
|
89
|
-
|
|
90
|
+
dfs(root);
|
|
90
91
|
}
|
|
91
92
|
});
|
|
92
93
|
allIds.forEach((id) => {
|
|
93
94
|
if (!visited.has(id)) {
|
|
94
95
|
forestRoots.push(id);
|
|
95
|
-
|
|
96
|
+
dfs(id);
|
|
96
97
|
}
|
|
97
98
|
});
|
|
99
|
+
const depth = new Map(allIds.map((id) => [id, 0]));
|
|
100
|
+
const limit = Math.max(1, allIds.length);
|
|
101
|
+
for (let pass = 0; pass < limit; pass += 1) {
|
|
102
|
+
let changed = false;
|
|
103
|
+
for (const id of allIds) {
|
|
104
|
+
const parents = forwardParents.get(id) || [];
|
|
105
|
+
for (const parent of parents) {
|
|
106
|
+
const nextDepth = Math.min(limit, (depth.get(parent) || 0) + 1);
|
|
107
|
+
if (nextDepth > (depth.get(id) || 0)) {
|
|
108
|
+
depth.set(id, nextDepth);
|
|
109
|
+
changed = true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (!changed) break;
|
|
114
|
+
}
|
|
115
|
+
const tree = new Map(allIds.map((id) => [id, []]));
|
|
116
|
+
const rootSet = new Set(forestRoots);
|
|
117
|
+
const chosenParent = /* @__PURE__ */ new Map();
|
|
118
|
+
for (const id of allIds) {
|
|
119
|
+
if (rootSet.has(id)) continue;
|
|
120
|
+
const parents = forwardParents.get(id) || [];
|
|
121
|
+
if (!parents.length) continue;
|
|
122
|
+
let bestParent = parents[0];
|
|
123
|
+
let bestDepth = depth.get(bestParent) ?? -1;
|
|
124
|
+
for (let index = 1; index < parents.length; index += 1) {
|
|
125
|
+
const candidate = parents[index];
|
|
126
|
+
const candidateDepth = depth.get(candidate) ?? -1;
|
|
127
|
+
if (candidateDepth > bestDepth) {
|
|
128
|
+
bestDepth = candidateDepth;
|
|
129
|
+
bestParent = candidate;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
chosenParent.set(id, bestParent);
|
|
133
|
+
}
|
|
134
|
+
const placedChild = /* @__PURE__ */ new Set();
|
|
135
|
+
for (const parentId of allIds) {
|
|
136
|
+
const out = tree.get(parentId);
|
|
137
|
+
for (const childId of children.get(parentId) || []) {
|
|
138
|
+
if (chosenParent.get(childId) === parentId && !placedChild.has(childId)) {
|
|
139
|
+
placedChild.add(childId);
|
|
140
|
+
out.push(childId);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
98
144
|
return { depth, tree, forestRoots };
|
|
99
145
|
}
|
|
100
146
|
function treeCenters(forestRoots, tree, depth, sizeOf, options) {
|
|
101
147
|
const dir = DIRECTIONS[options.direction] || DIRECTIONS.right;
|
|
102
|
-
const
|
|
103
|
-
const
|
|
148
|
+
const hasExplicitLevel = Number.isFinite(Number(options.levelGap));
|
|
149
|
+
const hasExplicitSibling = Number.isFinite(Number(options.siblingGap));
|
|
150
|
+
const explicitLevelGap = num(options.levelGap, 220);
|
|
151
|
+
const explicitSiblingGap = num(options.siblingGap, 140);
|
|
152
|
+
const edgeSiblingGap = hasExplicitSibling ? Math.max(16, explicitSiblingGap - 96) : DEFAULT_SIBLING_EDGE_GAP;
|
|
153
|
+
const crossSpan = (id) => {
|
|
154
|
+
const size = sizeOf(id);
|
|
155
|
+
return dir.main === "x" ? size.height : size.width;
|
|
156
|
+
};
|
|
157
|
+
const mainSpan = (id) => {
|
|
158
|
+
const size = sizeOf(id);
|
|
159
|
+
return dir.main === "x" ? size.width : size.height;
|
|
160
|
+
};
|
|
161
|
+
const subtreeSpan = /* @__PURE__ */ new Map();
|
|
162
|
+
const subtreeCount = /* @__PURE__ */ new Map();
|
|
163
|
+
const measure = (id) => {
|
|
164
|
+
const selfSpan = crossSpan(id);
|
|
165
|
+
const kids = tree.get(id) || [];
|
|
166
|
+
if (!kids.length) {
|
|
167
|
+
subtreeSpan.set(id, selfSpan);
|
|
168
|
+
subtreeCount.set(id, 1);
|
|
169
|
+
return selfSpan;
|
|
170
|
+
}
|
|
171
|
+
let childTotal = 0;
|
|
172
|
+
let totalCount = 1;
|
|
173
|
+
kids.forEach((kid, index) => {
|
|
174
|
+
if (index > 0) childTotal += edgeSiblingGap;
|
|
175
|
+
childTotal += measure(kid);
|
|
176
|
+
totalCount += subtreeCount.get(kid) || 1;
|
|
177
|
+
});
|
|
178
|
+
const span = Math.max(selfSpan, childTotal);
|
|
179
|
+
subtreeSpan.set(id, span);
|
|
180
|
+
subtreeCount.set(id, totalCount);
|
|
181
|
+
return span;
|
|
182
|
+
};
|
|
183
|
+
forestRoots.forEach(measure);
|
|
104
184
|
const crossOf = /* @__PURE__ */ new Map();
|
|
105
|
-
|
|
106
|
-
|
|
185
|
+
const place = (id, startCross) => {
|
|
186
|
+
const span = subtreeSpan.get(id) || crossSpan(id);
|
|
107
187
|
const kids = tree.get(id) || [];
|
|
108
188
|
if (!kids.length) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return
|
|
189
|
+
const center2 = startCross + span / 2;
|
|
190
|
+
crossOf.set(id, center2);
|
|
191
|
+
return center2;
|
|
192
|
+
}
|
|
193
|
+
let childTotal = 0;
|
|
194
|
+
const kidSpans = kids.map((kid, index) => {
|
|
195
|
+
const kidSpan = subtreeSpan.get(kid) || crossSpan(kid);
|
|
196
|
+
if (index > 0) childTotal += edgeSiblingGap;
|
|
197
|
+
childTotal += kidSpan;
|
|
198
|
+
return kidSpan;
|
|
199
|
+
});
|
|
200
|
+
let cursor = startCross + (span - childTotal) / 2;
|
|
201
|
+
const childCenters = kids.map((kid, index) => {
|
|
202
|
+
const kidSpan = kidSpans[index];
|
|
203
|
+
const center2 = place(kid, cursor);
|
|
204
|
+
cursor += kidSpan + edgeSiblingGap;
|
|
205
|
+
return center2;
|
|
206
|
+
});
|
|
207
|
+
if (kids.length >= 2) {
|
|
208
|
+
const counts = kids.map((kid) => subtreeCount.get(kid) || 1);
|
|
209
|
+
const maxCount = Math.max(...counts);
|
|
210
|
+
const minCount = Math.min(...counts);
|
|
211
|
+
if (maxCount > minCount) {
|
|
212
|
+
const primaryIndex = counts.indexOf(maxCount);
|
|
213
|
+
const center2 = childCenters[primaryIndex];
|
|
214
|
+
crossOf.set(id, center2);
|
|
215
|
+
return center2;
|
|
216
|
+
}
|
|
112
217
|
}
|
|
113
|
-
const childCenters = kids.map(place);
|
|
114
218
|
const center = (childCenters[0] + childCenters[childCenters.length - 1]) / 2;
|
|
115
219
|
crossOf.set(id, center);
|
|
116
220
|
return center;
|
|
117
221
|
};
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
222
|
+
let rootCursor = 0;
|
|
223
|
+
forestRoots.forEach((root, index) => {
|
|
224
|
+
if (index > 0) rootCursor += edgeSiblingGap;
|
|
225
|
+
const span = subtreeSpan.get(root) || crossSpan(root);
|
|
226
|
+
place(root, rootCursor);
|
|
227
|
+
rootCursor += span;
|
|
228
|
+
});
|
|
229
|
+
let maxDepth = 0;
|
|
230
|
+
const maxMainAtDepth = /* @__PURE__ */ new Map();
|
|
231
|
+
crossOf.forEach((_cross, id) => {
|
|
232
|
+
const d = depth.get(id) || 0;
|
|
233
|
+
if (d > maxDepth) maxDepth = d;
|
|
234
|
+
const current = maxMainAtDepth.get(d) || 0;
|
|
235
|
+
const span = mainSpan(id);
|
|
236
|
+
if (span > current) maxMainAtDepth.set(d, span);
|
|
121
237
|
});
|
|
238
|
+
const mainCenterAtDepth = /* @__PURE__ */ new Map([[0, 0]]);
|
|
239
|
+
for (let d = 1; d <= maxDepth; d += 1) {
|
|
240
|
+
const prevCenter = mainCenterAtDepth.get(d - 1) || 0;
|
|
241
|
+
const prevSpan = maxMainAtDepth.get(d - 1) || 160;
|
|
242
|
+
const currSpan = maxMainAtDepth.get(d) || 160;
|
|
243
|
+
const step = hasExplicitLevel ? Math.max(explicitLevelGap, (prevSpan + currSpan) / 2 + 24) : (prevSpan + currSpan) / 2 + DEFAULT_LEVEL_EDGE_GAP;
|
|
244
|
+
mainCenterAtDepth.set(d, prevCenter + step * dir.sign);
|
|
245
|
+
}
|
|
122
246
|
const centers = /* @__PURE__ */ new Map();
|
|
123
247
|
crossOf.forEach((cross, id) => {
|
|
124
|
-
const main = (depth.get(id) || 0)
|
|
248
|
+
const main = mainCenterAtDepth.get(depth.get(id) || 0) || 0;
|
|
125
249
|
centers.set(
|
|
126
250
|
id,
|
|
127
251
|
dir.main === "x" ? { centerX: main, centerY: cross } : { centerX: cross, centerY: main }
|
|
@@ -398,11 +522,12 @@ function createToolbarButton({ action, label, icon, disabled = false }) {
|
|
|
398
522
|
return button;
|
|
399
523
|
}
|
|
400
524
|
var DEFAULT_GRID_SIZE = 24;
|
|
401
|
-
var
|
|
402
|
-
var
|
|
403
|
-
var
|
|
404
|
-
var
|
|
405
|
-
var
|
|
525
|
+
var DEFAULT_MIN_SCALE = 0.35;
|
|
526
|
+
var DEFAULT_MAX_SCALE = 3;
|
|
527
|
+
var DEFAULT_MIN_NODE_SIZE = 56;
|
|
528
|
+
var DEFAULT_MAX_NODE_SIZE = 420;
|
|
529
|
+
var DEFAULT_WORLD_EXTENT = 12e3;
|
|
530
|
+
var WIDE_WORLD_EXTENT = 24e4;
|
|
406
531
|
var MAX_NODES = 1e4;
|
|
407
532
|
var MAX_EDGES = 1e4;
|
|
408
533
|
var RESIZE_HANDLES = ["n", "ne", "e", "se", "s", "sw", "w", "nw"];
|
|
@@ -434,7 +559,7 @@ var EDGE_STROKE_PRESETS = [
|
|
|
434
559
|
{ id: "medium", label: "Medium", width: DEFAULT_EDGE_STROKE_WIDTH },
|
|
435
560
|
{ id: "bold", label: "Bold", width: 3.5 }
|
|
436
561
|
];
|
|
437
|
-
var VD_FLOWCHART_VERSION = "1.
|
|
562
|
+
var VD_FLOWCHART_VERSION = "1.5.1";
|
|
438
563
|
var FLOWCHART_DOCUMENT_VERSION = "1.3.0";
|
|
439
564
|
var FLOWCHART_NODE_TYPES = [
|
|
440
565
|
"rounded-rect",
|
|
@@ -575,6 +700,17 @@ function deepClone(value) {
|
|
|
575
700
|
function formatNumber(value) {
|
|
576
701
|
return Number(value.toFixed(2));
|
|
577
702
|
}
|
|
703
|
+
function formatScale(value) {
|
|
704
|
+
return Number(toFiniteNumber(value, 1).toFixed(4));
|
|
705
|
+
}
|
|
706
|
+
function normalizeBoundPair(min, max, fallbackMin, fallbackMax) {
|
|
707
|
+
const low = Number(min);
|
|
708
|
+
const high = Number(max);
|
|
709
|
+
if (!Number.isFinite(low) || !Number.isFinite(high) || low <= 0 || high <= 0 || low >= high) {
|
|
710
|
+
return { min: fallbackMin, max: fallbackMax };
|
|
711
|
+
}
|
|
712
|
+
return { min: low, max: high };
|
|
713
|
+
}
|
|
578
714
|
function sanitizeId(value) {
|
|
579
715
|
if (value == null) return "";
|
|
580
716
|
return String(value).trim();
|
|
@@ -632,21 +768,36 @@ function normalizeNodeType(type) {
|
|
|
632
768
|
function getNodeSpec(type) {
|
|
633
769
|
return DEFAULT_NODE_SPECS[normalizeNodeType(type)];
|
|
634
770
|
}
|
|
635
|
-
function
|
|
771
|
+
function nodeVisualScale(node) {
|
|
772
|
+
if (!node || node.type === "junction") return 1;
|
|
773
|
+
const spec = getNodeSpec(node.type);
|
|
774
|
+
const width = spec.width > 0 ? toFiniteNumber(node.width, spec.width) / spec.width : 1;
|
|
775
|
+
const height = spec.height > 0 ? toFiniteNumber(node.height, spec.height) / spec.height : 1;
|
|
776
|
+
return clamp(Math.min(1, width, height), 0.01, 1);
|
|
777
|
+
}
|
|
778
|
+
function edgeVisualScale(fromNode, toNode) {
|
|
779
|
+
if (fromNode && toNode) return Math.min(nodeVisualScale(fromNode), nodeVisualScale(toNode));
|
|
780
|
+
if (fromNode) return nodeVisualScale(fromNode);
|
|
781
|
+
if (toNode) return nodeVisualScale(toNode);
|
|
782
|
+
return 1;
|
|
783
|
+
}
|
|
784
|
+
function getNodeSizeBounds(type, sizeBounds) {
|
|
636
785
|
const spec = getNodeSpec(type);
|
|
786
|
+
const min = sizeBounds?.min ?? DEFAULT_MIN_NODE_SIZE;
|
|
787
|
+
const max = sizeBounds?.max ?? DEFAULT_MAX_NODE_SIZE;
|
|
637
788
|
return {
|
|
638
|
-
minWidth: spec.minWidth ??
|
|
639
|
-
minHeight: spec.minHeight ??
|
|
640
|
-
maxWidth: spec.maxWidth ??
|
|
641
|
-
maxHeight: spec.maxHeight ??
|
|
789
|
+
minWidth: spec.minWidth ?? min,
|
|
790
|
+
minHeight: spec.minHeight ?? min,
|
|
791
|
+
maxWidth: spec.maxWidth ?? max,
|
|
792
|
+
maxHeight: spec.maxHeight ?? max
|
|
642
793
|
};
|
|
643
794
|
}
|
|
644
|
-
function clampNodeWidth(type, width, fallback) {
|
|
645
|
-
const bounds = getNodeSizeBounds(type);
|
|
795
|
+
function clampNodeWidth(type, width, fallback, sizeBounds) {
|
|
796
|
+
const bounds = getNodeSizeBounds(type, sizeBounds);
|
|
646
797
|
return clamp(toFiniteNumber(width, fallback), bounds.minWidth, bounds.maxWidth);
|
|
647
798
|
}
|
|
648
|
-
function clampNodeHeight(type, height, fallback) {
|
|
649
|
-
const bounds = getNodeSizeBounds(type);
|
|
799
|
+
function clampNodeHeight(type, height, fallback, sizeBounds) {
|
|
800
|
+
const bounds = getNodeSizeBounds(type, sizeBounds);
|
|
650
801
|
return clamp(toFiniteNumber(height, fallback), bounds.minHeight, bounds.maxHeight);
|
|
651
802
|
}
|
|
652
803
|
function isNodeResizable(nodeOrType) {
|
|
@@ -670,14 +821,16 @@ function getStrokePresetWidth(presetId) {
|
|
|
670
821
|
const preset = EDGE_STROKE_PRESETS.find((item) => item.id === presetId);
|
|
671
822
|
return preset?.width ?? DEFAULT_EDGE_STROKE_WIDTH;
|
|
672
823
|
}
|
|
673
|
-
function normalizeViewport(viewport) {
|
|
824
|
+
function normalizeViewport(viewport, scaleBounds) {
|
|
825
|
+
const min = scaleBounds?.min ?? DEFAULT_MIN_SCALE;
|
|
826
|
+
const max = scaleBounds?.max ?? DEFAULT_MAX_SCALE;
|
|
674
827
|
return {
|
|
675
828
|
x: toFiniteNumber(viewport?.x, 0),
|
|
676
829
|
y: toFiniteNumber(viewport?.y, 0),
|
|
677
|
-
scale: clamp(toFiniteNumber(viewport?.scale, 1),
|
|
830
|
+
scale: clamp(formatScale(toFiniteNumber(viewport?.scale, 1)), min, max)
|
|
678
831
|
};
|
|
679
832
|
}
|
|
680
|
-
function normalizeNode(rawNode, index, usedIds) {
|
|
833
|
+
function normalizeNode(rawNode, index, usedIds, sizeBounds) {
|
|
681
834
|
const type = normalizeNodeType(rawNode?.type);
|
|
682
835
|
const spec = getNodeSpec(type);
|
|
683
836
|
const node = {
|
|
@@ -685,8 +838,8 @@ function normalizeNode(rawNode, index, usedIds) {
|
|
|
685
838
|
type,
|
|
686
839
|
x: toFiniteNumber(rawNode?.x, index * 28),
|
|
687
840
|
y: toFiniteNumber(rawNode?.y, index * 18),
|
|
688
|
-
width: clampNodeWidth(type, rawNode?.width, spec.width),
|
|
689
|
-
height: clampNodeHeight(type, rawNode?.height, spec.height),
|
|
841
|
+
width: clampNodeWidth(type, rawNode?.width, spec.width, sizeBounds),
|
|
842
|
+
height: clampNodeHeight(type, rawNode?.height, spec.height, sizeBounds),
|
|
690
843
|
text: rawNode?.text == null ? spec.text : String(rawNode.text),
|
|
691
844
|
data: isPlainObject(rawNode?.data) ? deepClone(rawNode.data) : {}
|
|
692
845
|
};
|
|
@@ -779,7 +932,7 @@ function isSupportedDocumentVersion(value) {
|
|
|
779
932
|
if (parsed.minor === max.minor && parsed.patch > max.patch) return false;
|
|
780
933
|
return true;
|
|
781
934
|
}
|
|
782
|
-
function normalizeDocument(input) {
|
|
935
|
+
function normalizeDocument(input, limits = {}) {
|
|
783
936
|
let source = input;
|
|
784
937
|
if (typeof source === "string") {
|
|
785
938
|
try {
|
|
@@ -802,13 +955,13 @@ function normalizeDocument(input) {
|
|
|
802
955
|
}
|
|
803
956
|
}
|
|
804
957
|
const usedNodeIds = /* @__PURE__ */ new Set();
|
|
805
|
-
const nodes = (Array.isArray(source.nodes) ? source.nodes : []).slice(0, MAX_NODES).map((node, index) => normalizeNode(node, index, usedNodeIds));
|
|
958
|
+
const nodes = (Array.isArray(source.nodes) ? source.nodes : []).slice(0, MAX_NODES).map((node, index) => normalizeNode(node, index, usedNodeIds, limits.sizeBounds));
|
|
806
959
|
const nodeIds = new Set(nodes.map((node) => node.id));
|
|
807
960
|
const usedEdgeIds = /* @__PURE__ */ new Set();
|
|
808
961
|
const edges = (Array.isArray(source.edges) ? source.edges : []).slice(0, MAX_EDGES).map((edge, index) => normalizeEdge(edge, index, nodeIds, usedEdgeIds)).filter(Boolean);
|
|
809
962
|
return {
|
|
810
963
|
version: FLOWCHART_DOCUMENT_VERSION,
|
|
811
|
-
viewport: normalizeViewport(source.viewport),
|
|
964
|
+
viewport: normalizeViewport(source.viewport, limits.scaleBounds),
|
|
812
965
|
nodes,
|
|
813
966
|
edges
|
|
814
967
|
};
|
|
@@ -1025,19 +1178,30 @@ function getPolylineLabelPoint(points) {
|
|
|
1025
1178
|
const last = points[points.length - 1] || { x: 0, y: 0 };
|
|
1026
1179
|
return { x: formatNumber(last.x), y: formatNumber(last.y) };
|
|
1027
1180
|
}
|
|
1028
|
-
function
|
|
1181
|
+
function routeMetrics(edgeScale = 1) {
|
|
1182
|
+
const scale = clamp(toFiniteNumber(edgeScale, 1), 0.01, 1);
|
|
1183
|
+
return {
|
|
1184
|
+
scale,
|
|
1185
|
+
minCurveArm: MIN_CURVE_ARM * scale,
|
|
1186
|
+
maxCurveArm: MAX_CURVE_ARM * scale,
|
|
1187
|
+
stub: ORTHOGONAL_STUB_LENGTH * scale,
|
|
1188
|
+
corner: ORTHOGONAL_CORNER_RADIUS * scale,
|
|
1189
|
+
clearance: ORTHOGONAL_CLEARANCE * scale
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
function curveControlPoint(point, port, target, curvature, metrics = routeMetrics(1)) {
|
|
1029
1193
|
const normal = getPortNormal(port);
|
|
1030
1194
|
const dx = target.x - point.x;
|
|
1031
1195
|
const dy = target.y - point.y;
|
|
1032
1196
|
const distance = Math.hypot(dx, dy) || 1;
|
|
1033
1197
|
const align = (normal.x * dx + normal.y * dy) / distance;
|
|
1034
1198
|
const ease = 0.55 + 0.45 * align;
|
|
1035
|
-
const arm = clamp(distance * curvature * ease,
|
|
1199
|
+
const arm = clamp(distance * curvature * ease, metrics.minCurveArm, metrics.maxCurveArm);
|
|
1036
1200
|
return { x: point.x + normal.x * arm, y: point.y + normal.y * arm };
|
|
1037
1201
|
}
|
|
1038
|
-
function buildCurvePath(fromPoint, toPoint, fromPort = "right", toPort = "left") {
|
|
1039
|
-
const controlA = curveControlPoint(fromPoint, fromPort, toPoint, EDGE_CURVATURE);
|
|
1040
|
-
const controlB = curveControlPoint(toPoint, toPort, fromPoint, EDGE_CURVATURE);
|
|
1202
|
+
function buildCurvePath(fromPoint, toPoint, fromPort = "right", toPort = "left", metrics = routeMetrics(1)) {
|
|
1203
|
+
const controlA = curveControlPoint(fromPoint, fromPort, toPoint, EDGE_CURVATURE, metrics);
|
|
1204
|
+
const controlB = curveControlPoint(toPoint, toPort, fromPoint, EDGE_CURVATURE, metrics);
|
|
1041
1205
|
const labelX = 0.125 * fromPoint.x + 0.375 * controlA.x + 0.375 * controlB.x + 0.125 * toPoint.x;
|
|
1042
1206
|
const labelY = 0.125 * fromPoint.y + 0.375 * controlA.y + 0.375 * controlB.y + 0.125 * toPoint.y;
|
|
1043
1207
|
return {
|
|
@@ -1099,13 +1263,40 @@ function getOrthogonalMidpoints(fromStub, toStub, fromPort, toPort) {
|
|
|
1099
1263
|
}
|
|
1100
1264
|
return [{ x: fromStub.x, y: toStub.y }];
|
|
1101
1265
|
}
|
|
1102
|
-
function getOrthogonalPoints(fromPoint, toPoint, fromPort, toPort, fromRect, toRect) {
|
|
1266
|
+
function getOrthogonalPoints(fromPoint, toPoint, fromPort, toPort, fromRect, toRect, metrics = routeMetrics(1)) {
|
|
1103
1267
|
const fromNormal = getPortNormal(fromPort);
|
|
1104
1268
|
const toNormal = getPortNormal(toPort);
|
|
1105
|
-
const fromStub = offsetPoint(fromPoint, fromNormal, ORTHOGONAL_STUB_LENGTH);
|
|
1106
|
-
const toStub = offsetPoint(toPoint, toNormal, ORTHOGONAL_STUB_LENGTH);
|
|
1107
1269
|
const sourceHorizontal = isHorizontalPort(fromPort);
|
|
1108
1270
|
const targetHorizontal = isHorizontalPort(toPort);
|
|
1271
|
+
const minStub = 4 * (metrics.scale || 1);
|
|
1272
|
+
let fromStubLen = metrics.stub;
|
|
1273
|
+
let toStubLen = metrics.stub;
|
|
1274
|
+
if (sourceHorizontal && targetHorizontal && fromNormal.x === -toNormal.x) {
|
|
1275
|
+
const span = (toPoint.x - fromPoint.x) * fromNormal.x;
|
|
1276
|
+
if (span > 0) {
|
|
1277
|
+
const fitted = Math.min(metrics.stub, span * 0.45, span / 2);
|
|
1278
|
+
fromStubLen = fitted;
|
|
1279
|
+
toStubLen = fitted;
|
|
1280
|
+
}
|
|
1281
|
+
} else if (!sourceHorizontal && !targetHorizontal && fromNormal.y === -toNormal.y) {
|
|
1282
|
+
const span = (toPoint.y - fromPoint.y) * fromNormal.y;
|
|
1283
|
+
if (span > 0) {
|
|
1284
|
+
const fitted = Math.min(metrics.stub, span * 0.45, span / 2);
|
|
1285
|
+
fromStubLen = fitted;
|
|
1286
|
+
toStubLen = fitted;
|
|
1287
|
+
}
|
|
1288
|
+
} else if (sourceHorizontal !== targetHorizontal) {
|
|
1289
|
+
const spanFrom = sourceHorizontal ? (toPoint.x - fromPoint.x) * fromNormal.x : (toPoint.y - fromPoint.y) * fromNormal.y;
|
|
1290
|
+
const spanTo = targetHorizontal ? (fromPoint.x - toPoint.x) * toNormal.x : (fromPoint.y - toPoint.y) * toNormal.y;
|
|
1291
|
+
if (spanFrom > 0 && spanFrom < metrics.stub) {
|
|
1292
|
+
fromStubLen = Math.min(spanFrom / 2, Math.max(minStub, spanFrom * 0.5));
|
|
1293
|
+
}
|
|
1294
|
+
if (spanTo > 0 && spanTo < metrics.stub) {
|
|
1295
|
+
toStubLen = Math.min(spanTo / 2, Math.max(minStub, spanTo * 0.5));
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
const fromStub = offsetPoint(fromPoint, fromNormal, fromStubLen);
|
|
1299
|
+
const toStub = offsetPoint(toPoint, toNormal, toStubLen);
|
|
1109
1300
|
const mids = [];
|
|
1110
1301
|
if (sourceHorizontal && targetHorizontal) {
|
|
1111
1302
|
const facing = fromNormal.x === -toNormal.x;
|
|
@@ -1143,13 +1334,13 @@ function getOrthogonalPoints(fromPoint, toPoint, fromPort, toPort, fromRect, toR
|
|
|
1143
1334
|
);
|
|
1144
1335
|
return points;
|
|
1145
1336
|
}
|
|
1146
|
-
function buildOrthogonalPath(fromPoint, toPoint, fromPort = "right", toPort = "left", fromRect = null, toRect = null) {
|
|
1337
|
+
function buildOrthogonalPath(fromPoint, toPoint, fromPort = "right", toPort = "left", fromRect = null, toRect = null, metrics = routeMetrics(1)) {
|
|
1147
1338
|
let points;
|
|
1148
1339
|
if (fromRect && toRect) {
|
|
1149
|
-
points = getOrthogonalPoints(fromPoint, toPoint, fromPort, toPort, fromRect, toRect);
|
|
1340
|
+
points = getOrthogonalPoints(fromPoint, toPoint, fromPort, toPort, fromRect, toRect, metrics);
|
|
1150
1341
|
} else {
|
|
1151
|
-
const fromStub = offsetPoint(fromPoint, getPortNormal(fromPort),
|
|
1152
|
-
const toStub = offsetPoint(toPoint, getPortNormal(toPort),
|
|
1342
|
+
const fromStub = offsetPoint(fromPoint, getPortNormal(fromPort), metrics.stub);
|
|
1343
|
+
const toStub = offsetPoint(toPoint, getPortNormal(toPort), metrics.stub);
|
|
1153
1344
|
points = [];
|
|
1154
1345
|
[
|
|
1155
1346
|
fromPoint,
|
|
@@ -1161,7 +1352,7 @@ function buildOrthogonalPath(fromPoint, toPoint, fromPort = "right", toPort = "l
|
|
|
1161
1352
|
}
|
|
1162
1353
|
const label = getPolylineLabelPoint(points);
|
|
1163
1354
|
return {
|
|
1164
|
-
d: buildRoundedPath(points,
|
|
1355
|
+
d: buildRoundedPath(points, metrics.corner),
|
|
1165
1356
|
labelX: label.x,
|
|
1166
1357
|
labelY: label.y
|
|
1167
1358
|
};
|
|
@@ -1172,6 +1363,7 @@ function buildEdgePath(edge, fromNode = null, toNode = null) {
|
|
|
1172
1363
|
const fromPoint = edge?.fromPoint || (fromNode ? getPortPosition(fromNode, fromPort) : null);
|
|
1173
1364
|
const toPoint = edge?.toPoint || (toNode ? getPortPosition(toNode, toPort) : null);
|
|
1174
1365
|
const route = normalizeEdgeRoute(edge?.route);
|
|
1366
|
+
const metrics = routeMetrics(edgeVisualScale(fromNode, toNode));
|
|
1175
1367
|
if (!fromPoint || !toPoint) {
|
|
1176
1368
|
return { d: "", labelX: 0, labelY: 0 };
|
|
1177
1369
|
}
|
|
@@ -1179,15 +1371,17 @@ function buildEdgePath(edge, fromNode = null, toNode = null) {
|
|
|
1179
1371
|
return buildStraightPath(fromPoint, toPoint);
|
|
1180
1372
|
}
|
|
1181
1373
|
if (route === "orthogonal") {
|
|
1182
|
-
const fromRect = !edge?.fromPoint && fromNode ? getNodeRectBounds(fromNode,
|
|
1183
|
-
const toRect = !edge?.toPoint && toNode ? getNodeRectBounds(toNode,
|
|
1184
|
-
return buildOrthogonalPath(fromPoint, toPoint, fromPort, toPort, fromRect, toRect);
|
|
1374
|
+
const fromRect = !edge?.fromPoint && fromNode ? getNodeRectBounds(fromNode, metrics.clearance) : null;
|
|
1375
|
+
const toRect = !edge?.toPoint && toNode ? getNodeRectBounds(toNode, metrics.clearance) : null;
|
|
1376
|
+
return buildOrthogonalPath(fromPoint, toPoint, fromPort, toPort, fromRect, toRect, metrics);
|
|
1185
1377
|
}
|
|
1186
|
-
return buildCurvePath(fromPoint, toPoint, fromPort, toPort);
|
|
1378
|
+
return buildCurvePath(fromPoint, toPoint, fromPort, toPort, metrics);
|
|
1187
1379
|
}
|
|
1188
|
-
function createArrowMarker(id, strokeWidth, reversed = false) {
|
|
1189
|
-
const
|
|
1190
|
-
const
|
|
1380
|
+
function createArrowMarker(id, strokeWidth, reversed = false, edgeScale = 1) {
|
|
1381
|
+
const scale = clamp(toFiniteNumber(edgeScale, 1), 0.01, 1);
|
|
1382
|
+
const effective = Math.max(0.02, strokeWidth * scale);
|
|
1383
|
+
const size = formatNumber(Math.max(9 * scale, effective * 4.4));
|
|
1384
|
+
const refInset = formatNumber(Math.max(1.5 * scale, effective * 0.85));
|
|
1191
1385
|
const marker = svgEl("marker", {
|
|
1192
1386
|
id,
|
|
1193
1387
|
markerWidth: size,
|
|
@@ -1205,9 +1399,11 @@ function createArrowMarker(id, strokeWidth, reversed = false) {
|
|
|
1205
1399
|
);
|
|
1206
1400
|
return marker;
|
|
1207
1401
|
}
|
|
1208
|
-
function createDotMarker(id, strokeWidth) {
|
|
1209
|
-
const
|
|
1210
|
-
const
|
|
1402
|
+
function createDotMarker(id, strokeWidth, edgeScale = 1) {
|
|
1403
|
+
const scale = clamp(toFiniteNumber(edgeScale, 1), 0.01, 1);
|
|
1404
|
+
const effective = Math.max(0.02, strokeWidth * scale);
|
|
1405
|
+
const size = formatNumber(Math.max(8 * scale, effective * 3.3));
|
|
1406
|
+
const radius = formatNumber(Math.max(2.75 * scale, effective * 1.45));
|
|
1211
1407
|
const marker = svgEl("marker", {
|
|
1212
1408
|
id,
|
|
1213
1409
|
markerWidth: size,
|
|
@@ -1464,7 +1660,23 @@ var VdFlowchart = class {
|
|
|
1464
1660
|
this.element = resolveElement(options.element || options.target);
|
|
1465
1661
|
this.readonly = Boolean(options.readonly);
|
|
1466
1662
|
this.gridSize = clamp(toFiniteNumber(options.gridSize, DEFAULT_GRID_SIZE), 12, 64);
|
|
1467
|
-
|
|
1663
|
+
const scaleBounds = normalizeBoundPair(
|
|
1664
|
+
options.minScale,
|
|
1665
|
+
options.maxScale,
|
|
1666
|
+
DEFAULT_MIN_SCALE,
|
|
1667
|
+
DEFAULT_MAX_SCALE
|
|
1668
|
+
);
|
|
1669
|
+
const sizeBounds = normalizeBoundPair(
|
|
1670
|
+
options.minNodeSize,
|
|
1671
|
+
options.maxNodeSize,
|
|
1672
|
+
DEFAULT_MIN_NODE_SIZE,
|
|
1673
|
+
DEFAULT_MAX_NODE_SIZE
|
|
1674
|
+
);
|
|
1675
|
+
this.minScale = scaleBounds.min;
|
|
1676
|
+
this.maxScale = scaleBounds.max;
|
|
1677
|
+
this.minNodeSize = sizeBounds.min;
|
|
1678
|
+
this.maxNodeSize = sizeBounds.max;
|
|
1679
|
+
this.documentData = normalizeDocument(options.data || {}, { scaleBounds, sizeBounds });
|
|
1468
1680
|
this.listeners = {};
|
|
1469
1681
|
this.selection = null;
|
|
1470
1682
|
this.selectedNodeIds = /* @__PURE__ */ new Set();
|
|
@@ -1507,6 +1719,7 @@ var VdFlowchart = class {
|
|
|
1507
1719
|
this.handleClick = this.handleClick.bind(this);
|
|
1508
1720
|
this.handleDoubleClick = this.handleDoubleClick.bind(this);
|
|
1509
1721
|
this.handleWheel = this.handleWheel.bind(this);
|
|
1722
|
+
this.handleCanvasBlur = this.handleCanvasBlur.bind(this);
|
|
1510
1723
|
this.handleKeyDown = this.handleKeyDown.bind(this);
|
|
1511
1724
|
this.handleSelectionFieldInput = this.handleSelectionFieldInput.bind(this);
|
|
1512
1725
|
this.handleSelectionFieldChange = this.handleSelectionFieldChange.bind(this);
|
|
@@ -1692,10 +1905,10 @@ var VdFlowchart = class {
|
|
|
1692
1905
|
this.world = svgEl("g", { class: "vd-flowchart-world" });
|
|
1693
1906
|
this.gridRect = svgEl("rect", {
|
|
1694
1907
|
class: "vd-flowchart-grid",
|
|
1695
|
-
x: -
|
|
1696
|
-
y: -
|
|
1697
|
-
width:
|
|
1698
|
-
height:
|
|
1908
|
+
x: -DEFAULT_WORLD_EXTENT / 2,
|
|
1909
|
+
y: -DEFAULT_WORLD_EXTENT / 2,
|
|
1910
|
+
width: DEFAULT_WORLD_EXTENT,
|
|
1911
|
+
height: DEFAULT_WORLD_EXTENT,
|
|
1699
1912
|
fill: `url(#${this.gridPatternId})`
|
|
1700
1913
|
});
|
|
1701
1914
|
this.edgesLayer = svgEl("g", { class: "vd-flowchart-edges" });
|
|
@@ -1919,6 +2132,7 @@ var VdFlowchart = class {
|
|
|
1919
2132
|
this.canvasEl.addEventListener("click", this.handleClick);
|
|
1920
2133
|
this.canvasEl.addEventListener("dblclick", this.handleDoubleClick);
|
|
1921
2134
|
this.canvasEl.addEventListener("wheel", this.handleWheel, { passive: false });
|
|
2135
|
+
this.canvasEl.addEventListener("blur", this.handleCanvasBlur);
|
|
1922
2136
|
this.root.addEventListener("keydown", this.handleKeyDown, true);
|
|
1923
2137
|
window.addEventListener("pointerup", this.handlePointerUp);
|
|
1924
2138
|
window.addEventListener("resize", this.handleResize);
|
|
@@ -1937,6 +2151,7 @@ var VdFlowchart = class {
|
|
|
1937
2151
|
this.canvasEl.removeEventListener("click", this.handleClick);
|
|
1938
2152
|
this.canvasEl.removeEventListener("dblclick", this.handleDoubleClick);
|
|
1939
2153
|
this.canvasEl.removeEventListener("wheel", this.handleWheel);
|
|
2154
|
+
this.canvasEl.removeEventListener("blur", this.handleCanvasBlur);
|
|
1940
2155
|
this.root.removeEventListener("keydown", this.handleKeyDown, true);
|
|
1941
2156
|
window.removeEventListener("pointerup", this.handlePointerUp);
|
|
1942
2157
|
window.removeEventListener("resize", this.handleResize);
|
|
@@ -1945,6 +2160,19 @@ var VdFlowchart = class {
|
|
|
1945
2160
|
if (this.destroyed) return;
|
|
1946
2161
|
this.render({ inspector: false, json: false });
|
|
1947
2162
|
}
|
|
2163
|
+
/**
|
|
2164
|
+
* Focus the diagram canvas. Quiet focus returns from an inline editor without
|
|
2165
|
+
* painting the full-canvas `:focus-visible` ring (see `.is-quiet-focus`).
|
|
2166
|
+
*/
|
|
2167
|
+
focusCanvas(options = {}) {
|
|
2168
|
+
if (!this.canvasEl || this.destroyed) return;
|
|
2169
|
+
const quiet = options.quiet === true;
|
|
2170
|
+
this.canvasEl.classList.toggle("is-quiet-focus", quiet);
|
|
2171
|
+
this.canvasEl.focus({ preventScroll: true });
|
|
2172
|
+
}
|
|
2173
|
+
handleCanvasBlur() {
|
|
2174
|
+
this.canvasEl?.classList.remove("is-quiet-focus");
|
|
2175
|
+
}
|
|
1948
2176
|
updatePaletteState() {
|
|
1949
2177
|
const arrowArmed = this.activeTool === "arrow";
|
|
1950
2178
|
this.paletteGrid.querySelectorAll(".vd-flowchart-palette-btn").forEach((button) => {
|
|
@@ -2478,7 +2706,7 @@ var VdFlowchart = class {
|
|
|
2478
2706
|
const width = this.canvasEl.clientWidth || 800;
|
|
2479
2707
|
const height = this.canvasEl.clientHeight || 560;
|
|
2480
2708
|
this.scaleAround(
|
|
2481
|
-
clamp(scale,
|
|
2709
|
+
clamp(formatScale(scale), this.minScale, this.maxScale) / viewport.scale,
|
|
2482
2710
|
width / 2,
|
|
2483
2711
|
height / 2,
|
|
2484
2712
|
"viewport:zoom"
|
|
@@ -2513,7 +2741,8 @@ var VdFlowchart = class {
|
|
|
2513
2741
|
const node = normalizeNode(
|
|
2514
2742
|
{ ...data, id: void 0, x: data.x + 24, y: data.y + 24 },
|
|
2515
2743
|
this.documentData.nodes.length + index,
|
|
2516
|
-
usedNodeIds
|
|
2744
|
+
usedNodeIds,
|
|
2745
|
+
this.nodeSizeBounds()
|
|
2517
2746
|
);
|
|
2518
2747
|
idMap.set(data.id, node.id);
|
|
2519
2748
|
return node;
|
|
@@ -2545,7 +2774,8 @@ var VdFlowchart = class {
|
|
|
2545
2774
|
const node = normalizeNode(
|
|
2546
2775
|
{ ...data, id: void 0, collapsed: void 0, x: data.x + 24, y: data.y + 24 },
|
|
2547
2776
|
this.documentData.nodes.length,
|
|
2548
|
-
usedIds
|
|
2777
|
+
usedIds,
|
|
2778
|
+
this.nodeSizeBounds()
|
|
2549
2779
|
);
|
|
2550
2780
|
this.documentData.nodes.push(node);
|
|
2551
2781
|
this.select({ kind: "node", id: node.id });
|
|
@@ -3159,14 +3389,15 @@ var VdFlowchart = class {
|
|
|
3159
3389
|
if (this.activeTool === "arrow") return true;
|
|
3160
3390
|
return this.selection?.kind === "node" && this.selection.id === node.id && !this.hasMultiSelection();
|
|
3161
3391
|
}
|
|
3162
|
-
getMarkerId(markerType, position, strokeWidth) {
|
|
3392
|
+
getMarkerId(markerType, position, strokeWidth, edgeScale = 1) {
|
|
3163
3393
|
const width = normalizeEdgeStrokeWidth(strokeWidth);
|
|
3164
|
-
const
|
|
3394
|
+
const scale = clamp(formatNumber(Math.max(edgeScale, 0.01)), 0.01, 1);
|
|
3395
|
+
const key = `${markerType}:${position}:${width}:${scale}`;
|
|
3165
3396
|
if (this.markerIds.has(key)) {
|
|
3166
3397
|
return this.markerIds.get(key);
|
|
3167
3398
|
}
|
|
3168
3399
|
const markerId = nextId(`flowchart-${markerType}-${position}`);
|
|
3169
|
-
const marker = markerType === "dot" ? createDotMarker(markerId, width) : createArrowMarker(markerId, width, position === "start");
|
|
3400
|
+
const marker = markerType === "dot" ? createDotMarker(markerId, width, scale) : createArrowMarker(markerId, width, position === "start", scale);
|
|
3170
3401
|
this.markerDefs.appendChild(marker);
|
|
3171
3402
|
this.markerIds.set(key, markerId);
|
|
3172
3403
|
return markerId;
|
|
@@ -3174,8 +3405,15 @@ var VdFlowchart = class {
|
|
|
3174
3405
|
getEdgeStrokeWidth(edge) {
|
|
3175
3406
|
return normalizeEdgeStrokeWidth(edge?.strokeWidth);
|
|
3176
3407
|
}
|
|
3177
|
-
getEdgeHitStrokeWidth(edge) {
|
|
3178
|
-
|
|
3408
|
+
getEdgeHitStrokeWidth(edge, edgeScale = 1) {
|
|
3409
|
+
const visual = clamp(toFiniteNumber(edgeScale, 1), 0.01, 1);
|
|
3410
|
+
const base = this.getEdgeStrokeWidth(edge);
|
|
3411
|
+
const view = Math.max(this.documentData?.viewport?.scale || 1, 1e-4);
|
|
3412
|
+
const legacy = Math.max(EDGE_HIT_STROKE_MIN, base + 12);
|
|
3413
|
+
if (visual >= 0.999 && view >= DEFAULT_MIN_SCALE && view <= DEFAULT_MAX_SCALE) {
|
|
3414
|
+
return formatNumber(legacy);
|
|
3415
|
+
}
|
|
3416
|
+
return formatNumber(Math.max(base * visual, EDGE_HIT_STROKE_MIN / view));
|
|
3179
3417
|
}
|
|
3180
3418
|
findConnectionTarget(worldPoint, excludeNodeId, previousSnap = null, referencePoint = null) {
|
|
3181
3419
|
const snapPadding = CONNECTION_SNAP_PADDING / this.documentData.viewport.scale;
|
|
@@ -3232,17 +3470,37 @@ var VdFlowchart = class {
|
|
|
3232
3470
|
let width = original.width;
|
|
3233
3471
|
let height = original.height;
|
|
3234
3472
|
if (handle.includes("e")) {
|
|
3235
|
-
width = clampNodeWidth(
|
|
3473
|
+
width = clampNodeWidth(
|
|
3474
|
+
nextType,
|
|
3475
|
+
original.width + deltaX,
|
|
3476
|
+
original.width,
|
|
3477
|
+
this.nodeSizeBounds()
|
|
3478
|
+
);
|
|
3236
3479
|
}
|
|
3237
3480
|
if (handle.includes("s")) {
|
|
3238
|
-
height = clampNodeHeight(
|
|
3481
|
+
height = clampNodeHeight(
|
|
3482
|
+
nextType,
|
|
3483
|
+
original.height + deltaY,
|
|
3484
|
+
original.height,
|
|
3485
|
+
this.nodeSizeBounds()
|
|
3486
|
+
);
|
|
3239
3487
|
}
|
|
3240
3488
|
if (handle.includes("w")) {
|
|
3241
|
-
width = clampNodeWidth(
|
|
3489
|
+
width = clampNodeWidth(
|
|
3490
|
+
nextType,
|
|
3491
|
+
original.width - deltaX,
|
|
3492
|
+
original.width,
|
|
3493
|
+
this.nodeSizeBounds()
|
|
3494
|
+
);
|
|
3242
3495
|
x = original.x + original.width - width;
|
|
3243
3496
|
}
|
|
3244
3497
|
if (handle.includes("n")) {
|
|
3245
|
-
height = clampNodeHeight(
|
|
3498
|
+
height = clampNodeHeight(
|
|
3499
|
+
nextType,
|
|
3500
|
+
original.height - deltaY,
|
|
3501
|
+
original.height,
|
|
3502
|
+
this.nodeSizeBounds()
|
|
3503
|
+
);
|
|
3246
3504
|
y = original.y + original.height - height;
|
|
3247
3505
|
}
|
|
3248
3506
|
return {
|
|
@@ -3278,7 +3536,7 @@ var VdFlowchart = class {
|
|
|
3278
3536
|
scaleAround(factor, localX, localY, reason) {
|
|
3279
3537
|
const viewport = this.documentData.viewport;
|
|
3280
3538
|
const anchor = this.localToWorld(localX, localY);
|
|
3281
|
-
const nextScale = clamp(
|
|
3539
|
+
const nextScale = clamp(formatScale(viewport.scale * factor), this.minScale, this.maxScale);
|
|
3282
3540
|
if (nextScale === viewport.scale) return;
|
|
3283
3541
|
viewport.scale = nextScale;
|
|
3284
3542
|
viewport.x = formatNumber(localX - anchor.x * nextScale);
|
|
@@ -3370,7 +3628,7 @@ var VdFlowchart = class {
|
|
|
3370
3628
|
if (event.key === "Escape" || event.key === "Enter") {
|
|
3371
3629
|
event.preventDefault();
|
|
3372
3630
|
this.stopTextEdit({ commit: event.key === "Enter" });
|
|
3373
|
-
this.
|
|
3631
|
+
this.focusCanvas({ quiet: true });
|
|
3374
3632
|
}
|
|
3375
3633
|
});
|
|
3376
3634
|
textarea.addEventListener("blur", () => {
|
|
@@ -3397,7 +3655,7 @@ var VdFlowchart = class {
|
|
|
3397
3655
|
if (event.key === "Escape") {
|
|
3398
3656
|
event.preventDefault();
|
|
3399
3657
|
this.stopTextEdit({ commit: false });
|
|
3400
|
-
this.
|
|
3658
|
+
this.focusCanvas({ quiet: true });
|
|
3401
3659
|
return;
|
|
3402
3660
|
}
|
|
3403
3661
|
if (event.key === "Enter") {
|
|
@@ -3406,13 +3664,12 @@ var VdFlowchart = class {
|
|
|
3406
3664
|
if (!commits) return;
|
|
3407
3665
|
event.preventDefault();
|
|
3408
3666
|
this.stopTextEdit({ commit: true });
|
|
3409
|
-
this.
|
|
3667
|
+
this.focusCanvas({ quiet: true });
|
|
3410
3668
|
return;
|
|
3411
3669
|
}
|
|
3412
3670
|
if (event.key === "Tab" && mindmap && !event.shiftKey && !event.altKey && !event.ctrlKey) {
|
|
3413
3671
|
event.preventDefault();
|
|
3414
3672
|
this.stopTextEdit({ commit: true });
|
|
3415
|
-
this.canvasEl.focus();
|
|
3416
3673
|
this.insertBranchNode(node.id, { edit: true });
|
|
3417
3674
|
}
|
|
3418
3675
|
}
|
|
@@ -3461,19 +3718,20 @@ var VdFlowchart = class {
|
|
|
3461
3718
|
}
|
|
3462
3719
|
const viewport = this.documentData.viewport;
|
|
3463
3720
|
const scale = viewport.scale;
|
|
3721
|
+
const visual = nodeVisualScale(node);
|
|
3464
3722
|
const textarea = this.textEditor.textarea;
|
|
3465
3723
|
const width = node.width * scale;
|
|
3466
3724
|
const height = node.height * scale;
|
|
3467
3725
|
const metrics = getNodeFontMetrics(node);
|
|
3468
|
-
const lineHeight = metrics.lineHeight * scale;
|
|
3726
|
+
const lineHeight = metrics.lineHeight * visual * scale;
|
|
3469
3727
|
const lineCount = Math.max(1, textarea.value.split(/\r?\n/).length);
|
|
3470
|
-
const horizontalPadding = (node.type === "textbox" ? 16 : 12) * scale;
|
|
3471
|
-
const verticalPadding = node.type === "textbox" ? 12 * scale : Math.max(4, (height - lineCount * lineHeight) / 2);
|
|
3728
|
+
const horizontalPadding = (node.type === "textbox" ? 16 : 12) * visual * scale;
|
|
3729
|
+
const verticalPadding = node.type === "textbox" ? 12 * visual * scale : Math.max(4 * visual, (height - lineCount * lineHeight) / 2);
|
|
3472
3730
|
textarea.style.left = `${formatNumber(viewport.x + node.x * scale)}px`;
|
|
3473
3731
|
textarea.style.top = `${formatNumber(viewport.y + node.y * scale)}px`;
|
|
3474
3732
|
textarea.style.width = `${formatNumber(width)}px`;
|
|
3475
3733
|
textarea.style.height = `${formatNumber(height)}px`;
|
|
3476
|
-
textarea.style.fontSize = `${formatNumber(metrics.fontSize * scale)}px`;
|
|
3734
|
+
textarea.style.fontSize = `${formatNumber(metrics.fontSize * visual * scale)}px`;
|
|
3477
3735
|
textarea.style.lineHeight = `${formatNumber(lineHeight)}px`;
|
|
3478
3736
|
textarea.style.padding = `${formatNumber(verticalPadding)}px ${formatNumber(horizontalPadding)}px`;
|
|
3479
3737
|
}
|
|
@@ -3485,14 +3743,16 @@ var VdFlowchart = class {
|
|
|
3485
3743
|
this.stopTextEdit({ commit: false });
|
|
3486
3744
|
return;
|
|
3487
3745
|
}
|
|
3746
|
+
const visual = edgeVisualScale(fromNode, toNode);
|
|
3488
3747
|
const { labelX, labelY } = buildEdgePath(edge, fromNode, toNode);
|
|
3489
3748
|
const viewport = this.documentData.viewport;
|
|
3490
3749
|
const textarea = this.textEditor.textarea;
|
|
3491
|
-
const fontSize = Math.max(11, 13 * viewport.scale);
|
|
3492
|
-
const width = clamp(textarea.value.length * fontSize * 0.62 + 28, 96, 280);
|
|
3493
|
-
const height = fontSize * 1.4 + 12;
|
|
3750
|
+
const fontSize = Math.max(11 * visual, 13 * visual * viewport.scale);
|
|
3751
|
+
const width = clamp(textarea.value.length * fontSize * 0.62 + 28 * visual, 96 * visual, 280);
|
|
3752
|
+
const height = fontSize * 1.4 + 12 * visual;
|
|
3753
|
+
const labelOffset = 8 * visual;
|
|
3494
3754
|
textarea.style.left = `${formatNumber(viewport.x + labelX * viewport.scale - width / 2)}px`;
|
|
3495
|
-
textarea.style.top = `${formatNumber(viewport.y + (labelY -
|
|
3755
|
+
textarea.style.top = `${formatNumber(viewport.y + (labelY - labelOffset) * viewport.scale - height / 2)}px`;
|
|
3496
3756
|
textarea.style.width = `${formatNumber(width)}px`;
|
|
3497
3757
|
textarea.style.height = `${formatNumber(height)}px`;
|
|
3498
3758
|
textarea.style.fontSize = `${formatNumber(fontSize)}px`;
|
|
@@ -3501,6 +3761,7 @@ var VdFlowchart = class {
|
|
|
3501
3761
|
}
|
|
3502
3762
|
render(options = {}) {
|
|
3503
3763
|
if (this.destroyed) return;
|
|
3764
|
+
this.syncGrid();
|
|
3504
3765
|
const shouldRenderScene = options.scene !== false;
|
|
3505
3766
|
const shouldRenderInspector = options.inspector !== false;
|
|
3506
3767
|
const shouldRenderJson = options.json !== false;
|
|
@@ -3524,6 +3785,8 @@ var VdFlowchart = class {
|
|
|
3524
3785
|
clearChildren(this.previewLayer);
|
|
3525
3786
|
clearChildren(this.nodesLayer);
|
|
3526
3787
|
clearChildren(this.overlayLayer);
|
|
3788
|
+
clearChildren(this.markerDefs);
|
|
3789
|
+
this.markerIds = /* @__PURE__ */ new Map();
|
|
3527
3790
|
const hidden = this.getHiddenNodeIds();
|
|
3528
3791
|
this.sceneHidden = hidden;
|
|
3529
3792
|
this.sceneOutgoing = this.getOutgoingMap();
|
|
@@ -3587,17 +3850,18 @@ var VdFlowchart = class {
|
|
|
3587
3850
|
}
|
|
3588
3851
|
}
|
|
3589
3852
|
}
|
|
3590
|
-
applyEdgeMarkers(pathElement, edge) {
|
|
3853
|
+
applyEdgeMarkers(pathElement, edge, edgeScale = 1) {
|
|
3854
|
+
const width = this.getEdgeStrokeWidth(edge);
|
|
3591
3855
|
if (edge.startMarker && edge.startMarker !== "none") {
|
|
3592
3856
|
pathElement.setAttribute(
|
|
3593
3857
|
"marker-start",
|
|
3594
|
-
`url(#${this.getMarkerId(edge.startMarker, "start",
|
|
3858
|
+
`url(#${this.getMarkerId(edge.startMarker, "start", width, edgeScale)})`
|
|
3595
3859
|
);
|
|
3596
3860
|
}
|
|
3597
3861
|
if (edge.endMarker && edge.endMarker !== "none") {
|
|
3598
3862
|
pathElement.setAttribute(
|
|
3599
3863
|
"marker-end",
|
|
3600
|
-
`url(#${this.getMarkerId(edge.endMarker, "end",
|
|
3864
|
+
`url(#${this.getMarkerId(edge.endMarker, "end", width, edgeScale)})`
|
|
3601
3865
|
);
|
|
3602
3866
|
}
|
|
3603
3867
|
}
|
|
@@ -3605,6 +3869,7 @@ var VdFlowchart = class {
|
|
|
3605
3869
|
const fromNode = nodeMap.get(edge.from.nodeId);
|
|
3606
3870
|
const toNode = nodeMap.get(edge.to.nodeId);
|
|
3607
3871
|
if (!fromNode || !toNode) return null;
|
|
3872
|
+
const visual = edgeVisualScale(fromNode, toNode);
|
|
3608
3873
|
const edgePath = buildEdgePath(edge, fromNode, toNode);
|
|
3609
3874
|
const selected = this.selection?.kind === "edge" && this.selection.id === edge.id;
|
|
3610
3875
|
const inReconnectMode = this.reconnectEdgeId === edge.id;
|
|
@@ -3612,13 +3877,14 @@ var VdFlowchart = class {
|
|
|
3612
3877
|
class: `vd-flowchart-edge${selected ? " is-selected" : ""}${inReconnectMode ? " is-reconnecting" : ""}`,
|
|
3613
3878
|
"data-edge-id": edge.id
|
|
3614
3879
|
});
|
|
3615
|
-
const
|
|
3880
|
+
const baseStroke = this.getEdgeStrokeWidth(edge);
|
|
3881
|
+
const strokeWidth = visual < 0.999 ? formatNumber(Math.max(0.02, baseStroke * visual)) : baseStroke;
|
|
3616
3882
|
if (selected) {
|
|
3617
3883
|
group.appendChild(
|
|
3618
3884
|
svgEl("path", {
|
|
3619
3885
|
class: "vd-flowchart-edge-selection",
|
|
3620
3886
|
d: edgePath.d,
|
|
3621
|
-
"stroke-width": formatNumber(strokeWidth + 7)
|
|
3887
|
+
"stroke-width": visual < 0.999 ? formatNumber((baseStroke + 7) * visual) : formatNumber(strokeWidth + 7)
|
|
3622
3888
|
})
|
|
3623
3889
|
);
|
|
3624
3890
|
}
|
|
@@ -3628,23 +3894,28 @@ var VdFlowchart = class {
|
|
|
3628
3894
|
"stroke-width": strokeWidth,
|
|
3629
3895
|
"data-edge-id": edge.id
|
|
3630
3896
|
});
|
|
3631
|
-
this.applyEdgeMarkers(visiblePath, edge);
|
|
3897
|
+
this.applyEdgeMarkers(visiblePath, edge, visual);
|
|
3632
3898
|
const hitPath = svgEl("path", {
|
|
3633
3899
|
class: "vd-flowchart-edge-hit",
|
|
3634
3900
|
d: edgePath.d,
|
|
3635
|
-
"stroke-width": this.getEdgeHitStrokeWidth(edge),
|
|
3901
|
+
"stroke-width": this.getEdgeHitStrokeWidth(edge, visual),
|
|
3636
3902
|
"data-edge-id": edge.id
|
|
3637
3903
|
});
|
|
3638
3904
|
group.appendChild(visiblePath);
|
|
3639
3905
|
group.appendChild(hitPath);
|
|
3640
3906
|
if (edge.label && this.textEditor?.edgeId !== edge.id) {
|
|
3907
|
+
const labelOffset = visual < 0.999 ? formatNumber(8 * visual) : 8;
|
|
3641
3908
|
const label = svgEl("text", {
|
|
3642
3909
|
class: "vd-flowchart-edge-label",
|
|
3643
3910
|
x: edgePath.labelX,
|
|
3644
|
-
y: edgePath.labelY -
|
|
3911
|
+
y: edgePath.labelY - labelOffset,
|
|
3645
3912
|
"text-anchor": "middle",
|
|
3646
3913
|
"data-edge-id": edge.id
|
|
3647
3914
|
});
|
|
3915
|
+
if (visual < 0.999) {
|
|
3916
|
+
label.style.fontSize = `${formatNumber(Math.max(0.06, 12 * visual))}px`;
|
|
3917
|
+
label.style.strokeWidth = `${formatNumber(Math.max(0.03, 5 * visual))}px`;
|
|
3918
|
+
}
|
|
3648
3919
|
label.textContent = edge.label;
|
|
3649
3920
|
group.appendChild(label);
|
|
3650
3921
|
}
|
|
@@ -3683,18 +3954,26 @@ var VdFlowchart = class {
|
|
|
3683
3954
|
renderPreviewEdge(interaction) {
|
|
3684
3955
|
const fromPort = interaction.fromPort || interaction.source?.port || "right";
|
|
3685
3956
|
const toPort = interaction.toPort || interaction.target?.port || "left";
|
|
3686
|
-
const
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3957
|
+
const fromNode = interaction.source?.nodeId ? this.findNode(interaction.source.nodeId) : null;
|
|
3958
|
+
const toNode = interaction.target?.nodeId ? this.findNode(interaction.target.nodeId) : null;
|
|
3959
|
+
const visual = edgeVisualScale(fromNode, toNode);
|
|
3960
|
+
const edgePath = buildEdgePath(
|
|
3961
|
+
{
|
|
3962
|
+
strokeWidth: interaction.strokeWidth,
|
|
3963
|
+
route: interaction.route,
|
|
3964
|
+
fromPoint: interaction.fromPoint,
|
|
3965
|
+
toPoint: interaction.toPoint,
|
|
3966
|
+
from: { port: fromPort },
|
|
3967
|
+
to: { port: toPort }
|
|
3968
|
+
},
|
|
3969
|
+
fromNode,
|
|
3970
|
+
toNode
|
|
3971
|
+
);
|
|
3972
|
+
const baseStroke = this.getEdgeStrokeWidth(interaction);
|
|
3694
3973
|
return svgEl("path", {
|
|
3695
3974
|
class: "vd-flowchart-preview-path",
|
|
3696
3975
|
d: edgePath.d,
|
|
3697
|
-
"stroke-width":
|
|
3976
|
+
"stroke-width": visual < 0.999 ? formatNumber(Math.max(0.02, baseStroke * visual)) : baseStroke
|
|
3698
3977
|
});
|
|
3699
3978
|
}
|
|
3700
3979
|
renderNode(node) {
|
|
@@ -3709,17 +3988,21 @@ var VdFlowchart = class {
|
|
|
3709
3988
|
"data-node-id": node.id
|
|
3710
3989
|
});
|
|
3711
3990
|
const portsVisible = this.shouldShowNodePorts(node);
|
|
3991
|
+
const visual = nodeVisualScale(node);
|
|
3992
|
+
const corner = node.type === "rounded-rect" || node.type === "textbox" ? 16 : node.type === "junction" ? node.width / 2 : 6;
|
|
3712
3993
|
const hitbox = svgEl("rect", {
|
|
3713
3994
|
x: 0,
|
|
3714
3995
|
y: 0,
|
|
3715
3996
|
width: node.width,
|
|
3716
3997
|
height: node.height,
|
|
3717
|
-
rx:
|
|
3998
|
+
rx: visual < 0.999 && node.type !== "junction" ? formatNumber(corner * visual) : corner,
|
|
3718
3999
|
fill: "transparent",
|
|
3719
4000
|
"data-node-id": node.id
|
|
3720
4001
|
});
|
|
3721
4002
|
group.appendChild(hitbox);
|
|
3722
|
-
|
|
4003
|
+
const shape = this.renderNodeShape(node);
|
|
4004
|
+
this.applyScaledNodeShape(node, shape);
|
|
4005
|
+
group.appendChild(shape);
|
|
3723
4006
|
group.appendChild(this.renderNodeText(node));
|
|
3724
4007
|
const scale = this.documentData.viewport.scale || 1;
|
|
3725
4008
|
if (soloSelected && !this.readonly && SELECTION_BOX_TYPES.has(node.type)) {
|
|
@@ -3911,6 +4194,21 @@ var VdFlowchart = class {
|
|
|
3911
4194
|
});
|
|
3912
4195
|
return group;
|
|
3913
4196
|
}
|
|
4197
|
+
applyScaledNodeShape(node, shape) {
|
|
4198
|
+
const visual = nodeVisualScale(node);
|
|
4199
|
+
if (!shape || visual >= 0.999) return;
|
|
4200
|
+
const baseStroke = node.type === "junction" ? 1.25 : 1.5;
|
|
4201
|
+
shape.style.strokeWidth = String(formatNumber(Math.max(0.03, baseStroke * visual)));
|
|
4202
|
+
if (node.type === "textbox") {
|
|
4203
|
+
shape.style.strokeDasharray = `${formatNumber(8 * visual)} ${formatNumber(5 * visual)}`;
|
|
4204
|
+
}
|
|
4205
|
+
const baseRadius = node.type === "rounded-rect" ? 18 : node.type === "textbox" ? 14 : node.type === "rect" ? 2 : 0;
|
|
4206
|
+
if (baseRadius > 0) {
|
|
4207
|
+
const scaled = String(formatNumber(baseRadius * visual));
|
|
4208
|
+
shape.setAttribute("rx", scaled);
|
|
4209
|
+
shape.setAttribute("ry", scaled);
|
|
4210
|
+
}
|
|
4211
|
+
}
|
|
3914
4212
|
renderNodeShape(node) {
|
|
3915
4213
|
const baseClass = `vd-flowchart-node-shape vd-flowchart-node-shape--${node.type}`;
|
|
3916
4214
|
if (node.type === "rounded-rect") {
|
|
@@ -3987,21 +4285,28 @@ var VdFlowchart = class {
|
|
|
3987
4285
|
if (node.type === "junction" || !node.text) {
|
|
3988
4286
|
return textElement;
|
|
3989
4287
|
}
|
|
4288
|
+
const visual = nodeVisualScale(node);
|
|
4289
|
+
const metrics = getNodeFontMetrics(node);
|
|
4290
|
+
const lineHeight = visual < 0.999 ? Math.max(0.06, metrics.lineHeight * visual) : metrics.lineHeight;
|
|
4291
|
+
if (visual < 0.999) {
|
|
4292
|
+
textElement.style.fontSize = `${formatNumber(Math.max(0.05, metrics.fontSize * visual))}px`;
|
|
4293
|
+
}
|
|
4294
|
+
const equivalentWidth = visual < 0.999 ? node.width / visual : node.width;
|
|
3990
4295
|
if (node.type === "textbox") {
|
|
3991
|
-
const lines2 = wrapText(node.text, estimateChars(
|
|
3992
|
-
const
|
|
4296
|
+
const lines2 = wrapText(node.text, estimateChars(equivalentWidth - 32));
|
|
4297
|
+
const padX = visual < 0.999 ? formatNumber(16 * visual) : 16;
|
|
4298
|
+
const startY2 = visual < 0.999 ? formatNumber(28 * visual) : 28;
|
|
3993
4299
|
lines2.forEach((line, index) => {
|
|
3994
4300
|
const span = svgEl("tspan", {
|
|
3995
|
-
x:
|
|
3996
|
-
y:
|
|
4301
|
+
x: padX,
|
|
4302
|
+
y: visual < 0.999 ? formatNumber(startY2 + index * lineHeight) : startY2 + index * lineHeight
|
|
3997
4303
|
});
|
|
3998
4304
|
span.textContent = line;
|
|
3999
4305
|
textElement.appendChild(span);
|
|
4000
4306
|
});
|
|
4001
4307
|
return textElement;
|
|
4002
4308
|
}
|
|
4003
|
-
const lines = wrapText(node.text, estimateChars(
|
|
4004
|
-
const { lineHeight } = getNodeFontMetrics(node);
|
|
4309
|
+
const lines = wrapText(node.text, estimateChars(equivalentWidth));
|
|
4005
4310
|
const totalHeight = (lines.length - 1) * lineHeight;
|
|
4006
4311
|
const startY = node.height / 2 - totalHeight / 2;
|
|
4007
4312
|
textElement.setAttribute("text-anchor", "middle");
|
|
@@ -4009,7 +4314,7 @@ var VdFlowchart = class {
|
|
|
4009
4314
|
lines.forEach((line, index) => {
|
|
4010
4315
|
const span = svgEl("tspan", {
|
|
4011
4316
|
x: node.width / 2,
|
|
4012
|
-
y: startY + index * lineHeight,
|
|
4317
|
+
y: visual < 0.999 ? formatNumber(startY + index * lineHeight) : startY + index * lineHeight,
|
|
4013
4318
|
"text-anchor": "middle",
|
|
4014
4319
|
"dominant-baseline": "middle"
|
|
4015
4320
|
});
|
|
@@ -4061,12 +4366,12 @@ var VdFlowchart = class {
|
|
|
4061
4366
|
textArea.setAttribute("data-field", "node-text");
|
|
4062
4367
|
const widthInput = createElement("input", { value: node.width, type: "number" });
|
|
4063
4368
|
widthInput.setAttribute("data-field", "node-width");
|
|
4064
|
-
widthInput.setAttribute("min", String(
|
|
4065
|
-
widthInput.setAttribute("max", String(
|
|
4369
|
+
widthInput.setAttribute("min", String(this.minNodeSize));
|
|
4370
|
+
widthInput.setAttribute("max", String(this.maxNodeSize));
|
|
4066
4371
|
const heightInput = createElement("input", { value: node.height, type: "number" });
|
|
4067
4372
|
heightInput.setAttribute("data-field", "node-height");
|
|
4068
|
-
heightInput.setAttribute("min", String(
|
|
4069
|
-
heightInput.setAttribute("max", String(
|
|
4373
|
+
heightInput.setAttribute("min", String(this.minNodeSize));
|
|
4374
|
+
heightInput.setAttribute("max", String(this.maxNodeSize));
|
|
4070
4375
|
this.selectionFields.appendChild(createField("Type", typeSelect));
|
|
4071
4376
|
if (isNodeTextEditable(node)) {
|
|
4072
4377
|
this.selectionFields.appendChild(createField("Text", textArea));
|
|
@@ -4380,8 +4685,8 @@ var VdFlowchart = class {
|
|
|
4380
4685
|
this.reconnectEdgeId = null;
|
|
4381
4686
|
const currentViewport = deepClone(this.documentData.viewport);
|
|
4382
4687
|
const previousSelection = this.selection;
|
|
4383
|
-
this.documentData = normalizeDocument(snapshot);
|
|
4384
|
-
this.documentData.viewport = normalizeViewport(currentViewport);
|
|
4688
|
+
this.documentData = normalizeDocument(snapshot, this.normalizationLimits());
|
|
4689
|
+
this.documentData.viewport = normalizeViewport(currentViewport, this.scaleBounds());
|
|
4385
4690
|
this.selection = this.resolvePreservedSelection(previousSelection, true);
|
|
4386
4691
|
this.render();
|
|
4387
4692
|
this.emitChange(reason);
|
|
@@ -4458,7 +4763,8 @@ var VdFlowchart = class {
|
|
|
4458
4763
|
y: partialNode.y == null ? fallbackY : partialNode.y
|
|
4459
4764
|
},
|
|
4460
4765
|
this.documentData.nodes.length,
|
|
4461
|
-
usedIds
|
|
4766
|
+
usedIds,
|
|
4767
|
+
this.nodeSizeBounds()
|
|
4462
4768
|
);
|
|
4463
4769
|
this.documentData.nodes.push(node);
|
|
4464
4770
|
this.select({ kind: "node", id: node.id });
|
|
@@ -4671,7 +4977,7 @@ var VdFlowchart = class {
|
|
|
4671
4977
|
changed = true;
|
|
4672
4978
|
}
|
|
4673
4979
|
});
|
|
4674
|
-
const rerouted = options.reroutePorts === false ? false : this.rerouteEdgePorts();
|
|
4980
|
+
const rerouted = options.reroutePorts === false ? false : this.rerouteEdgePorts(resolvedMode === "tree" ? options.direction || "right" : null);
|
|
4675
4981
|
if (!changed && !rerouted) return this;
|
|
4676
4982
|
this.render();
|
|
4677
4983
|
this.emitChange("layout", { mode: resolvedMode });
|
|
@@ -4684,8 +4990,16 @@ var VdFlowchart = class {
|
|
|
4684
4990
|
// Re-pick each edge's from/to port from the nodes' current centers, so a
|
|
4685
4991
|
// freshly laid-out graph attaches connectors on sensible sides. Returns
|
|
4686
4992
|
// whether any port actually changed.
|
|
4687
|
-
rerouteEdgePorts() {
|
|
4993
|
+
rerouteEdgePorts(flowDirection = null) {
|
|
4688
4994
|
let changed = false;
|
|
4995
|
+
const flow = flowDirection === "down" || flowDirection === "up" || flowDirection === "left" || flowDirection === "right" ? flowDirection : null;
|
|
4996
|
+
const outDegree = /* @__PURE__ */ new Map();
|
|
4997
|
+
if (flow) {
|
|
4998
|
+
this.documentData.edges.forEach((edge) => {
|
|
4999
|
+
const fromId = edge?.from?.nodeId;
|
|
5000
|
+
if (fromId) outDegree.set(fromId, (outDegree.get(fromId) || 0) + 1);
|
|
5001
|
+
});
|
|
5002
|
+
}
|
|
4689
5003
|
this.documentData.edges.forEach((edge) => {
|
|
4690
5004
|
const fromNode = this.findNode(edge.from.nodeId);
|
|
4691
5005
|
const toNode = this.findNode(edge.to.nodeId);
|
|
@@ -4695,8 +5009,82 @@ var VdFlowchart = class {
|
|
|
4695
5009
|
y: fromNode.y + fromNode.height / 2
|
|
4696
5010
|
};
|
|
4697
5011
|
const toCenter = { x: toNode.x + toNode.width / 2, y: toNode.y + toNode.height / 2 };
|
|
4698
|
-
|
|
4699
|
-
|
|
5012
|
+
let nextFrom;
|
|
5013
|
+
let nextTo;
|
|
5014
|
+
if (flow) {
|
|
5015
|
+
const dx = toCenter.x - fromCenter.x;
|
|
5016
|
+
const dy = toCenter.y - fromCenter.y;
|
|
5017
|
+
const vertical = flow === "down" || flow === "up";
|
|
5018
|
+
const sign = flow === "up" || flow === "left" ? -1 : 1;
|
|
5019
|
+
const mainDelta = (vertical ? dy : dx) * sign;
|
|
5020
|
+
const crossDelta = vertical ? dx : dy;
|
|
5021
|
+
const isDiamondBranch = fromNode.type === "diamond" && (outDegree.get(fromNode.id) || 0) >= 2;
|
|
5022
|
+
if (mainDelta < -8) {
|
|
5023
|
+
const allNodes = this.documentData.nodes;
|
|
5024
|
+
if (vertical) {
|
|
5025
|
+
const minX = Math.min(fromNode.x, toNode.x);
|
|
5026
|
+
const maxRight = Math.max(fromNode.x + fromNode.width, toNode.x + toNode.width);
|
|
5027
|
+
const minY = Math.min(fromCenter.y, toCenter.y) - 8;
|
|
5028
|
+
const maxY = Math.max(fromCenter.y, toCenter.y) + 8;
|
|
5029
|
+
let rightCount = 0;
|
|
5030
|
+
let leftCount = 0;
|
|
5031
|
+
allNodes.forEach((n) => {
|
|
5032
|
+
if (!n || n.id === fromNode.id || n.id === toNode.id) return;
|
|
5033
|
+
const ny = n.y + n.height / 2;
|
|
5034
|
+
if (ny < minY || ny > maxY) return;
|
|
5035
|
+
const nx = n.x + n.width / 2;
|
|
5036
|
+
if (nx >= maxRight - 20) rightCount += 1;
|
|
5037
|
+
if (nx <= minX + 20) leftCount += 1;
|
|
5038
|
+
});
|
|
5039
|
+
const side = leftCount < rightCount ? "left" : rightCount < leftCount ? "right" : crossDelta < -8 ? "left" : "right";
|
|
5040
|
+
nextFrom = side;
|
|
5041
|
+
nextTo = side;
|
|
5042
|
+
} else {
|
|
5043
|
+
const minY = Math.min(fromNode.y, toNode.y);
|
|
5044
|
+
const maxBottom = Math.max(fromNode.y + fromNode.height, toNode.y + toNode.height);
|
|
5045
|
+
const minX = Math.min(fromCenter.x, toCenter.x) - 8;
|
|
5046
|
+
const maxX = Math.max(fromCenter.x, toCenter.x) + 8;
|
|
5047
|
+
let bottomCount = 0;
|
|
5048
|
+
let topCount = 0;
|
|
5049
|
+
allNodes.forEach((n) => {
|
|
5050
|
+
if (!n || n.id === fromNode.id || n.id === toNode.id) return;
|
|
5051
|
+
const nx = n.x + n.width / 2;
|
|
5052
|
+
if (nx < minX || nx > maxX) return;
|
|
5053
|
+
const ny = n.y + n.height / 2;
|
|
5054
|
+
if (ny >= maxBottom - 20) bottomCount += 1;
|
|
5055
|
+
if (ny <= minY + 20) topCount += 1;
|
|
5056
|
+
});
|
|
5057
|
+
const side = topCount < bottomCount ? "top" : bottomCount < topCount ? "bottom" : crossDelta < -8 ? "top" : "bottom";
|
|
5058
|
+
nextFrom = side;
|
|
5059
|
+
nextTo = side;
|
|
5060
|
+
}
|
|
5061
|
+
} else if (mainDelta >= 12) {
|
|
5062
|
+
if (vertical) {
|
|
5063
|
+
nextTo = sign > 0 ? "top" : "bottom";
|
|
5064
|
+
if (isDiamondBranch && Math.abs(crossDelta) >= fromNode.width * 0.45) {
|
|
5065
|
+
nextFrom = crossDelta > 0 ? "right" : "left";
|
|
5066
|
+
} else {
|
|
5067
|
+
nextFrom = sign > 0 ? "bottom" : "top";
|
|
5068
|
+
}
|
|
5069
|
+
} else {
|
|
5070
|
+
nextTo = sign > 0 ? "left" : "right";
|
|
5071
|
+
if (isDiamondBranch && Math.abs(crossDelta) >= fromNode.height * 0.45) {
|
|
5072
|
+
nextFrom = crossDelta > 0 ? "bottom" : "top";
|
|
5073
|
+
} else {
|
|
5074
|
+
nextFrom = sign > 0 ? "right" : "left";
|
|
5075
|
+
}
|
|
5076
|
+
}
|
|
5077
|
+
} else if (vertical) {
|
|
5078
|
+
nextFrom = dx >= 0 ? "right" : "left";
|
|
5079
|
+
nextTo = dx >= 0 ? "left" : "right";
|
|
5080
|
+
} else {
|
|
5081
|
+
nextFrom = dy >= 0 ? "bottom" : "top";
|
|
5082
|
+
nextTo = dy >= 0 ? "top" : "bottom";
|
|
5083
|
+
}
|
|
5084
|
+
} else {
|
|
5085
|
+
nextFrom = getPortByDirection(fromNode, toCenter).port;
|
|
5086
|
+
nextTo = getPortByDirection(toNode, fromCenter).port;
|
|
5087
|
+
}
|
|
4700
5088
|
if (nextFrom !== edge.from.port || nextTo !== edge.to.port) {
|
|
4701
5089
|
edge.from.port = nextFrom;
|
|
4702
5090
|
edge.to.port = nextTo;
|
|
@@ -4711,8 +5099,8 @@ var VdFlowchart = class {
|
|
|
4711
5099
|
node.x = patch.x == null ? node.x : formatNumber(toFiniteNumber(patch.x, node.x));
|
|
4712
5100
|
node.y = patch.y == null ? node.y : formatNumber(toFiniteNumber(patch.y, node.y));
|
|
4713
5101
|
node.text = isNodeTextEditable(nextType) ? patch.text == null ? node.text : String(patch.text) : getNodeSpec(nextType).text;
|
|
4714
|
-
node.width = clampNodeWidth(nextType, patch.width, node.width);
|
|
4715
|
-
node.height = clampNodeHeight(nextType, patch.height, node.height);
|
|
5102
|
+
node.width = clampNodeWidth(nextType, patch.width, node.width, this.nodeSizeBounds());
|
|
5103
|
+
node.height = clampNodeHeight(nextType, patch.height, node.height, this.nodeSizeBounds());
|
|
4716
5104
|
if (isPlainObject(patch.data)) {
|
|
4717
5105
|
node.data = deepClone(patch.data);
|
|
4718
5106
|
}
|
|
@@ -4901,8 +5289,45 @@ var VdFlowchart = class {
|
|
|
4901
5289
|
if (this.selection.kind === "node") return this.removeNode(this.selection.id);
|
|
4902
5290
|
return this.removeEdge(this.selection.id);
|
|
4903
5291
|
}
|
|
5292
|
+
nodeSizeBounds() {
|
|
5293
|
+
return { min: this.minNodeSize, max: this.maxNodeSize };
|
|
5294
|
+
}
|
|
5295
|
+
scaleBounds() {
|
|
5296
|
+
return { min: this.minScale, max: this.maxScale };
|
|
5297
|
+
}
|
|
5298
|
+
normalizationLimits() {
|
|
5299
|
+
return { scaleBounds: this.scaleBounds(), sizeBounds: this.nodeSizeBounds() };
|
|
5300
|
+
}
|
|
5301
|
+
hasWideCamera() {
|
|
5302
|
+
return this.minScale < DEFAULT_MIN_SCALE || this.maxScale > DEFAULT_MAX_SCALE;
|
|
5303
|
+
}
|
|
5304
|
+
syncGrid() {
|
|
5305
|
+
const wide = this.hasWideCamera();
|
|
5306
|
+
const extent = wide ? WIDE_WORLD_EXTENT : DEFAULT_WORLD_EXTENT;
|
|
5307
|
+
this.gridRect?.setAttribute("x", String(-extent / 2));
|
|
5308
|
+
this.gridRect?.setAttribute("y", String(-extent / 2));
|
|
5309
|
+
this.gridRect?.setAttribute("width", String(extent));
|
|
5310
|
+
this.gridRect?.setAttribute("height", String(extent));
|
|
5311
|
+
const pattern = this.svg?.querySelector("pattern");
|
|
5312
|
+
const path = pattern?.querySelector("path");
|
|
5313
|
+
if (!pattern || !path) return;
|
|
5314
|
+
let tile = this.gridSize;
|
|
5315
|
+
if (wide) {
|
|
5316
|
+
const scale = Math.max(this.minScale, this.documentData?.viewport?.scale || 1);
|
|
5317
|
+
while (tile * scale < 12 && tile < extent / 8) tile *= 4;
|
|
5318
|
+
while (tile * scale > 48 && tile > 1.5) tile /= 4;
|
|
5319
|
+
tile = formatNumber(tile);
|
|
5320
|
+
path.setAttribute("vector-effect", "non-scaling-stroke");
|
|
5321
|
+
} else {
|
|
5322
|
+
path.removeAttribute("vector-effect");
|
|
5323
|
+
}
|
|
5324
|
+
path.setAttribute("stroke-width", "1");
|
|
5325
|
+
pattern.setAttribute("width", String(tile));
|
|
5326
|
+
pattern.setAttribute("height", String(tile));
|
|
5327
|
+
path.setAttribute("d", `M ${tile} 0 L 0 0 0 ${tile}`);
|
|
5328
|
+
}
|
|
4904
5329
|
setViewport(viewport) {
|
|
4905
|
-
this.documentData.viewport = normalizeViewport(viewport);
|
|
5330
|
+
this.documentData.viewport = normalizeViewport(viewport, this.scaleBounds());
|
|
4906
5331
|
this.render({ inspector: false, json: true });
|
|
4907
5332
|
this.emitViewportChange("viewport:set");
|
|
4908
5333
|
return this;
|
|
@@ -4920,7 +5345,7 @@ var VdFlowchart = class {
|
|
|
4920
5345
|
return this;
|
|
4921
5346
|
}
|
|
4922
5347
|
resetView() {
|
|
4923
|
-
this.documentData.viewport = normalizeViewport({ x: 0, y: 0, scale: 1 });
|
|
5348
|
+
this.documentData.viewport = normalizeViewport({ x: 0, y: 0, scale: 1 }, this.scaleBounds());
|
|
4924
5349
|
this.render({ inspector: false, json: true });
|
|
4925
5350
|
this.emitViewportChange("viewport:reset");
|
|
4926
5351
|
return this;
|
|
@@ -4936,30 +5361,64 @@ var VdFlowchart = class {
|
|
|
4936
5361
|
const padding = 80;
|
|
4937
5362
|
const contentWidth = Math.max(1, bounds.right - bounds.left);
|
|
4938
5363
|
const contentHeight = Math.max(1, bounds.bottom - bounds.top);
|
|
4939
|
-
const scale =
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
5364
|
+
const scale = formatScale(
|
|
5365
|
+
clamp(
|
|
5366
|
+
Math.min((width - padding * 2) / contentWidth, (height - padding * 2) / contentHeight),
|
|
5367
|
+
this.minScale,
|
|
5368
|
+
this.maxScale
|
|
5369
|
+
)
|
|
4943
5370
|
);
|
|
4944
5371
|
this.documentData.viewport = {
|
|
4945
5372
|
x: formatNumber(width / 2 - (bounds.left + bounds.right) / 2 * scale),
|
|
4946
5373
|
y: formatNumber(height / 2 - (bounds.top + bounds.bottom) / 2 * scale),
|
|
4947
|
-
scale
|
|
5374
|
+
scale
|
|
4948
5375
|
};
|
|
4949
5376
|
this.render({ inspector: false, json: true });
|
|
4950
5377
|
this.emitViewportChange("viewport:fit");
|
|
4951
5378
|
return this;
|
|
4952
5379
|
}
|
|
5380
|
+
fitNode(nodeId, padding = 48) {
|
|
5381
|
+
const node = this.findNode(nodeId);
|
|
5382
|
+
if (!node || this.isNodeHidden(node.id)) return this;
|
|
5383
|
+
const pad = Math.max(0, toFiniteNumber(padding, 48));
|
|
5384
|
+
const width = this.canvasEl.clientWidth || 800;
|
|
5385
|
+
const height = this.canvasEl.clientHeight || 560;
|
|
5386
|
+
const availableWidth = Math.max(1, width - pad * 2);
|
|
5387
|
+
const availableHeight = Math.max(1, height - pad * 2);
|
|
5388
|
+
const scale = formatScale(
|
|
5389
|
+
clamp(
|
|
5390
|
+
Math.min(
|
|
5391
|
+
availableWidth / Math.max(1, node.width),
|
|
5392
|
+
availableHeight / Math.max(1, node.height)
|
|
5393
|
+
),
|
|
5394
|
+
this.minScale,
|
|
5395
|
+
this.maxScale
|
|
5396
|
+
)
|
|
5397
|
+
);
|
|
5398
|
+
const centerX = node.x + node.width / 2;
|
|
5399
|
+
const centerY = node.y + node.height / 2;
|
|
5400
|
+
this.documentData.viewport = {
|
|
5401
|
+
x: formatNumber(width / 2 - centerX * scale),
|
|
5402
|
+
y: formatNumber(height / 2 - centerY * scale),
|
|
5403
|
+
scale
|
|
5404
|
+
};
|
|
5405
|
+
this.render({ inspector: false, json: true });
|
|
5406
|
+
this.emitViewportChange("viewport:fit-node");
|
|
5407
|
+
return this;
|
|
5408
|
+
}
|
|
4953
5409
|
clear() {
|
|
4954
5410
|
this.stopTextEdit({ commit: false });
|
|
4955
5411
|
this.activeTool = null;
|
|
4956
5412
|
this.reconnectEdgeId = null;
|
|
4957
5413
|
this.clipboard = null;
|
|
4958
|
-
this.documentData = normalizeDocument(
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
5414
|
+
this.documentData = normalizeDocument(
|
|
5415
|
+
{
|
|
5416
|
+
nodes: [],
|
|
5417
|
+
edges: [],
|
|
5418
|
+
viewport: { x: 0, y: 0, scale: 1 }
|
|
5419
|
+
},
|
|
5420
|
+
this.normalizationLimits()
|
|
5421
|
+
);
|
|
4963
5422
|
this.selection = null;
|
|
4964
5423
|
this.render();
|
|
4965
5424
|
this.emitChange("clear");
|
|
@@ -4994,6 +5453,33 @@ var VdFlowchart = class {
|
|
|
4994
5453
|
if ("snapGuides" in options) this.snapGuides = options.snapGuides !== false;
|
|
4995
5454
|
if ("minimap" in options) this.setMinimapEnabled(options.minimap !== false);
|
|
4996
5455
|
if ("autoLayout" in options) this.autoLayout = Boolean(options.autoLayout);
|
|
5456
|
+
if ("minScale" in options || "maxScale" in options || "minNodeSize" in options || "maxNodeSize" in options) {
|
|
5457
|
+
const nextScale = normalizeBoundPair(
|
|
5458
|
+
"minScale" in options ? options.minScale : this.minScale,
|
|
5459
|
+
"maxScale" in options ? options.maxScale : this.maxScale,
|
|
5460
|
+
DEFAULT_MIN_SCALE,
|
|
5461
|
+
DEFAULT_MAX_SCALE
|
|
5462
|
+
);
|
|
5463
|
+
const nextSize = normalizeBoundPair(
|
|
5464
|
+
"minNodeSize" in options ? options.minNodeSize : this.minNodeSize,
|
|
5465
|
+
"maxNodeSize" in options ? options.maxNodeSize : this.maxNodeSize,
|
|
5466
|
+
DEFAULT_MIN_NODE_SIZE,
|
|
5467
|
+
DEFAULT_MAX_NODE_SIZE
|
|
5468
|
+
);
|
|
5469
|
+
this.minScale = nextScale.min;
|
|
5470
|
+
this.maxScale = nextScale.max;
|
|
5471
|
+
this.minNodeSize = nextSize.min;
|
|
5472
|
+
this.maxNodeSize = nextSize.max;
|
|
5473
|
+
const clamped = clamp(
|
|
5474
|
+
formatScale(this.documentData.viewport.scale),
|
|
5475
|
+
this.minScale,
|
|
5476
|
+
this.maxScale
|
|
5477
|
+
);
|
|
5478
|
+
if (clamped !== this.documentData.viewport.scale) {
|
|
5479
|
+
this.documentData.viewport.scale = clamped;
|
|
5480
|
+
this.pendingBoundsEvent = true;
|
|
5481
|
+
}
|
|
5482
|
+
}
|
|
4997
5483
|
this.syncCanvasLabel();
|
|
4998
5484
|
if (this.shortcutsHelpOpen) this.renderShortcutsHelp();
|
|
4999
5485
|
if ("history" in options && options.history !== false !== this.historyEnabled) {
|
|
@@ -5011,11 +5497,15 @@ var VdFlowchart = class {
|
|
|
5011
5497
|
}
|
|
5012
5498
|
}
|
|
5013
5499
|
this.render();
|
|
5500
|
+
if (this.pendingBoundsEvent) {
|
|
5501
|
+
this.pendingBoundsEvent = false;
|
|
5502
|
+
this.emitViewportChange("viewport:bounds");
|
|
5503
|
+
}
|
|
5014
5504
|
this.updateHistoryButtons();
|
|
5015
5505
|
return this;
|
|
5016
5506
|
}
|
|
5017
5507
|
load(data, options = {}) {
|
|
5018
|
-
const documentData = normalizeDocument(data);
|
|
5508
|
+
const documentData = normalizeDocument(data, this.normalizationLimits());
|
|
5019
5509
|
if (options.silent && JSON.stringify(documentData) === JSON.stringify(this.documentData)) {
|
|
5020
5510
|
return this;
|
|
5021
5511
|
}
|
|
@@ -5073,7 +5563,11 @@ var EDITOR_OPTION_KEYS = [
|
|
|
5073
5563
|
"keyboardShortcuts",
|
|
5074
5564
|
"snapGuides",
|
|
5075
5565
|
"minimap",
|
|
5076
|
-
"autoLayout"
|
|
5566
|
+
"autoLayout",
|
|
5567
|
+
"minScale",
|
|
5568
|
+
"maxScale",
|
|
5569
|
+
"minNodeSize",
|
|
5570
|
+
"maxNodeSize"
|
|
5077
5571
|
];
|
|
5078
5572
|
var VdFlowchart2 = (0, import_vue.defineComponent)({
|
|
5079
5573
|
name: "VdFlowchart",
|
|
@@ -5099,13 +5593,29 @@ var VdFlowchart2 = (0, import_vue.defineComponent)({
|
|
|
5099
5593
|
/** Show the overview minimap in the canvas corner (default true). */
|
|
5100
5594
|
minimap: { type: Boolean, default: true },
|
|
5101
5595
|
/** Re-run the current layout after keyboard or handle insertions (default false). */
|
|
5102
|
-
autoLayout: { type: Boolean, default: false }
|
|
5596
|
+
autoLayout: { type: Boolean, default: false },
|
|
5597
|
+
/** Lowest camera scale. Defaults to 0.35. Set it together with maxScale. */
|
|
5598
|
+
minScale: { type: Number, default: void 0 },
|
|
5599
|
+
/** Highest camera scale. Defaults to 3. Set it together with minScale. */
|
|
5600
|
+
maxScale: { type: Number, default: void 0 },
|
|
5601
|
+
/** Smallest resizable-node side. Defaults to 56. Set it together with maxNodeSize. */
|
|
5602
|
+
minNodeSize: { type: Number, default: void 0 },
|
|
5603
|
+
/** Largest resizable-node side. Defaults to 420. Set it together with minNodeSize. */
|
|
5604
|
+
maxNodeSize: { type: Number, default: void 0 }
|
|
5103
5605
|
},
|
|
5104
5606
|
emits: ["change", "select", "viewport", "connect", "ready"],
|
|
5105
5607
|
setup(props, { emit, expose }) {
|
|
5106
5608
|
const el = (0, import_vue.ref)(null);
|
|
5107
5609
|
let instance = null;
|
|
5108
|
-
const editorOptions = () => Object.fromEntries(
|
|
5610
|
+
const editorOptions = () => Object.fromEntries(
|
|
5611
|
+
EDITOR_OPTION_KEYS.flatMap((key) => {
|
|
5612
|
+
const value = props[key];
|
|
5613
|
+
if ((key === "minScale" || key === "maxScale" || key === "minNodeSize" || key === "maxNodeSize") && value == null) {
|
|
5614
|
+
return [];
|
|
5615
|
+
}
|
|
5616
|
+
return [[key, value]];
|
|
5617
|
+
})
|
|
5618
|
+
);
|
|
5109
5619
|
const create = () => {
|
|
5110
5620
|
instance = new VdFlowchart({
|
|
5111
5621
|
element: el.value,
|
|
@@ -5147,7 +5657,8 @@ var VdFlowchart2 = (0, import_vue.defineComponent)({
|
|
|
5147
5657
|
redo: () => instance?.redo(),
|
|
5148
5658
|
canUndo: () => Boolean(instance?.canUndo()),
|
|
5149
5659
|
canRedo: () => Boolean(instance?.canRedo()),
|
|
5150
|
-
layout: (mode, options) => instance?.layout(mode, options)
|
|
5660
|
+
layout: (mode, options) => instance?.layout(mode, options),
|
|
5661
|
+
fitNode: (nodeId, padding) => instance?.fitNode(nodeId, padding)
|
|
5151
5662
|
});
|
|
5152
5663
|
return () => (0, import_vue.h)("div", { ref: el, class: "vd-flowchart" });
|
|
5153
5664
|
}
|