@vanduo-oss/vd3-flowchart 1.5.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 +18 -0
- package/README.md +7 -2
- package/dist/index.cjs +283 -35
- package/dist/index.cjs.map +3 -3
- package/dist/index.js +283 -35
- package/dist/index.js.map +3 -3
- package/dist/meta.json +10 -10
- package/dist/vd3-flowchart.css +50 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@vanduo-oss/vd3-flowchart` are documented here.
|
|
4
4
|
|
|
5
|
+
## 1.5.1 — 2026-11-01
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Inline text editors for shapes use transparent chrome so circles and diamonds
|
|
10
|
+
keep their SVG silhouette while typing. Textbox, edge, and label editors keep
|
|
11
|
+
an opaque panel.
|
|
12
|
+
- Dark theme idle node strokes lean on the accent color so outlines stay visible
|
|
13
|
+
without hover or selection. Hosts may still override `--vd-flowchart-node-stroke`.
|
|
14
|
+
- The diagram canvas no longer draws a full-viewport focus ring. Mindmap Tab that
|
|
15
|
+
opens a new branch editor skips the intermediate canvas focus flash.
|
|
16
|
+
- Tree layout measures node size. Rank and sibling gaps stay clear of the boxes,
|
|
17
|
+
and a decision with one deep branch keeps that branch on the main spine.
|
|
18
|
+
- Facing orthogonal connectors shorten their stubs when two shapes sit closer
|
|
19
|
+
than two full stubs, so the route does not loop around the pair.
|
|
20
|
+
- Tree layout re-picks ports from the flow: diamond side branches leave the side
|
|
21
|
+
ports, and a reverse loop uses the quieter side of the spine.
|
|
22
|
+
|
|
5
23
|
## 1.5.0 — 2026-10-25
|
|
6
24
|
|
|
7
25
|
### Added
|
package/README.md
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# @vanduo-oss/vd3-flowchart
|
|
2
2
|
|
|
3
|
+
<a href="https://snyk.io/?utm_source=open-source&utm_medium=pg-ptr&utm_campaign=ref-2501-osp&utm_content=pg-cta"><img src="https://raw.githubusercontent.com/vanduo-oss/vd3-flowchart/main/assets/scanned-by-snyk.png" alt="Scanned by Snyk" width="206" height="141"></a>
|
|
4
|
+
|
|
3
5
|
[](./LICENSE)
|
|
4
6
|
|
|
5
7
|
Vanduo **flowchart** for Vue 3: a node/edge editor with undo/redo, layout
|
|
6
8
|
modes, and a framework-agnostic core. Extracted 1-to-1 from
|
|
7
9
|
`@vanduo-oss/vd3-cbun` flowchart **1.2.0**.
|
|
8
10
|
|
|
9
|
-
**Status: 1.5.
|
|
11
|
+
**Status: 1.5.1.** `VD_FLOWCHART_VERSION` tracks the package release.
|
|
10
12
|
`FLOWCHART_DOCUMENT_VERSION` (currently `1.3.0`) is the value `toJSON()`
|
|
11
13
|
writes. Do not reset either independently without a compatibility plan.
|
|
12
14
|
|
|
@@ -50,7 +52,7 @@ if anything but `vue` is externalized. The package declares
|
|
|
50
52
|
|
|
51
53
|
## Version policy
|
|
52
54
|
|
|
53
|
-
`package.json` version **is** `VD_FLOWCHART_VERSION` (`1.5.
|
|
55
|
+
`package.json` version **is** `VD_FLOWCHART_VERSION` (`1.5.1`). Bump those
|
|
54
56
|
together for a package release. Keep `FLOWCHART_DOCUMENT_VERSION` unchanged
|
|
55
57
|
unless the serialized schema changes; add fixtures and explicit compatibility
|
|
56
58
|
rules for a format change. Unversioned 1.x documents through 1.3.0 still load;
|
|
@@ -144,6 +146,9 @@ Tokens consumed include `--vd-bg-primary`, `--vd-bg-secondary`,
|
|
|
144
146
|
|
|
145
147
|
## Security
|
|
146
148
|
|
|
149
|
+
- **Open-source scanning** — this repo is monitored with
|
|
150
|
+
[Snyk](https://snyk.io/?utm_source=open-source&utm_medium=pg-ptr&utm_campaign=ref-2501-osp&utm_content=pg-cta)
|
|
151
|
+
(thanks for their support of open source).
|
|
147
152
|
- **No bundled dependencies** — beyond the `vue` peer, nothing is bundled; the
|
|
148
153
|
build fails if any other module is externalized or any `node_modules` input
|
|
149
154
|
is bundled.
|
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;
|
|
121
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);
|
|
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 }
|
|
@@ -435,7 +559,7 @@ var EDGE_STROKE_PRESETS = [
|
|
|
435
559
|
{ id: "medium", label: "Medium", width: DEFAULT_EDGE_STROKE_WIDTH },
|
|
436
560
|
{ id: "bold", label: "Bold", width: 3.5 }
|
|
437
561
|
];
|
|
438
|
-
var VD_FLOWCHART_VERSION = "1.5.
|
|
562
|
+
var VD_FLOWCHART_VERSION = "1.5.1";
|
|
439
563
|
var FLOWCHART_DOCUMENT_VERSION = "1.3.0";
|
|
440
564
|
var FLOWCHART_NODE_TYPES = [
|
|
441
565
|
"rounded-rect",
|
|
@@ -1142,10 +1266,37 @@ function getOrthogonalMidpoints(fromStub, toStub, fromPort, toPort) {
|
|
|
1142
1266
|
function getOrthogonalPoints(fromPoint, toPoint, fromPort, toPort, fromRect, toRect, metrics = routeMetrics(1)) {
|
|
1143
1267
|
const fromNormal = getPortNormal(fromPort);
|
|
1144
1268
|
const toNormal = getPortNormal(toPort);
|
|
1145
|
-
const fromStub = offsetPoint(fromPoint, fromNormal, metrics.stub);
|
|
1146
|
-
const toStub = offsetPoint(toPoint, toNormal, metrics.stub);
|
|
1147
1269
|
const sourceHorizontal = isHorizontalPort(fromPort);
|
|
1148
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);
|
|
1149
1300
|
const mids = [];
|
|
1150
1301
|
if (sourceHorizontal && targetHorizontal) {
|
|
1151
1302
|
const facing = fromNormal.x === -toNormal.x;
|
|
@@ -1568,6 +1719,7 @@ var VdFlowchart = class {
|
|
|
1568
1719
|
this.handleClick = this.handleClick.bind(this);
|
|
1569
1720
|
this.handleDoubleClick = this.handleDoubleClick.bind(this);
|
|
1570
1721
|
this.handleWheel = this.handleWheel.bind(this);
|
|
1722
|
+
this.handleCanvasBlur = this.handleCanvasBlur.bind(this);
|
|
1571
1723
|
this.handleKeyDown = this.handleKeyDown.bind(this);
|
|
1572
1724
|
this.handleSelectionFieldInput = this.handleSelectionFieldInput.bind(this);
|
|
1573
1725
|
this.handleSelectionFieldChange = this.handleSelectionFieldChange.bind(this);
|
|
@@ -1980,6 +2132,7 @@ var VdFlowchart = class {
|
|
|
1980
2132
|
this.canvasEl.addEventListener("click", this.handleClick);
|
|
1981
2133
|
this.canvasEl.addEventListener("dblclick", this.handleDoubleClick);
|
|
1982
2134
|
this.canvasEl.addEventListener("wheel", this.handleWheel, { passive: false });
|
|
2135
|
+
this.canvasEl.addEventListener("blur", this.handleCanvasBlur);
|
|
1983
2136
|
this.root.addEventListener("keydown", this.handleKeyDown, true);
|
|
1984
2137
|
window.addEventListener("pointerup", this.handlePointerUp);
|
|
1985
2138
|
window.addEventListener("resize", this.handleResize);
|
|
@@ -1998,6 +2151,7 @@ var VdFlowchart = class {
|
|
|
1998
2151
|
this.canvasEl.removeEventListener("click", this.handleClick);
|
|
1999
2152
|
this.canvasEl.removeEventListener("dblclick", this.handleDoubleClick);
|
|
2000
2153
|
this.canvasEl.removeEventListener("wheel", this.handleWheel);
|
|
2154
|
+
this.canvasEl.removeEventListener("blur", this.handleCanvasBlur);
|
|
2001
2155
|
this.root.removeEventListener("keydown", this.handleKeyDown, true);
|
|
2002
2156
|
window.removeEventListener("pointerup", this.handlePointerUp);
|
|
2003
2157
|
window.removeEventListener("resize", this.handleResize);
|
|
@@ -2006,6 +2160,19 @@ var VdFlowchart = class {
|
|
|
2006
2160
|
if (this.destroyed) return;
|
|
2007
2161
|
this.render({ inspector: false, json: false });
|
|
2008
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
|
+
}
|
|
2009
2176
|
updatePaletteState() {
|
|
2010
2177
|
const arrowArmed = this.activeTool === "arrow";
|
|
2011
2178
|
this.paletteGrid.querySelectorAll(".vd-flowchart-palette-btn").forEach((button) => {
|
|
@@ -3461,7 +3628,7 @@ var VdFlowchart = class {
|
|
|
3461
3628
|
if (event.key === "Escape" || event.key === "Enter") {
|
|
3462
3629
|
event.preventDefault();
|
|
3463
3630
|
this.stopTextEdit({ commit: event.key === "Enter" });
|
|
3464
|
-
this.
|
|
3631
|
+
this.focusCanvas({ quiet: true });
|
|
3465
3632
|
}
|
|
3466
3633
|
});
|
|
3467
3634
|
textarea.addEventListener("blur", () => {
|
|
@@ -3488,7 +3655,7 @@ var VdFlowchart = class {
|
|
|
3488
3655
|
if (event.key === "Escape") {
|
|
3489
3656
|
event.preventDefault();
|
|
3490
3657
|
this.stopTextEdit({ commit: false });
|
|
3491
|
-
this.
|
|
3658
|
+
this.focusCanvas({ quiet: true });
|
|
3492
3659
|
return;
|
|
3493
3660
|
}
|
|
3494
3661
|
if (event.key === "Enter") {
|
|
@@ -3497,13 +3664,12 @@ var VdFlowchart = class {
|
|
|
3497
3664
|
if (!commits) return;
|
|
3498
3665
|
event.preventDefault();
|
|
3499
3666
|
this.stopTextEdit({ commit: true });
|
|
3500
|
-
this.
|
|
3667
|
+
this.focusCanvas({ quiet: true });
|
|
3501
3668
|
return;
|
|
3502
3669
|
}
|
|
3503
3670
|
if (event.key === "Tab" && mindmap && !event.shiftKey && !event.altKey && !event.ctrlKey) {
|
|
3504
3671
|
event.preventDefault();
|
|
3505
3672
|
this.stopTextEdit({ commit: true });
|
|
3506
|
-
this.canvasEl.focus();
|
|
3507
3673
|
this.insertBranchNode(node.id, { edit: true });
|
|
3508
3674
|
}
|
|
3509
3675
|
}
|
|
@@ -4811,7 +4977,7 @@ var VdFlowchart = class {
|
|
|
4811
4977
|
changed = true;
|
|
4812
4978
|
}
|
|
4813
4979
|
});
|
|
4814
|
-
const rerouted = options.reroutePorts === false ? false : this.rerouteEdgePorts();
|
|
4980
|
+
const rerouted = options.reroutePorts === false ? false : this.rerouteEdgePorts(resolvedMode === "tree" ? options.direction || "right" : null);
|
|
4815
4981
|
if (!changed && !rerouted) return this;
|
|
4816
4982
|
this.render();
|
|
4817
4983
|
this.emitChange("layout", { mode: resolvedMode });
|
|
@@ -4824,8 +4990,16 @@ var VdFlowchart = class {
|
|
|
4824
4990
|
// Re-pick each edge's from/to port from the nodes' current centers, so a
|
|
4825
4991
|
// freshly laid-out graph attaches connectors on sensible sides. Returns
|
|
4826
4992
|
// whether any port actually changed.
|
|
4827
|
-
rerouteEdgePorts() {
|
|
4993
|
+
rerouteEdgePorts(flowDirection = null) {
|
|
4828
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
|
+
}
|
|
4829
5003
|
this.documentData.edges.forEach((edge) => {
|
|
4830
5004
|
const fromNode = this.findNode(edge.from.nodeId);
|
|
4831
5005
|
const toNode = this.findNode(edge.to.nodeId);
|
|
@@ -4835,8 +5009,82 @@ var VdFlowchart = class {
|
|
|
4835
5009
|
y: fromNode.y + fromNode.height / 2
|
|
4836
5010
|
};
|
|
4837
5011
|
const toCenter = { x: toNode.x + toNode.width / 2, y: toNode.y + toNode.height / 2 };
|
|
4838
|
-
|
|
4839
|
-
|
|
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
|
+
}
|
|
4840
5088
|
if (nextFrom !== edge.from.port || nextTo !== edge.to.port) {
|
|
4841
5089
|
edge.from.port = nextFrom;
|
|
4842
5090
|
edge.to.port = nextTo;
|