@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.js
CHANGED
|
@@ -8,6 +8,8 @@ var DIRECTIONS = {
|
|
|
8
8
|
down: { main: "y", sign: 1 },
|
|
9
9
|
up: { main: "y", sign: -1 }
|
|
10
10
|
};
|
|
11
|
+
var DEFAULT_LEVEL_EDGE_GAP = 88;
|
|
12
|
+
var DEFAULT_SIBLING_EDGE_GAP = 64;
|
|
11
13
|
function num(value, fallback) {
|
|
12
14
|
const next = Number(value);
|
|
13
15
|
return Number.isFinite(next) ? next : fallback;
|
|
@@ -32,60 +34,182 @@ function pickRoots(nodes, indegree, optRoot) {
|
|
|
32
34
|
}
|
|
33
35
|
function spanningForest(allIds, roots, children) {
|
|
34
36
|
const visited = /* @__PURE__ */ new Set();
|
|
35
|
-
const
|
|
36
|
-
const tree = /* @__PURE__ */ new Map();
|
|
37
|
+
const active = /* @__PURE__ */ new Set();
|
|
37
38
|
const forestRoots = [];
|
|
38
|
-
const
|
|
39
|
+
const forwardParents = new Map(allIds.map((id) => [id, []]));
|
|
40
|
+
const dfs = (id) => {
|
|
39
41
|
visited.add(id);
|
|
40
|
-
|
|
41
|
-
const kids = [];
|
|
42
|
+
active.add(id);
|
|
42
43
|
(children.get(id) || []).forEach((child) => {
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
if (active.has(child)) return;
|
|
45
|
+
const list = forwardParents.get(child);
|
|
46
|
+
if (list && !list.includes(id)) list.push(id);
|
|
47
|
+
if (!visited.has(child)) dfs(child);
|
|
47
48
|
});
|
|
48
|
-
|
|
49
|
+
active.delete(id);
|
|
49
50
|
};
|
|
50
51
|
roots.forEach((root) => {
|
|
51
52
|
if (!visited.has(root)) {
|
|
52
53
|
forestRoots.push(root);
|
|
53
|
-
|
|
54
|
+
dfs(root);
|
|
54
55
|
}
|
|
55
56
|
});
|
|
56
57
|
allIds.forEach((id) => {
|
|
57
58
|
if (!visited.has(id)) {
|
|
58
59
|
forestRoots.push(id);
|
|
59
|
-
|
|
60
|
+
dfs(id);
|
|
60
61
|
}
|
|
61
62
|
});
|
|
63
|
+
const depth = new Map(allIds.map((id) => [id, 0]));
|
|
64
|
+
const limit = Math.max(1, allIds.length);
|
|
65
|
+
for (let pass = 0; pass < limit; pass += 1) {
|
|
66
|
+
let changed = false;
|
|
67
|
+
for (const id of allIds) {
|
|
68
|
+
const parents = forwardParents.get(id) || [];
|
|
69
|
+
for (const parent of parents) {
|
|
70
|
+
const nextDepth = Math.min(limit, (depth.get(parent) || 0) + 1);
|
|
71
|
+
if (nextDepth > (depth.get(id) || 0)) {
|
|
72
|
+
depth.set(id, nextDepth);
|
|
73
|
+
changed = true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (!changed) break;
|
|
78
|
+
}
|
|
79
|
+
const tree = new Map(allIds.map((id) => [id, []]));
|
|
80
|
+
const rootSet = new Set(forestRoots);
|
|
81
|
+
const chosenParent = /* @__PURE__ */ new Map();
|
|
82
|
+
for (const id of allIds) {
|
|
83
|
+
if (rootSet.has(id)) continue;
|
|
84
|
+
const parents = forwardParents.get(id) || [];
|
|
85
|
+
if (!parents.length) continue;
|
|
86
|
+
let bestParent = parents[0];
|
|
87
|
+
let bestDepth = depth.get(bestParent) ?? -1;
|
|
88
|
+
for (let index = 1; index < parents.length; index += 1) {
|
|
89
|
+
const candidate = parents[index];
|
|
90
|
+
const candidateDepth = depth.get(candidate) ?? -1;
|
|
91
|
+
if (candidateDepth > bestDepth) {
|
|
92
|
+
bestDepth = candidateDepth;
|
|
93
|
+
bestParent = candidate;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
chosenParent.set(id, bestParent);
|
|
97
|
+
}
|
|
98
|
+
const placedChild = /* @__PURE__ */ new Set();
|
|
99
|
+
for (const parentId of allIds) {
|
|
100
|
+
const out = tree.get(parentId);
|
|
101
|
+
for (const childId of children.get(parentId) || []) {
|
|
102
|
+
if (chosenParent.get(childId) === parentId && !placedChild.has(childId)) {
|
|
103
|
+
placedChild.add(childId);
|
|
104
|
+
out.push(childId);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
62
108
|
return { depth, tree, forestRoots };
|
|
63
109
|
}
|
|
64
110
|
function treeCenters(forestRoots, tree, depth, sizeOf, options) {
|
|
65
111
|
const dir = DIRECTIONS[options.direction] || DIRECTIONS.right;
|
|
66
|
-
const
|
|
67
|
-
const
|
|
112
|
+
const hasExplicitLevel = Number.isFinite(Number(options.levelGap));
|
|
113
|
+
const hasExplicitSibling = Number.isFinite(Number(options.siblingGap));
|
|
114
|
+
const explicitLevelGap = num(options.levelGap, 220);
|
|
115
|
+
const explicitSiblingGap = num(options.siblingGap, 140);
|
|
116
|
+
const edgeSiblingGap = hasExplicitSibling ? Math.max(16, explicitSiblingGap - 96) : DEFAULT_SIBLING_EDGE_GAP;
|
|
117
|
+
const crossSpan = (id) => {
|
|
118
|
+
const size = sizeOf(id);
|
|
119
|
+
return dir.main === "x" ? size.height : size.width;
|
|
120
|
+
};
|
|
121
|
+
const mainSpan = (id) => {
|
|
122
|
+
const size = sizeOf(id);
|
|
123
|
+
return dir.main === "x" ? size.width : size.height;
|
|
124
|
+
};
|
|
125
|
+
const subtreeSpan = /* @__PURE__ */ new Map();
|
|
126
|
+
const subtreeCount = /* @__PURE__ */ new Map();
|
|
127
|
+
const measure = (id) => {
|
|
128
|
+
const selfSpan = crossSpan(id);
|
|
129
|
+
const kids = tree.get(id) || [];
|
|
130
|
+
if (!kids.length) {
|
|
131
|
+
subtreeSpan.set(id, selfSpan);
|
|
132
|
+
subtreeCount.set(id, 1);
|
|
133
|
+
return selfSpan;
|
|
134
|
+
}
|
|
135
|
+
let childTotal = 0;
|
|
136
|
+
let totalCount = 1;
|
|
137
|
+
kids.forEach((kid, index) => {
|
|
138
|
+
if (index > 0) childTotal += edgeSiblingGap;
|
|
139
|
+
childTotal += measure(kid);
|
|
140
|
+
totalCount += subtreeCount.get(kid) || 1;
|
|
141
|
+
});
|
|
142
|
+
const span = Math.max(selfSpan, childTotal);
|
|
143
|
+
subtreeSpan.set(id, span);
|
|
144
|
+
subtreeCount.set(id, totalCount);
|
|
145
|
+
return span;
|
|
146
|
+
};
|
|
147
|
+
forestRoots.forEach(measure);
|
|
68
148
|
const crossOf = /* @__PURE__ */ new Map();
|
|
69
|
-
|
|
70
|
-
|
|
149
|
+
const place = (id, startCross) => {
|
|
150
|
+
const span = subtreeSpan.get(id) || crossSpan(id);
|
|
71
151
|
const kids = tree.get(id) || [];
|
|
72
152
|
if (!kids.length) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return
|
|
153
|
+
const center2 = startCross + span / 2;
|
|
154
|
+
crossOf.set(id, center2);
|
|
155
|
+
return center2;
|
|
156
|
+
}
|
|
157
|
+
let childTotal = 0;
|
|
158
|
+
const kidSpans = kids.map((kid, index) => {
|
|
159
|
+
const kidSpan = subtreeSpan.get(kid) || crossSpan(kid);
|
|
160
|
+
if (index > 0) childTotal += edgeSiblingGap;
|
|
161
|
+
childTotal += kidSpan;
|
|
162
|
+
return kidSpan;
|
|
163
|
+
});
|
|
164
|
+
let cursor = startCross + (span - childTotal) / 2;
|
|
165
|
+
const childCenters = kids.map((kid, index) => {
|
|
166
|
+
const kidSpan = kidSpans[index];
|
|
167
|
+
const center2 = place(kid, cursor);
|
|
168
|
+
cursor += kidSpan + edgeSiblingGap;
|
|
169
|
+
return center2;
|
|
170
|
+
});
|
|
171
|
+
if (kids.length >= 2) {
|
|
172
|
+
const counts = kids.map((kid) => subtreeCount.get(kid) || 1);
|
|
173
|
+
const maxCount = Math.max(...counts);
|
|
174
|
+
const minCount = Math.min(...counts);
|
|
175
|
+
if (maxCount > minCount) {
|
|
176
|
+
const primaryIndex = counts.indexOf(maxCount);
|
|
177
|
+
const center2 = childCenters[primaryIndex];
|
|
178
|
+
crossOf.set(id, center2);
|
|
179
|
+
return center2;
|
|
180
|
+
}
|
|
76
181
|
}
|
|
77
|
-
const childCenters = kids.map(place);
|
|
78
182
|
const center = (childCenters[0] + childCenters[childCenters.length - 1]) / 2;
|
|
79
183
|
crossOf.set(id, center);
|
|
80
184
|
return center;
|
|
81
185
|
};
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
186
|
+
let rootCursor = 0;
|
|
187
|
+
forestRoots.forEach((root, index) => {
|
|
188
|
+
if (index > 0) rootCursor += edgeSiblingGap;
|
|
189
|
+
const span = subtreeSpan.get(root) || crossSpan(root);
|
|
190
|
+
place(root, rootCursor);
|
|
191
|
+
rootCursor += span;
|
|
192
|
+
});
|
|
193
|
+
let maxDepth = 0;
|
|
194
|
+
const maxMainAtDepth = /* @__PURE__ */ new Map();
|
|
195
|
+
crossOf.forEach((_cross, id) => {
|
|
196
|
+
const d = depth.get(id) || 0;
|
|
197
|
+
if (d > maxDepth) maxDepth = d;
|
|
198
|
+
const current = maxMainAtDepth.get(d) || 0;
|
|
199
|
+
const span = mainSpan(id);
|
|
200
|
+
if (span > current) maxMainAtDepth.set(d, span);
|
|
85
201
|
});
|
|
202
|
+
const mainCenterAtDepth = /* @__PURE__ */ new Map([[0, 0]]);
|
|
203
|
+
for (let d = 1; d <= maxDepth; d += 1) {
|
|
204
|
+
const prevCenter = mainCenterAtDepth.get(d - 1) || 0;
|
|
205
|
+
const prevSpan = maxMainAtDepth.get(d - 1) || 160;
|
|
206
|
+
const currSpan = maxMainAtDepth.get(d) || 160;
|
|
207
|
+
const step = hasExplicitLevel ? Math.max(explicitLevelGap, (prevSpan + currSpan) / 2 + 24) : (prevSpan + currSpan) / 2 + DEFAULT_LEVEL_EDGE_GAP;
|
|
208
|
+
mainCenterAtDepth.set(d, prevCenter + step * dir.sign);
|
|
209
|
+
}
|
|
86
210
|
const centers = /* @__PURE__ */ new Map();
|
|
87
211
|
crossOf.forEach((cross, id) => {
|
|
88
|
-
const main = (depth.get(id) || 0)
|
|
212
|
+
const main = mainCenterAtDepth.get(depth.get(id) || 0) || 0;
|
|
89
213
|
centers.set(
|
|
90
214
|
id,
|
|
91
215
|
dir.main === "x" ? { centerX: main, centerY: cross } : { centerX: cross, centerY: main }
|
|
@@ -362,11 +486,12 @@ function createToolbarButton({ action, label, icon, disabled = false }) {
|
|
|
362
486
|
return button;
|
|
363
487
|
}
|
|
364
488
|
var DEFAULT_GRID_SIZE = 24;
|
|
365
|
-
var
|
|
366
|
-
var
|
|
367
|
-
var
|
|
368
|
-
var
|
|
369
|
-
var
|
|
489
|
+
var DEFAULT_MIN_SCALE = 0.35;
|
|
490
|
+
var DEFAULT_MAX_SCALE = 3;
|
|
491
|
+
var DEFAULT_MIN_NODE_SIZE = 56;
|
|
492
|
+
var DEFAULT_MAX_NODE_SIZE = 420;
|
|
493
|
+
var DEFAULT_WORLD_EXTENT = 12e3;
|
|
494
|
+
var WIDE_WORLD_EXTENT = 24e4;
|
|
370
495
|
var MAX_NODES = 1e4;
|
|
371
496
|
var MAX_EDGES = 1e4;
|
|
372
497
|
var RESIZE_HANDLES = ["n", "ne", "e", "se", "s", "sw", "w", "nw"];
|
|
@@ -398,7 +523,7 @@ var EDGE_STROKE_PRESETS = [
|
|
|
398
523
|
{ id: "medium", label: "Medium", width: DEFAULT_EDGE_STROKE_WIDTH },
|
|
399
524
|
{ id: "bold", label: "Bold", width: 3.5 }
|
|
400
525
|
];
|
|
401
|
-
var VD_FLOWCHART_VERSION = "1.
|
|
526
|
+
var VD_FLOWCHART_VERSION = "1.5.1";
|
|
402
527
|
var FLOWCHART_DOCUMENT_VERSION = "1.3.0";
|
|
403
528
|
var FLOWCHART_NODE_TYPES = [
|
|
404
529
|
"rounded-rect",
|
|
@@ -539,6 +664,17 @@ function deepClone(value) {
|
|
|
539
664
|
function formatNumber(value) {
|
|
540
665
|
return Number(value.toFixed(2));
|
|
541
666
|
}
|
|
667
|
+
function formatScale(value) {
|
|
668
|
+
return Number(toFiniteNumber(value, 1).toFixed(4));
|
|
669
|
+
}
|
|
670
|
+
function normalizeBoundPair(min, max, fallbackMin, fallbackMax) {
|
|
671
|
+
const low = Number(min);
|
|
672
|
+
const high = Number(max);
|
|
673
|
+
if (!Number.isFinite(low) || !Number.isFinite(high) || low <= 0 || high <= 0 || low >= high) {
|
|
674
|
+
return { min: fallbackMin, max: fallbackMax };
|
|
675
|
+
}
|
|
676
|
+
return { min: low, max: high };
|
|
677
|
+
}
|
|
542
678
|
function sanitizeId(value) {
|
|
543
679
|
if (value == null) return "";
|
|
544
680
|
return String(value).trim();
|
|
@@ -596,21 +732,36 @@ function normalizeNodeType(type) {
|
|
|
596
732
|
function getNodeSpec(type) {
|
|
597
733
|
return DEFAULT_NODE_SPECS[normalizeNodeType(type)];
|
|
598
734
|
}
|
|
599
|
-
function
|
|
735
|
+
function nodeVisualScale(node) {
|
|
736
|
+
if (!node || node.type === "junction") return 1;
|
|
737
|
+
const spec = getNodeSpec(node.type);
|
|
738
|
+
const width = spec.width > 0 ? toFiniteNumber(node.width, spec.width) / spec.width : 1;
|
|
739
|
+
const height = spec.height > 0 ? toFiniteNumber(node.height, spec.height) / spec.height : 1;
|
|
740
|
+
return clamp(Math.min(1, width, height), 0.01, 1);
|
|
741
|
+
}
|
|
742
|
+
function edgeVisualScale(fromNode, toNode) {
|
|
743
|
+
if (fromNode && toNode) return Math.min(nodeVisualScale(fromNode), nodeVisualScale(toNode));
|
|
744
|
+
if (fromNode) return nodeVisualScale(fromNode);
|
|
745
|
+
if (toNode) return nodeVisualScale(toNode);
|
|
746
|
+
return 1;
|
|
747
|
+
}
|
|
748
|
+
function getNodeSizeBounds(type, sizeBounds) {
|
|
600
749
|
const spec = getNodeSpec(type);
|
|
750
|
+
const min = sizeBounds?.min ?? DEFAULT_MIN_NODE_SIZE;
|
|
751
|
+
const max = sizeBounds?.max ?? DEFAULT_MAX_NODE_SIZE;
|
|
601
752
|
return {
|
|
602
|
-
minWidth: spec.minWidth ??
|
|
603
|
-
minHeight: spec.minHeight ??
|
|
604
|
-
maxWidth: spec.maxWidth ??
|
|
605
|
-
maxHeight: spec.maxHeight ??
|
|
753
|
+
minWidth: spec.minWidth ?? min,
|
|
754
|
+
minHeight: spec.minHeight ?? min,
|
|
755
|
+
maxWidth: spec.maxWidth ?? max,
|
|
756
|
+
maxHeight: spec.maxHeight ?? max
|
|
606
757
|
};
|
|
607
758
|
}
|
|
608
|
-
function clampNodeWidth(type, width, fallback) {
|
|
609
|
-
const bounds = getNodeSizeBounds(type);
|
|
759
|
+
function clampNodeWidth(type, width, fallback, sizeBounds) {
|
|
760
|
+
const bounds = getNodeSizeBounds(type, sizeBounds);
|
|
610
761
|
return clamp(toFiniteNumber(width, fallback), bounds.minWidth, bounds.maxWidth);
|
|
611
762
|
}
|
|
612
|
-
function clampNodeHeight(type, height, fallback) {
|
|
613
|
-
const bounds = getNodeSizeBounds(type);
|
|
763
|
+
function clampNodeHeight(type, height, fallback, sizeBounds) {
|
|
764
|
+
const bounds = getNodeSizeBounds(type, sizeBounds);
|
|
614
765
|
return clamp(toFiniteNumber(height, fallback), bounds.minHeight, bounds.maxHeight);
|
|
615
766
|
}
|
|
616
767
|
function isNodeResizable(nodeOrType) {
|
|
@@ -634,14 +785,16 @@ function getStrokePresetWidth(presetId) {
|
|
|
634
785
|
const preset = EDGE_STROKE_PRESETS.find((item) => item.id === presetId);
|
|
635
786
|
return preset?.width ?? DEFAULT_EDGE_STROKE_WIDTH;
|
|
636
787
|
}
|
|
637
|
-
function normalizeViewport(viewport) {
|
|
788
|
+
function normalizeViewport(viewport, scaleBounds) {
|
|
789
|
+
const min = scaleBounds?.min ?? DEFAULT_MIN_SCALE;
|
|
790
|
+
const max = scaleBounds?.max ?? DEFAULT_MAX_SCALE;
|
|
638
791
|
return {
|
|
639
792
|
x: toFiniteNumber(viewport?.x, 0),
|
|
640
793
|
y: toFiniteNumber(viewport?.y, 0),
|
|
641
|
-
scale: clamp(toFiniteNumber(viewport?.scale, 1),
|
|
794
|
+
scale: clamp(formatScale(toFiniteNumber(viewport?.scale, 1)), min, max)
|
|
642
795
|
};
|
|
643
796
|
}
|
|
644
|
-
function normalizeNode(rawNode, index, usedIds) {
|
|
797
|
+
function normalizeNode(rawNode, index, usedIds, sizeBounds) {
|
|
645
798
|
const type = normalizeNodeType(rawNode?.type);
|
|
646
799
|
const spec = getNodeSpec(type);
|
|
647
800
|
const node = {
|
|
@@ -649,8 +802,8 @@ function normalizeNode(rawNode, index, usedIds) {
|
|
|
649
802
|
type,
|
|
650
803
|
x: toFiniteNumber(rawNode?.x, index * 28),
|
|
651
804
|
y: toFiniteNumber(rawNode?.y, index * 18),
|
|
652
|
-
width: clampNodeWidth(type, rawNode?.width, spec.width),
|
|
653
|
-
height: clampNodeHeight(type, rawNode?.height, spec.height),
|
|
805
|
+
width: clampNodeWidth(type, rawNode?.width, spec.width, sizeBounds),
|
|
806
|
+
height: clampNodeHeight(type, rawNode?.height, spec.height, sizeBounds),
|
|
654
807
|
text: rawNode?.text == null ? spec.text : String(rawNode.text),
|
|
655
808
|
data: isPlainObject(rawNode?.data) ? deepClone(rawNode.data) : {}
|
|
656
809
|
};
|
|
@@ -743,7 +896,7 @@ function isSupportedDocumentVersion(value) {
|
|
|
743
896
|
if (parsed.minor === max.minor && parsed.patch > max.patch) return false;
|
|
744
897
|
return true;
|
|
745
898
|
}
|
|
746
|
-
function normalizeDocument(input) {
|
|
899
|
+
function normalizeDocument(input, limits = {}) {
|
|
747
900
|
let source = input;
|
|
748
901
|
if (typeof source === "string") {
|
|
749
902
|
try {
|
|
@@ -766,13 +919,13 @@ function normalizeDocument(input) {
|
|
|
766
919
|
}
|
|
767
920
|
}
|
|
768
921
|
const usedNodeIds = /* @__PURE__ */ new Set();
|
|
769
|
-
const nodes = (Array.isArray(source.nodes) ? source.nodes : []).slice(0, MAX_NODES).map((node, index) => normalizeNode(node, index, usedNodeIds));
|
|
922
|
+
const nodes = (Array.isArray(source.nodes) ? source.nodes : []).slice(0, MAX_NODES).map((node, index) => normalizeNode(node, index, usedNodeIds, limits.sizeBounds));
|
|
770
923
|
const nodeIds = new Set(nodes.map((node) => node.id));
|
|
771
924
|
const usedEdgeIds = /* @__PURE__ */ new Set();
|
|
772
925
|
const edges = (Array.isArray(source.edges) ? source.edges : []).slice(0, MAX_EDGES).map((edge, index) => normalizeEdge(edge, index, nodeIds, usedEdgeIds)).filter(Boolean);
|
|
773
926
|
return {
|
|
774
927
|
version: FLOWCHART_DOCUMENT_VERSION,
|
|
775
|
-
viewport: normalizeViewport(source.viewport),
|
|
928
|
+
viewport: normalizeViewport(source.viewport, limits.scaleBounds),
|
|
776
929
|
nodes,
|
|
777
930
|
edges
|
|
778
931
|
};
|
|
@@ -989,19 +1142,30 @@ function getPolylineLabelPoint(points) {
|
|
|
989
1142
|
const last = points[points.length - 1] || { x: 0, y: 0 };
|
|
990
1143
|
return { x: formatNumber(last.x), y: formatNumber(last.y) };
|
|
991
1144
|
}
|
|
992
|
-
function
|
|
1145
|
+
function routeMetrics(edgeScale = 1) {
|
|
1146
|
+
const scale = clamp(toFiniteNumber(edgeScale, 1), 0.01, 1);
|
|
1147
|
+
return {
|
|
1148
|
+
scale,
|
|
1149
|
+
minCurveArm: MIN_CURVE_ARM * scale,
|
|
1150
|
+
maxCurveArm: MAX_CURVE_ARM * scale,
|
|
1151
|
+
stub: ORTHOGONAL_STUB_LENGTH * scale,
|
|
1152
|
+
corner: ORTHOGONAL_CORNER_RADIUS * scale,
|
|
1153
|
+
clearance: ORTHOGONAL_CLEARANCE * scale
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
function curveControlPoint(point, port, target, curvature, metrics = routeMetrics(1)) {
|
|
993
1157
|
const normal = getPortNormal(port);
|
|
994
1158
|
const dx = target.x - point.x;
|
|
995
1159
|
const dy = target.y - point.y;
|
|
996
1160
|
const distance = Math.hypot(dx, dy) || 1;
|
|
997
1161
|
const align = (normal.x * dx + normal.y * dy) / distance;
|
|
998
1162
|
const ease = 0.55 + 0.45 * align;
|
|
999
|
-
const arm = clamp(distance * curvature * ease,
|
|
1163
|
+
const arm = clamp(distance * curvature * ease, metrics.minCurveArm, metrics.maxCurveArm);
|
|
1000
1164
|
return { x: point.x + normal.x * arm, y: point.y + normal.y * arm };
|
|
1001
1165
|
}
|
|
1002
|
-
function buildCurvePath(fromPoint, toPoint, fromPort = "right", toPort = "left") {
|
|
1003
|
-
const controlA = curveControlPoint(fromPoint, fromPort, toPoint, EDGE_CURVATURE);
|
|
1004
|
-
const controlB = curveControlPoint(toPoint, toPort, fromPoint, EDGE_CURVATURE);
|
|
1166
|
+
function buildCurvePath(fromPoint, toPoint, fromPort = "right", toPort = "left", metrics = routeMetrics(1)) {
|
|
1167
|
+
const controlA = curveControlPoint(fromPoint, fromPort, toPoint, EDGE_CURVATURE, metrics);
|
|
1168
|
+
const controlB = curveControlPoint(toPoint, toPort, fromPoint, EDGE_CURVATURE, metrics);
|
|
1005
1169
|
const labelX = 0.125 * fromPoint.x + 0.375 * controlA.x + 0.375 * controlB.x + 0.125 * toPoint.x;
|
|
1006
1170
|
const labelY = 0.125 * fromPoint.y + 0.375 * controlA.y + 0.375 * controlB.y + 0.125 * toPoint.y;
|
|
1007
1171
|
return {
|
|
@@ -1063,13 +1227,40 @@ function getOrthogonalMidpoints(fromStub, toStub, fromPort, toPort) {
|
|
|
1063
1227
|
}
|
|
1064
1228
|
return [{ x: fromStub.x, y: toStub.y }];
|
|
1065
1229
|
}
|
|
1066
|
-
function getOrthogonalPoints(fromPoint, toPoint, fromPort, toPort, fromRect, toRect) {
|
|
1230
|
+
function getOrthogonalPoints(fromPoint, toPoint, fromPort, toPort, fromRect, toRect, metrics = routeMetrics(1)) {
|
|
1067
1231
|
const fromNormal = getPortNormal(fromPort);
|
|
1068
1232
|
const toNormal = getPortNormal(toPort);
|
|
1069
|
-
const fromStub = offsetPoint(fromPoint, fromNormal, ORTHOGONAL_STUB_LENGTH);
|
|
1070
|
-
const toStub = offsetPoint(toPoint, toNormal, ORTHOGONAL_STUB_LENGTH);
|
|
1071
1233
|
const sourceHorizontal = isHorizontalPort(fromPort);
|
|
1072
1234
|
const targetHorizontal = isHorizontalPort(toPort);
|
|
1235
|
+
const minStub = 4 * (metrics.scale || 1);
|
|
1236
|
+
let fromStubLen = metrics.stub;
|
|
1237
|
+
let toStubLen = metrics.stub;
|
|
1238
|
+
if (sourceHorizontal && targetHorizontal && fromNormal.x === -toNormal.x) {
|
|
1239
|
+
const span = (toPoint.x - fromPoint.x) * fromNormal.x;
|
|
1240
|
+
if (span > 0) {
|
|
1241
|
+
const fitted = Math.min(metrics.stub, span * 0.45, span / 2);
|
|
1242
|
+
fromStubLen = fitted;
|
|
1243
|
+
toStubLen = fitted;
|
|
1244
|
+
}
|
|
1245
|
+
} else if (!sourceHorizontal && !targetHorizontal && fromNormal.y === -toNormal.y) {
|
|
1246
|
+
const span = (toPoint.y - fromPoint.y) * fromNormal.y;
|
|
1247
|
+
if (span > 0) {
|
|
1248
|
+
const fitted = Math.min(metrics.stub, span * 0.45, span / 2);
|
|
1249
|
+
fromStubLen = fitted;
|
|
1250
|
+
toStubLen = fitted;
|
|
1251
|
+
}
|
|
1252
|
+
} else if (sourceHorizontal !== targetHorizontal) {
|
|
1253
|
+
const spanFrom = sourceHorizontal ? (toPoint.x - fromPoint.x) * fromNormal.x : (toPoint.y - fromPoint.y) * fromNormal.y;
|
|
1254
|
+
const spanTo = targetHorizontal ? (fromPoint.x - toPoint.x) * toNormal.x : (fromPoint.y - toPoint.y) * toNormal.y;
|
|
1255
|
+
if (spanFrom > 0 && spanFrom < metrics.stub) {
|
|
1256
|
+
fromStubLen = Math.min(spanFrom / 2, Math.max(minStub, spanFrom * 0.5));
|
|
1257
|
+
}
|
|
1258
|
+
if (spanTo > 0 && spanTo < metrics.stub) {
|
|
1259
|
+
toStubLen = Math.min(spanTo / 2, Math.max(minStub, spanTo * 0.5));
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
const fromStub = offsetPoint(fromPoint, fromNormal, fromStubLen);
|
|
1263
|
+
const toStub = offsetPoint(toPoint, toNormal, toStubLen);
|
|
1073
1264
|
const mids = [];
|
|
1074
1265
|
if (sourceHorizontal && targetHorizontal) {
|
|
1075
1266
|
const facing = fromNormal.x === -toNormal.x;
|
|
@@ -1107,13 +1298,13 @@ function getOrthogonalPoints(fromPoint, toPoint, fromPort, toPort, fromRect, toR
|
|
|
1107
1298
|
);
|
|
1108
1299
|
return points;
|
|
1109
1300
|
}
|
|
1110
|
-
function buildOrthogonalPath(fromPoint, toPoint, fromPort = "right", toPort = "left", fromRect = null, toRect = null) {
|
|
1301
|
+
function buildOrthogonalPath(fromPoint, toPoint, fromPort = "right", toPort = "left", fromRect = null, toRect = null, metrics = routeMetrics(1)) {
|
|
1111
1302
|
let points;
|
|
1112
1303
|
if (fromRect && toRect) {
|
|
1113
|
-
points = getOrthogonalPoints(fromPoint, toPoint, fromPort, toPort, fromRect, toRect);
|
|
1304
|
+
points = getOrthogonalPoints(fromPoint, toPoint, fromPort, toPort, fromRect, toRect, metrics);
|
|
1114
1305
|
} else {
|
|
1115
|
-
const fromStub = offsetPoint(fromPoint, getPortNormal(fromPort),
|
|
1116
|
-
const toStub = offsetPoint(toPoint, getPortNormal(toPort),
|
|
1306
|
+
const fromStub = offsetPoint(fromPoint, getPortNormal(fromPort), metrics.stub);
|
|
1307
|
+
const toStub = offsetPoint(toPoint, getPortNormal(toPort), metrics.stub);
|
|
1117
1308
|
points = [];
|
|
1118
1309
|
[
|
|
1119
1310
|
fromPoint,
|
|
@@ -1125,7 +1316,7 @@ function buildOrthogonalPath(fromPoint, toPoint, fromPort = "right", toPort = "l
|
|
|
1125
1316
|
}
|
|
1126
1317
|
const label = getPolylineLabelPoint(points);
|
|
1127
1318
|
return {
|
|
1128
|
-
d: buildRoundedPath(points,
|
|
1319
|
+
d: buildRoundedPath(points, metrics.corner),
|
|
1129
1320
|
labelX: label.x,
|
|
1130
1321
|
labelY: label.y
|
|
1131
1322
|
};
|
|
@@ -1136,6 +1327,7 @@ function buildEdgePath(edge, fromNode = null, toNode = null) {
|
|
|
1136
1327
|
const fromPoint = edge?.fromPoint || (fromNode ? getPortPosition(fromNode, fromPort) : null);
|
|
1137
1328
|
const toPoint = edge?.toPoint || (toNode ? getPortPosition(toNode, toPort) : null);
|
|
1138
1329
|
const route = normalizeEdgeRoute(edge?.route);
|
|
1330
|
+
const metrics = routeMetrics(edgeVisualScale(fromNode, toNode));
|
|
1139
1331
|
if (!fromPoint || !toPoint) {
|
|
1140
1332
|
return { d: "", labelX: 0, labelY: 0 };
|
|
1141
1333
|
}
|
|
@@ -1143,15 +1335,17 @@ function buildEdgePath(edge, fromNode = null, toNode = null) {
|
|
|
1143
1335
|
return buildStraightPath(fromPoint, toPoint);
|
|
1144
1336
|
}
|
|
1145
1337
|
if (route === "orthogonal") {
|
|
1146
|
-
const fromRect = !edge?.fromPoint && fromNode ? getNodeRectBounds(fromNode,
|
|
1147
|
-
const toRect = !edge?.toPoint && toNode ? getNodeRectBounds(toNode,
|
|
1148
|
-
return buildOrthogonalPath(fromPoint, toPoint, fromPort, toPort, fromRect, toRect);
|
|
1338
|
+
const fromRect = !edge?.fromPoint && fromNode ? getNodeRectBounds(fromNode, metrics.clearance) : null;
|
|
1339
|
+
const toRect = !edge?.toPoint && toNode ? getNodeRectBounds(toNode, metrics.clearance) : null;
|
|
1340
|
+
return buildOrthogonalPath(fromPoint, toPoint, fromPort, toPort, fromRect, toRect, metrics);
|
|
1149
1341
|
}
|
|
1150
|
-
return buildCurvePath(fromPoint, toPoint, fromPort, toPort);
|
|
1342
|
+
return buildCurvePath(fromPoint, toPoint, fromPort, toPort, metrics);
|
|
1151
1343
|
}
|
|
1152
|
-
function createArrowMarker(id, strokeWidth, reversed = false) {
|
|
1153
|
-
const
|
|
1154
|
-
const
|
|
1344
|
+
function createArrowMarker(id, strokeWidth, reversed = false, edgeScale = 1) {
|
|
1345
|
+
const scale = clamp(toFiniteNumber(edgeScale, 1), 0.01, 1);
|
|
1346
|
+
const effective = Math.max(0.02, strokeWidth * scale);
|
|
1347
|
+
const size = formatNumber(Math.max(9 * scale, effective * 4.4));
|
|
1348
|
+
const refInset = formatNumber(Math.max(1.5 * scale, effective * 0.85));
|
|
1155
1349
|
const marker = svgEl("marker", {
|
|
1156
1350
|
id,
|
|
1157
1351
|
markerWidth: size,
|
|
@@ -1169,9 +1363,11 @@ function createArrowMarker(id, strokeWidth, reversed = false) {
|
|
|
1169
1363
|
);
|
|
1170
1364
|
return marker;
|
|
1171
1365
|
}
|
|
1172
|
-
function createDotMarker(id, strokeWidth) {
|
|
1173
|
-
const
|
|
1174
|
-
const
|
|
1366
|
+
function createDotMarker(id, strokeWidth, edgeScale = 1) {
|
|
1367
|
+
const scale = clamp(toFiniteNumber(edgeScale, 1), 0.01, 1);
|
|
1368
|
+
const effective = Math.max(0.02, strokeWidth * scale);
|
|
1369
|
+
const size = formatNumber(Math.max(8 * scale, effective * 3.3));
|
|
1370
|
+
const radius = formatNumber(Math.max(2.75 * scale, effective * 1.45));
|
|
1175
1371
|
const marker = svgEl("marker", {
|
|
1176
1372
|
id,
|
|
1177
1373
|
markerWidth: size,
|
|
@@ -1428,7 +1624,23 @@ var VdFlowchart = class {
|
|
|
1428
1624
|
this.element = resolveElement(options.element || options.target);
|
|
1429
1625
|
this.readonly = Boolean(options.readonly);
|
|
1430
1626
|
this.gridSize = clamp(toFiniteNumber(options.gridSize, DEFAULT_GRID_SIZE), 12, 64);
|
|
1431
|
-
|
|
1627
|
+
const scaleBounds = normalizeBoundPair(
|
|
1628
|
+
options.minScale,
|
|
1629
|
+
options.maxScale,
|
|
1630
|
+
DEFAULT_MIN_SCALE,
|
|
1631
|
+
DEFAULT_MAX_SCALE
|
|
1632
|
+
);
|
|
1633
|
+
const sizeBounds = normalizeBoundPair(
|
|
1634
|
+
options.minNodeSize,
|
|
1635
|
+
options.maxNodeSize,
|
|
1636
|
+
DEFAULT_MIN_NODE_SIZE,
|
|
1637
|
+
DEFAULT_MAX_NODE_SIZE
|
|
1638
|
+
);
|
|
1639
|
+
this.minScale = scaleBounds.min;
|
|
1640
|
+
this.maxScale = scaleBounds.max;
|
|
1641
|
+
this.minNodeSize = sizeBounds.min;
|
|
1642
|
+
this.maxNodeSize = sizeBounds.max;
|
|
1643
|
+
this.documentData = normalizeDocument(options.data || {}, { scaleBounds, sizeBounds });
|
|
1432
1644
|
this.listeners = {};
|
|
1433
1645
|
this.selection = null;
|
|
1434
1646
|
this.selectedNodeIds = /* @__PURE__ */ new Set();
|
|
@@ -1471,6 +1683,7 @@ var VdFlowchart = class {
|
|
|
1471
1683
|
this.handleClick = this.handleClick.bind(this);
|
|
1472
1684
|
this.handleDoubleClick = this.handleDoubleClick.bind(this);
|
|
1473
1685
|
this.handleWheel = this.handleWheel.bind(this);
|
|
1686
|
+
this.handleCanvasBlur = this.handleCanvasBlur.bind(this);
|
|
1474
1687
|
this.handleKeyDown = this.handleKeyDown.bind(this);
|
|
1475
1688
|
this.handleSelectionFieldInput = this.handleSelectionFieldInput.bind(this);
|
|
1476
1689
|
this.handleSelectionFieldChange = this.handleSelectionFieldChange.bind(this);
|
|
@@ -1656,10 +1869,10 @@ var VdFlowchart = class {
|
|
|
1656
1869
|
this.world = svgEl("g", { class: "vd-flowchart-world" });
|
|
1657
1870
|
this.gridRect = svgEl("rect", {
|
|
1658
1871
|
class: "vd-flowchart-grid",
|
|
1659
|
-
x: -
|
|
1660
|
-
y: -
|
|
1661
|
-
width:
|
|
1662
|
-
height:
|
|
1872
|
+
x: -DEFAULT_WORLD_EXTENT / 2,
|
|
1873
|
+
y: -DEFAULT_WORLD_EXTENT / 2,
|
|
1874
|
+
width: DEFAULT_WORLD_EXTENT,
|
|
1875
|
+
height: DEFAULT_WORLD_EXTENT,
|
|
1663
1876
|
fill: `url(#${this.gridPatternId})`
|
|
1664
1877
|
});
|
|
1665
1878
|
this.edgesLayer = svgEl("g", { class: "vd-flowchart-edges" });
|
|
@@ -1883,6 +2096,7 @@ var VdFlowchart = class {
|
|
|
1883
2096
|
this.canvasEl.addEventListener("click", this.handleClick);
|
|
1884
2097
|
this.canvasEl.addEventListener("dblclick", this.handleDoubleClick);
|
|
1885
2098
|
this.canvasEl.addEventListener("wheel", this.handleWheel, { passive: false });
|
|
2099
|
+
this.canvasEl.addEventListener("blur", this.handleCanvasBlur);
|
|
1886
2100
|
this.root.addEventListener("keydown", this.handleKeyDown, true);
|
|
1887
2101
|
window.addEventListener("pointerup", this.handlePointerUp);
|
|
1888
2102
|
window.addEventListener("resize", this.handleResize);
|
|
@@ -1901,6 +2115,7 @@ var VdFlowchart = class {
|
|
|
1901
2115
|
this.canvasEl.removeEventListener("click", this.handleClick);
|
|
1902
2116
|
this.canvasEl.removeEventListener("dblclick", this.handleDoubleClick);
|
|
1903
2117
|
this.canvasEl.removeEventListener("wheel", this.handleWheel);
|
|
2118
|
+
this.canvasEl.removeEventListener("blur", this.handleCanvasBlur);
|
|
1904
2119
|
this.root.removeEventListener("keydown", this.handleKeyDown, true);
|
|
1905
2120
|
window.removeEventListener("pointerup", this.handlePointerUp);
|
|
1906
2121
|
window.removeEventListener("resize", this.handleResize);
|
|
@@ -1909,6 +2124,19 @@ var VdFlowchart = class {
|
|
|
1909
2124
|
if (this.destroyed) return;
|
|
1910
2125
|
this.render({ inspector: false, json: false });
|
|
1911
2126
|
}
|
|
2127
|
+
/**
|
|
2128
|
+
* Focus the diagram canvas. Quiet focus returns from an inline editor without
|
|
2129
|
+
* painting the full-canvas `:focus-visible` ring (see `.is-quiet-focus`).
|
|
2130
|
+
*/
|
|
2131
|
+
focusCanvas(options = {}) {
|
|
2132
|
+
if (!this.canvasEl || this.destroyed) return;
|
|
2133
|
+
const quiet = options.quiet === true;
|
|
2134
|
+
this.canvasEl.classList.toggle("is-quiet-focus", quiet);
|
|
2135
|
+
this.canvasEl.focus({ preventScroll: true });
|
|
2136
|
+
}
|
|
2137
|
+
handleCanvasBlur() {
|
|
2138
|
+
this.canvasEl?.classList.remove("is-quiet-focus");
|
|
2139
|
+
}
|
|
1912
2140
|
updatePaletteState() {
|
|
1913
2141
|
const arrowArmed = this.activeTool === "arrow";
|
|
1914
2142
|
this.paletteGrid.querySelectorAll(".vd-flowchart-palette-btn").forEach((button) => {
|
|
@@ -2442,7 +2670,7 @@ var VdFlowchart = class {
|
|
|
2442
2670
|
const width = this.canvasEl.clientWidth || 800;
|
|
2443
2671
|
const height = this.canvasEl.clientHeight || 560;
|
|
2444
2672
|
this.scaleAround(
|
|
2445
|
-
clamp(scale,
|
|
2673
|
+
clamp(formatScale(scale), this.minScale, this.maxScale) / viewport.scale,
|
|
2446
2674
|
width / 2,
|
|
2447
2675
|
height / 2,
|
|
2448
2676
|
"viewport:zoom"
|
|
@@ -2477,7 +2705,8 @@ var VdFlowchart = class {
|
|
|
2477
2705
|
const node = normalizeNode(
|
|
2478
2706
|
{ ...data, id: void 0, x: data.x + 24, y: data.y + 24 },
|
|
2479
2707
|
this.documentData.nodes.length + index,
|
|
2480
|
-
usedNodeIds
|
|
2708
|
+
usedNodeIds,
|
|
2709
|
+
this.nodeSizeBounds()
|
|
2481
2710
|
);
|
|
2482
2711
|
idMap.set(data.id, node.id);
|
|
2483
2712
|
return node;
|
|
@@ -2509,7 +2738,8 @@ var VdFlowchart = class {
|
|
|
2509
2738
|
const node = normalizeNode(
|
|
2510
2739
|
{ ...data, id: void 0, collapsed: void 0, x: data.x + 24, y: data.y + 24 },
|
|
2511
2740
|
this.documentData.nodes.length,
|
|
2512
|
-
usedIds
|
|
2741
|
+
usedIds,
|
|
2742
|
+
this.nodeSizeBounds()
|
|
2513
2743
|
);
|
|
2514
2744
|
this.documentData.nodes.push(node);
|
|
2515
2745
|
this.select({ kind: "node", id: node.id });
|
|
@@ -3123,14 +3353,15 @@ var VdFlowchart = class {
|
|
|
3123
3353
|
if (this.activeTool === "arrow") return true;
|
|
3124
3354
|
return this.selection?.kind === "node" && this.selection.id === node.id && !this.hasMultiSelection();
|
|
3125
3355
|
}
|
|
3126
|
-
getMarkerId(markerType, position, strokeWidth) {
|
|
3356
|
+
getMarkerId(markerType, position, strokeWidth, edgeScale = 1) {
|
|
3127
3357
|
const width = normalizeEdgeStrokeWidth(strokeWidth);
|
|
3128
|
-
const
|
|
3358
|
+
const scale = clamp(formatNumber(Math.max(edgeScale, 0.01)), 0.01, 1);
|
|
3359
|
+
const key = `${markerType}:${position}:${width}:${scale}`;
|
|
3129
3360
|
if (this.markerIds.has(key)) {
|
|
3130
3361
|
return this.markerIds.get(key);
|
|
3131
3362
|
}
|
|
3132
3363
|
const markerId = nextId(`flowchart-${markerType}-${position}`);
|
|
3133
|
-
const marker = markerType === "dot" ? createDotMarker(markerId, width) : createArrowMarker(markerId, width, position === "start");
|
|
3364
|
+
const marker = markerType === "dot" ? createDotMarker(markerId, width, scale) : createArrowMarker(markerId, width, position === "start", scale);
|
|
3134
3365
|
this.markerDefs.appendChild(marker);
|
|
3135
3366
|
this.markerIds.set(key, markerId);
|
|
3136
3367
|
return markerId;
|
|
@@ -3138,8 +3369,15 @@ var VdFlowchart = class {
|
|
|
3138
3369
|
getEdgeStrokeWidth(edge) {
|
|
3139
3370
|
return normalizeEdgeStrokeWidth(edge?.strokeWidth);
|
|
3140
3371
|
}
|
|
3141
|
-
getEdgeHitStrokeWidth(edge) {
|
|
3142
|
-
|
|
3372
|
+
getEdgeHitStrokeWidth(edge, edgeScale = 1) {
|
|
3373
|
+
const visual = clamp(toFiniteNumber(edgeScale, 1), 0.01, 1);
|
|
3374
|
+
const base = this.getEdgeStrokeWidth(edge);
|
|
3375
|
+
const view = Math.max(this.documentData?.viewport?.scale || 1, 1e-4);
|
|
3376
|
+
const legacy = Math.max(EDGE_HIT_STROKE_MIN, base + 12);
|
|
3377
|
+
if (visual >= 0.999 && view >= DEFAULT_MIN_SCALE && view <= DEFAULT_MAX_SCALE) {
|
|
3378
|
+
return formatNumber(legacy);
|
|
3379
|
+
}
|
|
3380
|
+
return formatNumber(Math.max(base * visual, EDGE_HIT_STROKE_MIN / view));
|
|
3143
3381
|
}
|
|
3144
3382
|
findConnectionTarget(worldPoint, excludeNodeId, previousSnap = null, referencePoint = null) {
|
|
3145
3383
|
const snapPadding = CONNECTION_SNAP_PADDING / this.documentData.viewport.scale;
|
|
@@ -3196,17 +3434,37 @@ var VdFlowchart = class {
|
|
|
3196
3434
|
let width = original.width;
|
|
3197
3435
|
let height = original.height;
|
|
3198
3436
|
if (handle.includes("e")) {
|
|
3199
|
-
width = clampNodeWidth(
|
|
3437
|
+
width = clampNodeWidth(
|
|
3438
|
+
nextType,
|
|
3439
|
+
original.width + deltaX,
|
|
3440
|
+
original.width,
|
|
3441
|
+
this.nodeSizeBounds()
|
|
3442
|
+
);
|
|
3200
3443
|
}
|
|
3201
3444
|
if (handle.includes("s")) {
|
|
3202
|
-
height = clampNodeHeight(
|
|
3445
|
+
height = clampNodeHeight(
|
|
3446
|
+
nextType,
|
|
3447
|
+
original.height + deltaY,
|
|
3448
|
+
original.height,
|
|
3449
|
+
this.nodeSizeBounds()
|
|
3450
|
+
);
|
|
3203
3451
|
}
|
|
3204
3452
|
if (handle.includes("w")) {
|
|
3205
|
-
width = clampNodeWidth(
|
|
3453
|
+
width = clampNodeWidth(
|
|
3454
|
+
nextType,
|
|
3455
|
+
original.width - deltaX,
|
|
3456
|
+
original.width,
|
|
3457
|
+
this.nodeSizeBounds()
|
|
3458
|
+
);
|
|
3206
3459
|
x = original.x + original.width - width;
|
|
3207
3460
|
}
|
|
3208
3461
|
if (handle.includes("n")) {
|
|
3209
|
-
height = clampNodeHeight(
|
|
3462
|
+
height = clampNodeHeight(
|
|
3463
|
+
nextType,
|
|
3464
|
+
original.height - deltaY,
|
|
3465
|
+
original.height,
|
|
3466
|
+
this.nodeSizeBounds()
|
|
3467
|
+
);
|
|
3210
3468
|
y = original.y + original.height - height;
|
|
3211
3469
|
}
|
|
3212
3470
|
return {
|
|
@@ -3242,7 +3500,7 @@ var VdFlowchart = class {
|
|
|
3242
3500
|
scaleAround(factor, localX, localY, reason) {
|
|
3243
3501
|
const viewport = this.documentData.viewport;
|
|
3244
3502
|
const anchor = this.localToWorld(localX, localY);
|
|
3245
|
-
const nextScale = clamp(
|
|
3503
|
+
const nextScale = clamp(formatScale(viewport.scale * factor), this.minScale, this.maxScale);
|
|
3246
3504
|
if (nextScale === viewport.scale) return;
|
|
3247
3505
|
viewport.scale = nextScale;
|
|
3248
3506
|
viewport.x = formatNumber(localX - anchor.x * nextScale);
|
|
@@ -3334,7 +3592,7 @@ var VdFlowchart = class {
|
|
|
3334
3592
|
if (event.key === "Escape" || event.key === "Enter") {
|
|
3335
3593
|
event.preventDefault();
|
|
3336
3594
|
this.stopTextEdit({ commit: event.key === "Enter" });
|
|
3337
|
-
this.
|
|
3595
|
+
this.focusCanvas({ quiet: true });
|
|
3338
3596
|
}
|
|
3339
3597
|
});
|
|
3340
3598
|
textarea.addEventListener("blur", () => {
|
|
@@ -3361,7 +3619,7 @@ var VdFlowchart = class {
|
|
|
3361
3619
|
if (event.key === "Escape") {
|
|
3362
3620
|
event.preventDefault();
|
|
3363
3621
|
this.stopTextEdit({ commit: false });
|
|
3364
|
-
this.
|
|
3622
|
+
this.focusCanvas({ quiet: true });
|
|
3365
3623
|
return;
|
|
3366
3624
|
}
|
|
3367
3625
|
if (event.key === "Enter") {
|
|
@@ -3370,13 +3628,12 @@ var VdFlowchart = class {
|
|
|
3370
3628
|
if (!commits) return;
|
|
3371
3629
|
event.preventDefault();
|
|
3372
3630
|
this.stopTextEdit({ commit: true });
|
|
3373
|
-
this.
|
|
3631
|
+
this.focusCanvas({ quiet: true });
|
|
3374
3632
|
return;
|
|
3375
3633
|
}
|
|
3376
3634
|
if (event.key === "Tab" && mindmap && !event.shiftKey && !event.altKey && !event.ctrlKey) {
|
|
3377
3635
|
event.preventDefault();
|
|
3378
3636
|
this.stopTextEdit({ commit: true });
|
|
3379
|
-
this.canvasEl.focus();
|
|
3380
3637
|
this.insertBranchNode(node.id, { edit: true });
|
|
3381
3638
|
}
|
|
3382
3639
|
}
|
|
@@ -3425,19 +3682,20 @@ var VdFlowchart = class {
|
|
|
3425
3682
|
}
|
|
3426
3683
|
const viewport = this.documentData.viewport;
|
|
3427
3684
|
const scale = viewport.scale;
|
|
3685
|
+
const visual = nodeVisualScale(node);
|
|
3428
3686
|
const textarea = this.textEditor.textarea;
|
|
3429
3687
|
const width = node.width * scale;
|
|
3430
3688
|
const height = node.height * scale;
|
|
3431
3689
|
const metrics = getNodeFontMetrics(node);
|
|
3432
|
-
const lineHeight = metrics.lineHeight * scale;
|
|
3690
|
+
const lineHeight = metrics.lineHeight * visual * scale;
|
|
3433
3691
|
const lineCount = Math.max(1, textarea.value.split(/\r?\n/).length);
|
|
3434
|
-
const horizontalPadding = (node.type === "textbox" ? 16 : 12) * scale;
|
|
3435
|
-
const verticalPadding = node.type === "textbox" ? 12 * scale : Math.max(4, (height - lineCount * lineHeight) / 2);
|
|
3692
|
+
const horizontalPadding = (node.type === "textbox" ? 16 : 12) * visual * scale;
|
|
3693
|
+
const verticalPadding = node.type === "textbox" ? 12 * visual * scale : Math.max(4 * visual, (height - lineCount * lineHeight) / 2);
|
|
3436
3694
|
textarea.style.left = `${formatNumber(viewport.x + node.x * scale)}px`;
|
|
3437
3695
|
textarea.style.top = `${formatNumber(viewport.y + node.y * scale)}px`;
|
|
3438
3696
|
textarea.style.width = `${formatNumber(width)}px`;
|
|
3439
3697
|
textarea.style.height = `${formatNumber(height)}px`;
|
|
3440
|
-
textarea.style.fontSize = `${formatNumber(metrics.fontSize * scale)}px`;
|
|
3698
|
+
textarea.style.fontSize = `${formatNumber(metrics.fontSize * visual * scale)}px`;
|
|
3441
3699
|
textarea.style.lineHeight = `${formatNumber(lineHeight)}px`;
|
|
3442
3700
|
textarea.style.padding = `${formatNumber(verticalPadding)}px ${formatNumber(horizontalPadding)}px`;
|
|
3443
3701
|
}
|
|
@@ -3449,14 +3707,16 @@ var VdFlowchart = class {
|
|
|
3449
3707
|
this.stopTextEdit({ commit: false });
|
|
3450
3708
|
return;
|
|
3451
3709
|
}
|
|
3710
|
+
const visual = edgeVisualScale(fromNode, toNode);
|
|
3452
3711
|
const { labelX, labelY } = buildEdgePath(edge, fromNode, toNode);
|
|
3453
3712
|
const viewport = this.documentData.viewport;
|
|
3454
3713
|
const textarea = this.textEditor.textarea;
|
|
3455
|
-
const fontSize = Math.max(11, 13 * viewport.scale);
|
|
3456
|
-
const width = clamp(textarea.value.length * fontSize * 0.62 + 28, 96, 280);
|
|
3457
|
-
const height = fontSize * 1.4 + 12;
|
|
3714
|
+
const fontSize = Math.max(11 * visual, 13 * visual * viewport.scale);
|
|
3715
|
+
const width = clamp(textarea.value.length * fontSize * 0.62 + 28 * visual, 96 * visual, 280);
|
|
3716
|
+
const height = fontSize * 1.4 + 12 * visual;
|
|
3717
|
+
const labelOffset = 8 * visual;
|
|
3458
3718
|
textarea.style.left = `${formatNumber(viewport.x + labelX * viewport.scale - width / 2)}px`;
|
|
3459
|
-
textarea.style.top = `${formatNumber(viewport.y + (labelY -
|
|
3719
|
+
textarea.style.top = `${formatNumber(viewport.y + (labelY - labelOffset) * viewport.scale - height / 2)}px`;
|
|
3460
3720
|
textarea.style.width = `${formatNumber(width)}px`;
|
|
3461
3721
|
textarea.style.height = `${formatNumber(height)}px`;
|
|
3462
3722
|
textarea.style.fontSize = `${formatNumber(fontSize)}px`;
|
|
@@ -3465,6 +3725,7 @@ var VdFlowchart = class {
|
|
|
3465
3725
|
}
|
|
3466
3726
|
render(options = {}) {
|
|
3467
3727
|
if (this.destroyed) return;
|
|
3728
|
+
this.syncGrid();
|
|
3468
3729
|
const shouldRenderScene = options.scene !== false;
|
|
3469
3730
|
const shouldRenderInspector = options.inspector !== false;
|
|
3470
3731
|
const shouldRenderJson = options.json !== false;
|
|
@@ -3488,6 +3749,8 @@ var VdFlowchart = class {
|
|
|
3488
3749
|
clearChildren(this.previewLayer);
|
|
3489
3750
|
clearChildren(this.nodesLayer);
|
|
3490
3751
|
clearChildren(this.overlayLayer);
|
|
3752
|
+
clearChildren(this.markerDefs);
|
|
3753
|
+
this.markerIds = /* @__PURE__ */ new Map();
|
|
3491
3754
|
const hidden = this.getHiddenNodeIds();
|
|
3492
3755
|
this.sceneHidden = hidden;
|
|
3493
3756
|
this.sceneOutgoing = this.getOutgoingMap();
|
|
@@ -3551,17 +3814,18 @@ var VdFlowchart = class {
|
|
|
3551
3814
|
}
|
|
3552
3815
|
}
|
|
3553
3816
|
}
|
|
3554
|
-
applyEdgeMarkers(pathElement, edge) {
|
|
3817
|
+
applyEdgeMarkers(pathElement, edge, edgeScale = 1) {
|
|
3818
|
+
const width = this.getEdgeStrokeWidth(edge);
|
|
3555
3819
|
if (edge.startMarker && edge.startMarker !== "none") {
|
|
3556
3820
|
pathElement.setAttribute(
|
|
3557
3821
|
"marker-start",
|
|
3558
|
-
`url(#${this.getMarkerId(edge.startMarker, "start",
|
|
3822
|
+
`url(#${this.getMarkerId(edge.startMarker, "start", width, edgeScale)})`
|
|
3559
3823
|
);
|
|
3560
3824
|
}
|
|
3561
3825
|
if (edge.endMarker && edge.endMarker !== "none") {
|
|
3562
3826
|
pathElement.setAttribute(
|
|
3563
3827
|
"marker-end",
|
|
3564
|
-
`url(#${this.getMarkerId(edge.endMarker, "end",
|
|
3828
|
+
`url(#${this.getMarkerId(edge.endMarker, "end", width, edgeScale)})`
|
|
3565
3829
|
);
|
|
3566
3830
|
}
|
|
3567
3831
|
}
|
|
@@ -3569,6 +3833,7 @@ var VdFlowchart = class {
|
|
|
3569
3833
|
const fromNode = nodeMap.get(edge.from.nodeId);
|
|
3570
3834
|
const toNode = nodeMap.get(edge.to.nodeId);
|
|
3571
3835
|
if (!fromNode || !toNode) return null;
|
|
3836
|
+
const visual = edgeVisualScale(fromNode, toNode);
|
|
3572
3837
|
const edgePath = buildEdgePath(edge, fromNode, toNode);
|
|
3573
3838
|
const selected = this.selection?.kind === "edge" && this.selection.id === edge.id;
|
|
3574
3839
|
const inReconnectMode = this.reconnectEdgeId === edge.id;
|
|
@@ -3576,13 +3841,14 @@ var VdFlowchart = class {
|
|
|
3576
3841
|
class: `vd-flowchart-edge${selected ? " is-selected" : ""}${inReconnectMode ? " is-reconnecting" : ""}`,
|
|
3577
3842
|
"data-edge-id": edge.id
|
|
3578
3843
|
});
|
|
3579
|
-
const
|
|
3844
|
+
const baseStroke = this.getEdgeStrokeWidth(edge);
|
|
3845
|
+
const strokeWidth = visual < 0.999 ? formatNumber(Math.max(0.02, baseStroke * visual)) : baseStroke;
|
|
3580
3846
|
if (selected) {
|
|
3581
3847
|
group.appendChild(
|
|
3582
3848
|
svgEl("path", {
|
|
3583
3849
|
class: "vd-flowchart-edge-selection",
|
|
3584
3850
|
d: edgePath.d,
|
|
3585
|
-
"stroke-width": formatNumber(strokeWidth + 7)
|
|
3851
|
+
"stroke-width": visual < 0.999 ? formatNumber((baseStroke + 7) * visual) : formatNumber(strokeWidth + 7)
|
|
3586
3852
|
})
|
|
3587
3853
|
);
|
|
3588
3854
|
}
|
|
@@ -3592,23 +3858,28 @@ var VdFlowchart = class {
|
|
|
3592
3858
|
"stroke-width": strokeWidth,
|
|
3593
3859
|
"data-edge-id": edge.id
|
|
3594
3860
|
});
|
|
3595
|
-
this.applyEdgeMarkers(visiblePath, edge);
|
|
3861
|
+
this.applyEdgeMarkers(visiblePath, edge, visual);
|
|
3596
3862
|
const hitPath = svgEl("path", {
|
|
3597
3863
|
class: "vd-flowchart-edge-hit",
|
|
3598
3864
|
d: edgePath.d,
|
|
3599
|
-
"stroke-width": this.getEdgeHitStrokeWidth(edge),
|
|
3865
|
+
"stroke-width": this.getEdgeHitStrokeWidth(edge, visual),
|
|
3600
3866
|
"data-edge-id": edge.id
|
|
3601
3867
|
});
|
|
3602
3868
|
group.appendChild(visiblePath);
|
|
3603
3869
|
group.appendChild(hitPath);
|
|
3604
3870
|
if (edge.label && this.textEditor?.edgeId !== edge.id) {
|
|
3871
|
+
const labelOffset = visual < 0.999 ? formatNumber(8 * visual) : 8;
|
|
3605
3872
|
const label = svgEl("text", {
|
|
3606
3873
|
class: "vd-flowchart-edge-label",
|
|
3607
3874
|
x: edgePath.labelX,
|
|
3608
|
-
y: edgePath.labelY -
|
|
3875
|
+
y: edgePath.labelY - labelOffset,
|
|
3609
3876
|
"text-anchor": "middle",
|
|
3610
3877
|
"data-edge-id": edge.id
|
|
3611
3878
|
});
|
|
3879
|
+
if (visual < 0.999) {
|
|
3880
|
+
label.style.fontSize = `${formatNumber(Math.max(0.06, 12 * visual))}px`;
|
|
3881
|
+
label.style.strokeWidth = `${formatNumber(Math.max(0.03, 5 * visual))}px`;
|
|
3882
|
+
}
|
|
3612
3883
|
label.textContent = edge.label;
|
|
3613
3884
|
group.appendChild(label);
|
|
3614
3885
|
}
|
|
@@ -3647,18 +3918,26 @@ var VdFlowchart = class {
|
|
|
3647
3918
|
renderPreviewEdge(interaction) {
|
|
3648
3919
|
const fromPort = interaction.fromPort || interaction.source?.port || "right";
|
|
3649
3920
|
const toPort = interaction.toPort || interaction.target?.port || "left";
|
|
3650
|
-
const
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3921
|
+
const fromNode = interaction.source?.nodeId ? this.findNode(interaction.source.nodeId) : null;
|
|
3922
|
+
const toNode = interaction.target?.nodeId ? this.findNode(interaction.target.nodeId) : null;
|
|
3923
|
+
const visual = edgeVisualScale(fromNode, toNode);
|
|
3924
|
+
const edgePath = buildEdgePath(
|
|
3925
|
+
{
|
|
3926
|
+
strokeWidth: interaction.strokeWidth,
|
|
3927
|
+
route: interaction.route,
|
|
3928
|
+
fromPoint: interaction.fromPoint,
|
|
3929
|
+
toPoint: interaction.toPoint,
|
|
3930
|
+
from: { port: fromPort },
|
|
3931
|
+
to: { port: toPort }
|
|
3932
|
+
},
|
|
3933
|
+
fromNode,
|
|
3934
|
+
toNode
|
|
3935
|
+
);
|
|
3936
|
+
const baseStroke = this.getEdgeStrokeWidth(interaction);
|
|
3658
3937
|
return svgEl("path", {
|
|
3659
3938
|
class: "vd-flowchart-preview-path",
|
|
3660
3939
|
d: edgePath.d,
|
|
3661
|
-
"stroke-width":
|
|
3940
|
+
"stroke-width": visual < 0.999 ? formatNumber(Math.max(0.02, baseStroke * visual)) : baseStroke
|
|
3662
3941
|
});
|
|
3663
3942
|
}
|
|
3664
3943
|
renderNode(node) {
|
|
@@ -3673,17 +3952,21 @@ var VdFlowchart = class {
|
|
|
3673
3952
|
"data-node-id": node.id
|
|
3674
3953
|
});
|
|
3675
3954
|
const portsVisible = this.shouldShowNodePorts(node);
|
|
3955
|
+
const visual = nodeVisualScale(node);
|
|
3956
|
+
const corner = node.type === "rounded-rect" || node.type === "textbox" ? 16 : node.type === "junction" ? node.width / 2 : 6;
|
|
3676
3957
|
const hitbox = svgEl("rect", {
|
|
3677
3958
|
x: 0,
|
|
3678
3959
|
y: 0,
|
|
3679
3960
|
width: node.width,
|
|
3680
3961
|
height: node.height,
|
|
3681
|
-
rx:
|
|
3962
|
+
rx: visual < 0.999 && node.type !== "junction" ? formatNumber(corner * visual) : corner,
|
|
3682
3963
|
fill: "transparent",
|
|
3683
3964
|
"data-node-id": node.id
|
|
3684
3965
|
});
|
|
3685
3966
|
group.appendChild(hitbox);
|
|
3686
|
-
|
|
3967
|
+
const shape = this.renderNodeShape(node);
|
|
3968
|
+
this.applyScaledNodeShape(node, shape);
|
|
3969
|
+
group.appendChild(shape);
|
|
3687
3970
|
group.appendChild(this.renderNodeText(node));
|
|
3688
3971
|
const scale = this.documentData.viewport.scale || 1;
|
|
3689
3972
|
if (soloSelected && !this.readonly && SELECTION_BOX_TYPES.has(node.type)) {
|
|
@@ -3875,6 +4158,21 @@ var VdFlowchart = class {
|
|
|
3875
4158
|
});
|
|
3876
4159
|
return group;
|
|
3877
4160
|
}
|
|
4161
|
+
applyScaledNodeShape(node, shape) {
|
|
4162
|
+
const visual = nodeVisualScale(node);
|
|
4163
|
+
if (!shape || visual >= 0.999) return;
|
|
4164
|
+
const baseStroke = node.type === "junction" ? 1.25 : 1.5;
|
|
4165
|
+
shape.style.strokeWidth = String(formatNumber(Math.max(0.03, baseStroke * visual)));
|
|
4166
|
+
if (node.type === "textbox") {
|
|
4167
|
+
shape.style.strokeDasharray = `${formatNumber(8 * visual)} ${formatNumber(5 * visual)}`;
|
|
4168
|
+
}
|
|
4169
|
+
const baseRadius = node.type === "rounded-rect" ? 18 : node.type === "textbox" ? 14 : node.type === "rect" ? 2 : 0;
|
|
4170
|
+
if (baseRadius > 0) {
|
|
4171
|
+
const scaled = String(formatNumber(baseRadius * visual));
|
|
4172
|
+
shape.setAttribute("rx", scaled);
|
|
4173
|
+
shape.setAttribute("ry", scaled);
|
|
4174
|
+
}
|
|
4175
|
+
}
|
|
3878
4176
|
renderNodeShape(node) {
|
|
3879
4177
|
const baseClass = `vd-flowchart-node-shape vd-flowchart-node-shape--${node.type}`;
|
|
3880
4178
|
if (node.type === "rounded-rect") {
|
|
@@ -3951,21 +4249,28 @@ var VdFlowchart = class {
|
|
|
3951
4249
|
if (node.type === "junction" || !node.text) {
|
|
3952
4250
|
return textElement;
|
|
3953
4251
|
}
|
|
4252
|
+
const visual = nodeVisualScale(node);
|
|
4253
|
+
const metrics = getNodeFontMetrics(node);
|
|
4254
|
+
const lineHeight = visual < 0.999 ? Math.max(0.06, metrics.lineHeight * visual) : metrics.lineHeight;
|
|
4255
|
+
if (visual < 0.999) {
|
|
4256
|
+
textElement.style.fontSize = `${formatNumber(Math.max(0.05, metrics.fontSize * visual))}px`;
|
|
4257
|
+
}
|
|
4258
|
+
const equivalentWidth = visual < 0.999 ? node.width / visual : node.width;
|
|
3954
4259
|
if (node.type === "textbox") {
|
|
3955
|
-
const lines2 = wrapText(node.text, estimateChars(
|
|
3956
|
-
const
|
|
4260
|
+
const lines2 = wrapText(node.text, estimateChars(equivalentWidth - 32));
|
|
4261
|
+
const padX = visual < 0.999 ? formatNumber(16 * visual) : 16;
|
|
4262
|
+
const startY2 = visual < 0.999 ? formatNumber(28 * visual) : 28;
|
|
3957
4263
|
lines2.forEach((line, index) => {
|
|
3958
4264
|
const span = svgEl("tspan", {
|
|
3959
|
-
x:
|
|
3960
|
-
y:
|
|
4265
|
+
x: padX,
|
|
4266
|
+
y: visual < 0.999 ? formatNumber(startY2 + index * lineHeight) : startY2 + index * lineHeight
|
|
3961
4267
|
});
|
|
3962
4268
|
span.textContent = line;
|
|
3963
4269
|
textElement.appendChild(span);
|
|
3964
4270
|
});
|
|
3965
4271
|
return textElement;
|
|
3966
4272
|
}
|
|
3967
|
-
const lines = wrapText(node.text, estimateChars(
|
|
3968
|
-
const { lineHeight } = getNodeFontMetrics(node);
|
|
4273
|
+
const lines = wrapText(node.text, estimateChars(equivalentWidth));
|
|
3969
4274
|
const totalHeight = (lines.length - 1) * lineHeight;
|
|
3970
4275
|
const startY = node.height / 2 - totalHeight / 2;
|
|
3971
4276
|
textElement.setAttribute("text-anchor", "middle");
|
|
@@ -3973,7 +4278,7 @@ var VdFlowchart = class {
|
|
|
3973
4278
|
lines.forEach((line, index) => {
|
|
3974
4279
|
const span = svgEl("tspan", {
|
|
3975
4280
|
x: node.width / 2,
|
|
3976
|
-
y: startY + index * lineHeight,
|
|
4281
|
+
y: visual < 0.999 ? formatNumber(startY + index * lineHeight) : startY + index * lineHeight,
|
|
3977
4282
|
"text-anchor": "middle",
|
|
3978
4283
|
"dominant-baseline": "middle"
|
|
3979
4284
|
});
|
|
@@ -4025,12 +4330,12 @@ var VdFlowchart = class {
|
|
|
4025
4330
|
textArea.setAttribute("data-field", "node-text");
|
|
4026
4331
|
const widthInput = createElement("input", { value: node.width, type: "number" });
|
|
4027
4332
|
widthInput.setAttribute("data-field", "node-width");
|
|
4028
|
-
widthInput.setAttribute("min", String(
|
|
4029
|
-
widthInput.setAttribute("max", String(
|
|
4333
|
+
widthInput.setAttribute("min", String(this.minNodeSize));
|
|
4334
|
+
widthInput.setAttribute("max", String(this.maxNodeSize));
|
|
4030
4335
|
const heightInput = createElement("input", { value: node.height, type: "number" });
|
|
4031
4336
|
heightInput.setAttribute("data-field", "node-height");
|
|
4032
|
-
heightInput.setAttribute("min", String(
|
|
4033
|
-
heightInput.setAttribute("max", String(
|
|
4337
|
+
heightInput.setAttribute("min", String(this.minNodeSize));
|
|
4338
|
+
heightInput.setAttribute("max", String(this.maxNodeSize));
|
|
4034
4339
|
this.selectionFields.appendChild(createField("Type", typeSelect));
|
|
4035
4340
|
if (isNodeTextEditable(node)) {
|
|
4036
4341
|
this.selectionFields.appendChild(createField("Text", textArea));
|
|
@@ -4344,8 +4649,8 @@ var VdFlowchart = class {
|
|
|
4344
4649
|
this.reconnectEdgeId = null;
|
|
4345
4650
|
const currentViewport = deepClone(this.documentData.viewport);
|
|
4346
4651
|
const previousSelection = this.selection;
|
|
4347
|
-
this.documentData = normalizeDocument(snapshot);
|
|
4348
|
-
this.documentData.viewport = normalizeViewport(currentViewport);
|
|
4652
|
+
this.documentData = normalizeDocument(snapshot, this.normalizationLimits());
|
|
4653
|
+
this.documentData.viewport = normalizeViewport(currentViewport, this.scaleBounds());
|
|
4349
4654
|
this.selection = this.resolvePreservedSelection(previousSelection, true);
|
|
4350
4655
|
this.render();
|
|
4351
4656
|
this.emitChange(reason);
|
|
@@ -4422,7 +4727,8 @@ var VdFlowchart = class {
|
|
|
4422
4727
|
y: partialNode.y == null ? fallbackY : partialNode.y
|
|
4423
4728
|
},
|
|
4424
4729
|
this.documentData.nodes.length,
|
|
4425
|
-
usedIds
|
|
4730
|
+
usedIds,
|
|
4731
|
+
this.nodeSizeBounds()
|
|
4426
4732
|
);
|
|
4427
4733
|
this.documentData.nodes.push(node);
|
|
4428
4734
|
this.select({ kind: "node", id: node.id });
|
|
@@ -4635,7 +4941,7 @@ var VdFlowchart = class {
|
|
|
4635
4941
|
changed = true;
|
|
4636
4942
|
}
|
|
4637
4943
|
});
|
|
4638
|
-
const rerouted = options.reroutePorts === false ? false : this.rerouteEdgePorts();
|
|
4944
|
+
const rerouted = options.reroutePorts === false ? false : this.rerouteEdgePorts(resolvedMode === "tree" ? options.direction || "right" : null);
|
|
4639
4945
|
if (!changed && !rerouted) return this;
|
|
4640
4946
|
this.render();
|
|
4641
4947
|
this.emitChange("layout", { mode: resolvedMode });
|
|
@@ -4648,8 +4954,16 @@ var VdFlowchart = class {
|
|
|
4648
4954
|
// Re-pick each edge's from/to port from the nodes' current centers, so a
|
|
4649
4955
|
// freshly laid-out graph attaches connectors on sensible sides. Returns
|
|
4650
4956
|
// whether any port actually changed.
|
|
4651
|
-
rerouteEdgePorts() {
|
|
4957
|
+
rerouteEdgePorts(flowDirection = null) {
|
|
4652
4958
|
let changed = false;
|
|
4959
|
+
const flow = flowDirection === "down" || flowDirection === "up" || flowDirection === "left" || flowDirection === "right" ? flowDirection : null;
|
|
4960
|
+
const outDegree = /* @__PURE__ */ new Map();
|
|
4961
|
+
if (flow) {
|
|
4962
|
+
this.documentData.edges.forEach((edge) => {
|
|
4963
|
+
const fromId = edge?.from?.nodeId;
|
|
4964
|
+
if (fromId) outDegree.set(fromId, (outDegree.get(fromId) || 0) + 1);
|
|
4965
|
+
});
|
|
4966
|
+
}
|
|
4653
4967
|
this.documentData.edges.forEach((edge) => {
|
|
4654
4968
|
const fromNode = this.findNode(edge.from.nodeId);
|
|
4655
4969
|
const toNode = this.findNode(edge.to.nodeId);
|
|
@@ -4659,8 +4973,82 @@ var VdFlowchart = class {
|
|
|
4659
4973
|
y: fromNode.y + fromNode.height / 2
|
|
4660
4974
|
};
|
|
4661
4975
|
const toCenter = { x: toNode.x + toNode.width / 2, y: toNode.y + toNode.height / 2 };
|
|
4662
|
-
|
|
4663
|
-
|
|
4976
|
+
let nextFrom;
|
|
4977
|
+
let nextTo;
|
|
4978
|
+
if (flow) {
|
|
4979
|
+
const dx = toCenter.x - fromCenter.x;
|
|
4980
|
+
const dy = toCenter.y - fromCenter.y;
|
|
4981
|
+
const vertical = flow === "down" || flow === "up";
|
|
4982
|
+
const sign = flow === "up" || flow === "left" ? -1 : 1;
|
|
4983
|
+
const mainDelta = (vertical ? dy : dx) * sign;
|
|
4984
|
+
const crossDelta = vertical ? dx : dy;
|
|
4985
|
+
const isDiamondBranch = fromNode.type === "diamond" && (outDegree.get(fromNode.id) || 0) >= 2;
|
|
4986
|
+
if (mainDelta < -8) {
|
|
4987
|
+
const allNodes = this.documentData.nodes;
|
|
4988
|
+
if (vertical) {
|
|
4989
|
+
const minX = Math.min(fromNode.x, toNode.x);
|
|
4990
|
+
const maxRight = Math.max(fromNode.x + fromNode.width, toNode.x + toNode.width);
|
|
4991
|
+
const minY = Math.min(fromCenter.y, toCenter.y) - 8;
|
|
4992
|
+
const maxY = Math.max(fromCenter.y, toCenter.y) + 8;
|
|
4993
|
+
let rightCount = 0;
|
|
4994
|
+
let leftCount = 0;
|
|
4995
|
+
allNodes.forEach((n) => {
|
|
4996
|
+
if (!n || n.id === fromNode.id || n.id === toNode.id) return;
|
|
4997
|
+
const ny = n.y + n.height / 2;
|
|
4998
|
+
if (ny < minY || ny > maxY) return;
|
|
4999
|
+
const nx = n.x + n.width / 2;
|
|
5000
|
+
if (nx >= maxRight - 20) rightCount += 1;
|
|
5001
|
+
if (nx <= minX + 20) leftCount += 1;
|
|
5002
|
+
});
|
|
5003
|
+
const side = leftCount < rightCount ? "left" : rightCount < leftCount ? "right" : crossDelta < -8 ? "left" : "right";
|
|
5004
|
+
nextFrom = side;
|
|
5005
|
+
nextTo = side;
|
|
5006
|
+
} else {
|
|
5007
|
+
const minY = Math.min(fromNode.y, toNode.y);
|
|
5008
|
+
const maxBottom = Math.max(fromNode.y + fromNode.height, toNode.y + toNode.height);
|
|
5009
|
+
const minX = Math.min(fromCenter.x, toCenter.x) - 8;
|
|
5010
|
+
const maxX = Math.max(fromCenter.x, toCenter.x) + 8;
|
|
5011
|
+
let bottomCount = 0;
|
|
5012
|
+
let topCount = 0;
|
|
5013
|
+
allNodes.forEach((n) => {
|
|
5014
|
+
if (!n || n.id === fromNode.id || n.id === toNode.id) return;
|
|
5015
|
+
const nx = n.x + n.width / 2;
|
|
5016
|
+
if (nx < minX || nx > maxX) return;
|
|
5017
|
+
const ny = n.y + n.height / 2;
|
|
5018
|
+
if (ny >= maxBottom - 20) bottomCount += 1;
|
|
5019
|
+
if (ny <= minY + 20) topCount += 1;
|
|
5020
|
+
});
|
|
5021
|
+
const side = topCount < bottomCount ? "top" : bottomCount < topCount ? "bottom" : crossDelta < -8 ? "top" : "bottom";
|
|
5022
|
+
nextFrom = side;
|
|
5023
|
+
nextTo = side;
|
|
5024
|
+
}
|
|
5025
|
+
} else if (mainDelta >= 12) {
|
|
5026
|
+
if (vertical) {
|
|
5027
|
+
nextTo = sign > 0 ? "top" : "bottom";
|
|
5028
|
+
if (isDiamondBranch && Math.abs(crossDelta) >= fromNode.width * 0.45) {
|
|
5029
|
+
nextFrom = crossDelta > 0 ? "right" : "left";
|
|
5030
|
+
} else {
|
|
5031
|
+
nextFrom = sign > 0 ? "bottom" : "top";
|
|
5032
|
+
}
|
|
5033
|
+
} else {
|
|
5034
|
+
nextTo = sign > 0 ? "left" : "right";
|
|
5035
|
+
if (isDiamondBranch && Math.abs(crossDelta) >= fromNode.height * 0.45) {
|
|
5036
|
+
nextFrom = crossDelta > 0 ? "bottom" : "top";
|
|
5037
|
+
} else {
|
|
5038
|
+
nextFrom = sign > 0 ? "right" : "left";
|
|
5039
|
+
}
|
|
5040
|
+
}
|
|
5041
|
+
} else if (vertical) {
|
|
5042
|
+
nextFrom = dx >= 0 ? "right" : "left";
|
|
5043
|
+
nextTo = dx >= 0 ? "left" : "right";
|
|
5044
|
+
} else {
|
|
5045
|
+
nextFrom = dy >= 0 ? "bottom" : "top";
|
|
5046
|
+
nextTo = dy >= 0 ? "top" : "bottom";
|
|
5047
|
+
}
|
|
5048
|
+
} else {
|
|
5049
|
+
nextFrom = getPortByDirection(fromNode, toCenter).port;
|
|
5050
|
+
nextTo = getPortByDirection(toNode, fromCenter).port;
|
|
5051
|
+
}
|
|
4664
5052
|
if (nextFrom !== edge.from.port || nextTo !== edge.to.port) {
|
|
4665
5053
|
edge.from.port = nextFrom;
|
|
4666
5054
|
edge.to.port = nextTo;
|
|
@@ -4675,8 +5063,8 @@ var VdFlowchart = class {
|
|
|
4675
5063
|
node.x = patch.x == null ? node.x : formatNumber(toFiniteNumber(patch.x, node.x));
|
|
4676
5064
|
node.y = patch.y == null ? node.y : formatNumber(toFiniteNumber(patch.y, node.y));
|
|
4677
5065
|
node.text = isNodeTextEditable(nextType) ? patch.text == null ? node.text : String(patch.text) : getNodeSpec(nextType).text;
|
|
4678
|
-
node.width = clampNodeWidth(nextType, patch.width, node.width);
|
|
4679
|
-
node.height = clampNodeHeight(nextType, patch.height, node.height);
|
|
5066
|
+
node.width = clampNodeWidth(nextType, patch.width, node.width, this.nodeSizeBounds());
|
|
5067
|
+
node.height = clampNodeHeight(nextType, patch.height, node.height, this.nodeSizeBounds());
|
|
4680
5068
|
if (isPlainObject(patch.data)) {
|
|
4681
5069
|
node.data = deepClone(patch.data);
|
|
4682
5070
|
}
|
|
@@ -4865,8 +5253,45 @@ var VdFlowchart = class {
|
|
|
4865
5253
|
if (this.selection.kind === "node") return this.removeNode(this.selection.id);
|
|
4866
5254
|
return this.removeEdge(this.selection.id);
|
|
4867
5255
|
}
|
|
5256
|
+
nodeSizeBounds() {
|
|
5257
|
+
return { min: this.minNodeSize, max: this.maxNodeSize };
|
|
5258
|
+
}
|
|
5259
|
+
scaleBounds() {
|
|
5260
|
+
return { min: this.minScale, max: this.maxScale };
|
|
5261
|
+
}
|
|
5262
|
+
normalizationLimits() {
|
|
5263
|
+
return { scaleBounds: this.scaleBounds(), sizeBounds: this.nodeSizeBounds() };
|
|
5264
|
+
}
|
|
5265
|
+
hasWideCamera() {
|
|
5266
|
+
return this.minScale < DEFAULT_MIN_SCALE || this.maxScale > DEFAULT_MAX_SCALE;
|
|
5267
|
+
}
|
|
5268
|
+
syncGrid() {
|
|
5269
|
+
const wide = this.hasWideCamera();
|
|
5270
|
+
const extent = wide ? WIDE_WORLD_EXTENT : DEFAULT_WORLD_EXTENT;
|
|
5271
|
+
this.gridRect?.setAttribute("x", String(-extent / 2));
|
|
5272
|
+
this.gridRect?.setAttribute("y", String(-extent / 2));
|
|
5273
|
+
this.gridRect?.setAttribute("width", String(extent));
|
|
5274
|
+
this.gridRect?.setAttribute("height", String(extent));
|
|
5275
|
+
const pattern = this.svg?.querySelector("pattern");
|
|
5276
|
+
const path = pattern?.querySelector("path");
|
|
5277
|
+
if (!pattern || !path) return;
|
|
5278
|
+
let tile = this.gridSize;
|
|
5279
|
+
if (wide) {
|
|
5280
|
+
const scale = Math.max(this.minScale, this.documentData?.viewport?.scale || 1);
|
|
5281
|
+
while (tile * scale < 12 && tile < extent / 8) tile *= 4;
|
|
5282
|
+
while (tile * scale > 48 && tile > 1.5) tile /= 4;
|
|
5283
|
+
tile = formatNumber(tile);
|
|
5284
|
+
path.setAttribute("vector-effect", "non-scaling-stroke");
|
|
5285
|
+
} else {
|
|
5286
|
+
path.removeAttribute("vector-effect");
|
|
5287
|
+
}
|
|
5288
|
+
path.setAttribute("stroke-width", "1");
|
|
5289
|
+
pattern.setAttribute("width", String(tile));
|
|
5290
|
+
pattern.setAttribute("height", String(tile));
|
|
5291
|
+
path.setAttribute("d", `M ${tile} 0 L 0 0 0 ${tile}`);
|
|
5292
|
+
}
|
|
4868
5293
|
setViewport(viewport) {
|
|
4869
|
-
this.documentData.viewport = normalizeViewport(viewport);
|
|
5294
|
+
this.documentData.viewport = normalizeViewport(viewport, this.scaleBounds());
|
|
4870
5295
|
this.render({ inspector: false, json: true });
|
|
4871
5296
|
this.emitViewportChange("viewport:set");
|
|
4872
5297
|
return this;
|
|
@@ -4884,7 +5309,7 @@ var VdFlowchart = class {
|
|
|
4884
5309
|
return this;
|
|
4885
5310
|
}
|
|
4886
5311
|
resetView() {
|
|
4887
|
-
this.documentData.viewport = normalizeViewport({ x: 0, y: 0, scale: 1 });
|
|
5312
|
+
this.documentData.viewport = normalizeViewport({ x: 0, y: 0, scale: 1 }, this.scaleBounds());
|
|
4888
5313
|
this.render({ inspector: false, json: true });
|
|
4889
5314
|
this.emitViewportChange("viewport:reset");
|
|
4890
5315
|
return this;
|
|
@@ -4900,30 +5325,64 @@ var VdFlowchart = class {
|
|
|
4900
5325
|
const padding = 80;
|
|
4901
5326
|
const contentWidth = Math.max(1, bounds.right - bounds.left);
|
|
4902
5327
|
const contentHeight = Math.max(1, bounds.bottom - bounds.top);
|
|
4903
|
-
const scale =
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
5328
|
+
const scale = formatScale(
|
|
5329
|
+
clamp(
|
|
5330
|
+
Math.min((width - padding * 2) / contentWidth, (height - padding * 2) / contentHeight),
|
|
5331
|
+
this.minScale,
|
|
5332
|
+
this.maxScale
|
|
5333
|
+
)
|
|
4907
5334
|
);
|
|
4908
5335
|
this.documentData.viewport = {
|
|
4909
5336
|
x: formatNumber(width / 2 - (bounds.left + bounds.right) / 2 * scale),
|
|
4910
5337
|
y: formatNumber(height / 2 - (bounds.top + bounds.bottom) / 2 * scale),
|
|
4911
|
-
scale
|
|
5338
|
+
scale
|
|
4912
5339
|
};
|
|
4913
5340
|
this.render({ inspector: false, json: true });
|
|
4914
5341
|
this.emitViewportChange("viewport:fit");
|
|
4915
5342
|
return this;
|
|
4916
5343
|
}
|
|
5344
|
+
fitNode(nodeId, padding = 48) {
|
|
5345
|
+
const node = this.findNode(nodeId);
|
|
5346
|
+
if (!node || this.isNodeHidden(node.id)) return this;
|
|
5347
|
+
const pad = Math.max(0, toFiniteNumber(padding, 48));
|
|
5348
|
+
const width = this.canvasEl.clientWidth || 800;
|
|
5349
|
+
const height = this.canvasEl.clientHeight || 560;
|
|
5350
|
+
const availableWidth = Math.max(1, width - pad * 2);
|
|
5351
|
+
const availableHeight = Math.max(1, height - pad * 2);
|
|
5352
|
+
const scale = formatScale(
|
|
5353
|
+
clamp(
|
|
5354
|
+
Math.min(
|
|
5355
|
+
availableWidth / Math.max(1, node.width),
|
|
5356
|
+
availableHeight / Math.max(1, node.height)
|
|
5357
|
+
),
|
|
5358
|
+
this.minScale,
|
|
5359
|
+
this.maxScale
|
|
5360
|
+
)
|
|
5361
|
+
);
|
|
5362
|
+
const centerX = node.x + node.width / 2;
|
|
5363
|
+
const centerY = node.y + node.height / 2;
|
|
5364
|
+
this.documentData.viewport = {
|
|
5365
|
+
x: formatNumber(width / 2 - centerX * scale),
|
|
5366
|
+
y: formatNumber(height / 2 - centerY * scale),
|
|
5367
|
+
scale
|
|
5368
|
+
};
|
|
5369
|
+
this.render({ inspector: false, json: true });
|
|
5370
|
+
this.emitViewportChange("viewport:fit-node");
|
|
5371
|
+
return this;
|
|
5372
|
+
}
|
|
4917
5373
|
clear() {
|
|
4918
5374
|
this.stopTextEdit({ commit: false });
|
|
4919
5375
|
this.activeTool = null;
|
|
4920
5376
|
this.reconnectEdgeId = null;
|
|
4921
5377
|
this.clipboard = null;
|
|
4922
|
-
this.documentData = normalizeDocument(
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
5378
|
+
this.documentData = normalizeDocument(
|
|
5379
|
+
{
|
|
5380
|
+
nodes: [],
|
|
5381
|
+
edges: [],
|
|
5382
|
+
viewport: { x: 0, y: 0, scale: 1 }
|
|
5383
|
+
},
|
|
5384
|
+
this.normalizationLimits()
|
|
5385
|
+
);
|
|
4927
5386
|
this.selection = null;
|
|
4928
5387
|
this.render();
|
|
4929
5388
|
this.emitChange("clear");
|
|
@@ -4958,6 +5417,33 @@ var VdFlowchart = class {
|
|
|
4958
5417
|
if ("snapGuides" in options) this.snapGuides = options.snapGuides !== false;
|
|
4959
5418
|
if ("minimap" in options) this.setMinimapEnabled(options.minimap !== false);
|
|
4960
5419
|
if ("autoLayout" in options) this.autoLayout = Boolean(options.autoLayout);
|
|
5420
|
+
if ("minScale" in options || "maxScale" in options || "minNodeSize" in options || "maxNodeSize" in options) {
|
|
5421
|
+
const nextScale = normalizeBoundPair(
|
|
5422
|
+
"minScale" in options ? options.minScale : this.minScale,
|
|
5423
|
+
"maxScale" in options ? options.maxScale : this.maxScale,
|
|
5424
|
+
DEFAULT_MIN_SCALE,
|
|
5425
|
+
DEFAULT_MAX_SCALE
|
|
5426
|
+
);
|
|
5427
|
+
const nextSize = normalizeBoundPair(
|
|
5428
|
+
"minNodeSize" in options ? options.minNodeSize : this.minNodeSize,
|
|
5429
|
+
"maxNodeSize" in options ? options.maxNodeSize : this.maxNodeSize,
|
|
5430
|
+
DEFAULT_MIN_NODE_SIZE,
|
|
5431
|
+
DEFAULT_MAX_NODE_SIZE
|
|
5432
|
+
);
|
|
5433
|
+
this.minScale = nextScale.min;
|
|
5434
|
+
this.maxScale = nextScale.max;
|
|
5435
|
+
this.minNodeSize = nextSize.min;
|
|
5436
|
+
this.maxNodeSize = nextSize.max;
|
|
5437
|
+
const clamped = clamp(
|
|
5438
|
+
formatScale(this.documentData.viewport.scale),
|
|
5439
|
+
this.minScale,
|
|
5440
|
+
this.maxScale
|
|
5441
|
+
);
|
|
5442
|
+
if (clamped !== this.documentData.viewport.scale) {
|
|
5443
|
+
this.documentData.viewport.scale = clamped;
|
|
5444
|
+
this.pendingBoundsEvent = true;
|
|
5445
|
+
}
|
|
5446
|
+
}
|
|
4961
5447
|
this.syncCanvasLabel();
|
|
4962
5448
|
if (this.shortcutsHelpOpen) this.renderShortcutsHelp();
|
|
4963
5449
|
if ("history" in options && options.history !== false !== this.historyEnabled) {
|
|
@@ -4975,11 +5461,15 @@ var VdFlowchart = class {
|
|
|
4975
5461
|
}
|
|
4976
5462
|
}
|
|
4977
5463
|
this.render();
|
|
5464
|
+
if (this.pendingBoundsEvent) {
|
|
5465
|
+
this.pendingBoundsEvent = false;
|
|
5466
|
+
this.emitViewportChange("viewport:bounds");
|
|
5467
|
+
}
|
|
4978
5468
|
this.updateHistoryButtons();
|
|
4979
5469
|
return this;
|
|
4980
5470
|
}
|
|
4981
5471
|
load(data, options = {}) {
|
|
4982
|
-
const documentData = normalizeDocument(data);
|
|
5472
|
+
const documentData = normalizeDocument(data, this.normalizationLimits());
|
|
4983
5473
|
if (options.silent && JSON.stringify(documentData) === JSON.stringify(this.documentData)) {
|
|
4984
5474
|
return this;
|
|
4985
5475
|
}
|
|
@@ -5037,7 +5527,11 @@ var EDITOR_OPTION_KEYS = [
|
|
|
5037
5527
|
"keyboardShortcuts",
|
|
5038
5528
|
"snapGuides",
|
|
5039
5529
|
"minimap",
|
|
5040
|
-
"autoLayout"
|
|
5530
|
+
"autoLayout",
|
|
5531
|
+
"minScale",
|
|
5532
|
+
"maxScale",
|
|
5533
|
+
"minNodeSize",
|
|
5534
|
+
"maxNodeSize"
|
|
5041
5535
|
];
|
|
5042
5536
|
var VdFlowchart2 = defineComponent({
|
|
5043
5537
|
name: "VdFlowchart",
|
|
@@ -5063,13 +5557,29 @@ var VdFlowchart2 = defineComponent({
|
|
|
5063
5557
|
/** Show the overview minimap in the canvas corner (default true). */
|
|
5064
5558
|
minimap: { type: Boolean, default: true },
|
|
5065
5559
|
/** Re-run the current layout after keyboard or handle insertions (default false). */
|
|
5066
|
-
autoLayout: { type: Boolean, default: false }
|
|
5560
|
+
autoLayout: { type: Boolean, default: false },
|
|
5561
|
+
/** Lowest camera scale. Defaults to 0.35. Set it together with maxScale. */
|
|
5562
|
+
minScale: { type: Number, default: void 0 },
|
|
5563
|
+
/** Highest camera scale. Defaults to 3. Set it together with minScale. */
|
|
5564
|
+
maxScale: { type: Number, default: void 0 },
|
|
5565
|
+
/** Smallest resizable-node side. Defaults to 56. Set it together with maxNodeSize. */
|
|
5566
|
+
minNodeSize: { type: Number, default: void 0 },
|
|
5567
|
+
/** Largest resizable-node side. Defaults to 420. Set it together with minNodeSize. */
|
|
5568
|
+
maxNodeSize: { type: Number, default: void 0 }
|
|
5067
5569
|
},
|
|
5068
5570
|
emits: ["change", "select", "viewport", "connect", "ready"],
|
|
5069
5571
|
setup(props, { emit, expose }) {
|
|
5070
5572
|
const el = ref(null);
|
|
5071
5573
|
let instance = null;
|
|
5072
|
-
const editorOptions = () => Object.fromEntries(
|
|
5574
|
+
const editorOptions = () => Object.fromEntries(
|
|
5575
|
+
EDITOR_OPTION_KEYS.flatMap((key) => {
|
|
5576
|
+
const value = props[key];
|
|
5577
|
+
if ((key === "minScale" || key === "maxScale" || key === "minNodeSize" || key === "maxNodeSize") && value == null) {
|
|
5578
|
+
return [];
|
|
5579
|
+
}
|
|
5580
|
+
return [[key, value]];
|
|
5581
|
+
})
|
|
5582
|
+
);
|
|
5073
5583
|
const create = () => {
|
|
5074
5584
|
instance = new VdFlowchart({
|
|
5075
5585
|
element: el.value,
|
|
@@ -5111,7 +5621,8 @@ var VdFlowchart2 = defineComponent({
|
|
|
5111
5621
|
redo: () => instance?.redo(),
|
|
5112
5622
|
canUndo: () => Boolean(instance?.canUndo()),
|
|
5113
5623
|
canRedo: () => Boolean(instance?.canRedo()),
|
|
5114
|
-
layout: (mode, options) => instance?.layout(mode, options)
|
|
5624
|
+
layout: (mode, options) => instance?.layout(mode, options),
|
|
5625
|
+
fitNode: (nodeId, padding) => instance?.fitNode(nodeId, padding)
|
|
5115
5626
|
});
|
|
5116
5627
|
return () => h("div", { ref: el, class: "vd-flowchart" });
|
|
5117
5628
|
}
|