@vanduo-oss/vd3-flowchart 1.4.0 → 1.5.0
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 +27 -0
- package/README.md +30 -5
- package/SKILL.md +9 -0
- package/dist/core.d.ts +14 -0
- package/dist/index.cjs +387 -124
- package/dist/index.cjs.map +3 -3
- package/dist/index.js +387 -124
- package/dist/index.js.map +3 -3
- package/dist/meta.json +10 -10
- package/dist/vue.d.ts +9 -0
- package/package.json +5 -2
- package/recipes/editor.vue +4 -2
package/dist/index.js
CHANGED
|
@@ -362,11 +362,12 @@ function createToolbarButton({ action, label, icon, disabled = false }) {
|
|
|
362
362
|
return button;
|
|
363
363
|
}
|
|
364
364
|
var DEFAULT_GRID_SIZE = 24;
|
|
365
|
-
var
|
|
366
|
-
var
|
|
367
|
-
var
|
|
368
|
-
var
|
|
369
|
-
var
|
|
365
|
+
var DEFAULT_MIN_SCALE = 0.35;
|
|
366
|
+
var DEFAULT_MAX_SCALE = 3;
|
|
367
|
+
var DEFAULT_MIN_NODE_SIZE = 56;
|
|
368
|
+
var DEFAULT_MAX_NODE_SIZE = 420;
|
|
369
|
+
var DEFAULT_WORLD_EXTENT = 12e3;
|
|
370
|
+
var WIDE_WORLD_EXTENT = 24e4;
|
|
370
371
|
var MAX_NODES = 1e4;
|
|
371
372
|
var MAX_EDGES = 1e4;
|
|
372
373
|
var RESIZE_HANDLES = ["n", "ne", "e", "se", "s", "sw", "w", "nw"];
|
|
@@ -398,7 +399,7 @@ var EDGE_STROKE_PRESETS = [
|
|
|
398
399
|
{ id: "medium", label: "Medium", width: DEFAULT_EDGE_STROKE_WIDTH },
|
|
399
400
|
{ id: "bold", label: "Bold", width: 3.5 }
|
|
400
401
|
];
|
|
401
|
-
var VD_FLOWCHART_VERSION = "1.
|
|
402
|
+
var VD_FLOWCHART_VERSION = "1.5.0";
|
|
402
403
|
var FLOWCHART_DOCUMENT_VERSION = "1.3.0";
|
|
403
404
|
var FLOWCHART_NODE_TYPES = [
|
|
404
405
|
"rounded-rect",
|
|
@@ -539,6 +540,17 @@ function deepClone(value) {
|
|
|
539
540
|
function formatNumber(value) {
|
|
540
541
|
return Number(value.toFixed(2));
|
|
541
542
|
}
|
|
543
|
+
function formatScale(value) {
|
|
544
|
+
return Number(toFiniteNumber(value, 1).toFixed(4));
|
|
545
|
+
}
|
|
546
|
+
function normalizeBoundPair(min, max, fallbackMin, fallbackMax) {
|
|
547
|
+
const low = Number(min);
|
|
548
|
+
const high = Number(max);
|
|
549
|
+
if (!Number.isFinite(low) || !Number.isFinite(high) || low <= 0 || high <= 0 || low >= high) {
|
|
550
|
+
return { min: fallbackMin, max: fallbackMax };
|
|
551
|
+
}
|
|
552
|
+
return { min: low, max: high };
|
|
553
|
+
}
|
|
542
554
|
function sanitizeId(value) {
|
|
543
555
|
if (value == null) return "";
|
|
544
556
|
return String(value).trim();
|
|
@@ -596,21 +608,36 @@ function normalizeNodeType(type) {
|
|
|
596
608
|
function getNodeSpec(type) {
|
|
597
609
|
return DEFAULT_NODE_SPECS[normalizeNodeType(type)];
|
|
598
610
|
}
|
|
599
|
-
function
|
|
611
|
+
function nodeVisualScale(node) {
|
|
612
|
+
if (!node || node.type === "junction") return 1;
|
|
613
|
+
const spec = getNodeSpec(node.type);
|
|
614
|
+
const width = spec.width > 0 ? toFiniteNumber(node.width, spec.width) / spec.width : 1;
|
|
615
|
+
const height = spec.height > 0 ? toFiniteNumber(node.height, spec.height) / spec.height : 1;
|
|
616
|
+
return clamp(Math.min(1, width, height), 0.01, 1);
|
|
617
|
+
}
|
|
618
|
+
function edgeVisualScale(fromNode, toNode) {
|
|
619
|
+
if (fromNode && toNode) return Math.min(nodeVisualScale(fromNode), nodeVisualScale(toNode));
|
|
620
|
+
if (fromNode) return nodeVisualScale(fromNode);
|
|
621
|
+
if (toNode) return nodeVisualScale(toNode);
|
|
622
|
+
return 1;
|
|
623
|
+
}
|
|
624
|
+
function getNodeSizeBounds(type, sizeBounds) {
|
|
600
625
|
const spec = getNodeSpec(type);
|
|
626
|
+
const min = sizeBounds?.min ?? DEFAULT_MIN_NODE_SIZE;
|
|
627
|
+
const max = sizeBounds?.max ?? DEFAULT_MAX_NODE_SIZE;
|
|
601
628
|
return {
|
|
602
|
-
minWidth: spec.minWidth ??
|
|
603
|
-
minHeight: spec.minHeight ??
|
|
604
|
-
maxWidth: spec.maxWidth ??
|
|
605
|
-
maxHeight: spec.maxHeight ??
|
|
629
|
+
minWidth: spec.minWidth ?? min,
|
|
630
|
+
minHeight: spec.minHeight ?? min,
|
|
631
|
+
maxWidth: spec.maxWidth ?? max,
|
|
632
|
+
maxHeight: spec.maxHeight ?? max
|
|
606
633
|
};
|
|
607
634
|
}
|
|
608
|
-
function clampNodeWidth(type, width, fallback) {
|
|
609
|
-
const bounds = getNodeSizeBounds(type);
|
|
635
|
+
function clampNodeWidth(type, width, fallback, sizeBounds) {
|
|
636
|
+
const bounds = getNodeSizeBounds(type, sizeBounds);
|
|
610
637
|
return clamp(toFiniteNumber(width, fallback), bounds.minWidth, bounds.maxWidth);
|
|
611
638
|
}
|
|
612
|
-
function clampNodeHeight(type, height, fallback) {
|
|
613
|
-
const bounds = getNodeSizeBounds(type);
|
|
639
|
+
function clampNodeHeight(type, height, fallback, sizeBounds) {
|
|
640
|
+
const bounds = getNodeSizeBounds(type, sizeBounds);
|
|
614
641
|
return clamp(toFiniteNumber(height, fallback), bounds.minHeight, bounds.maxHeight);
|
|
615
642
|
}
|
|
616
643
|
function isNodeResizable(nodeOrType) {
|
|
@@ -634,14 +661,16 @@ function getStrokePresetWidth(presetId) {
|
|
|
634
661
|
const preset = EDGE_STROKE_PRESETS.find((item) => item.id === presetId);
|
|
635
662
|
return preset?.width ?? DEFAULT_EDGE_STROKE_WIDTH;
|
|
636
663
|
}
|
|
637
|
-
function normalizeViewport(viewport) {
|
|
664
|
+
function normalizeViewport(viewport, scaleBounds) {
|
|
665
|
+
const min = scaleBounds?.min ?? DEFAULT_MIN_SCALE;
|
|
666
|
+
const max = scaleBounds?.max ?? DEFAULT_MAX_SCALE;
|
|
638
667
|
return {
|
|
639
668
|
x: toFiniteNumber(viewport?.x, 0),
|
|
640
669
|
y: toFiniteNumber(viewport?.y, 0),
|
|
641
|
-
scale: clamp(toFiniteNumber(viewport?.scale, 1),
|
|
670
|
+
scale: clamp(formatScale(toFiniteNumber(viewport?.scale, 1)), min, max)
|
|
642
671
|
};
|
|
643
672
|
}
|
|
644
|
-
function normalizeNode(rawNode, index, usedIds) {
|
|
673
|
+
function normalizeNode(rawNode, index, usedIds, sizeBounds) {
|
|
645
674
|
const type = normalizeNodeType(rawNode?.type);
|
|
646
675
|
const spec = getNodeSpec(type);
|
|
647
676
|
const node = {
|
|
@@ -649,8 +678,8 @@ function normalizeNode(rawNode, index, usedIds) {
|
|
|
649
678
|
type,
|
|
650
679
|
x: toFiniteNumber(rawNode?.x, index * 28),
|
|
651
680
|
y: toFiniteNumber(rawNode?.y, index * 18),
|
|
652
|
-
width: clampNodeWidth(type, rawNode?.width, spec.width),
|
|
653
|
-
height: clampNodeHeight(type, rawNode?.height, spec.height),
|
|
681
|
+
width: clampNodeWidth(type, rawNode?.width, spec.width, sizeBounds),
|
|
682
|
+
height: clampNodeHeight(type, rawNode?.height, spec.height, sizeBounds),
|
|
654
683
|
text: rawNode?.text == null ? spec.text : String(rawNode.text),
|
|
655
684
|
data: isPlainObject(rawNode?.data) ? deepClone(rawNode.data) : {}
|
|
656
685
|
};
|
|
@@ -743,7 +772,7 @@ function isSupportedDocumentVersion(value) {
|
|
|
743
772
|
if (parsed.minor === max.minor && parsed.patch > max.patch) return false;
|
|
744
773
|
return true;
|
|
745
774
|
}
|
|
746
|
-
function normalizeDocument(input) {
|
|
775
|
+
function normalizeDocument(input, limits = {}) {
|
|
747
776
|
let source = input;
|
|
748
777
|
if (typeof source === "string") {
|
|
749
778
|
try {
|
|
@@ -766,13 +795,13 @@ function normalizeDocument(input) {
|
|
|
766
795
|
}
|
|
767
796
|
}
|
|
768
797
|
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));
|
|
798
|
+
const nodes = (Array.isArray(source.nodes) ? source.nodes : []).slice(0, MAX_NODES).map((node, index) => normalizeNode(node, index, usedNodeIds, limits.sizeBounds));
|
|
770
799
|
const nodeIds = new Set(nodes.map((node) => node.id));
|
|
771
800
|
const usedEdgeIds = /* @__PURE__ */ new Set();
|
|
772
801
|
const edges = (Array.isArray(source.edges) ? source.edges : []).slice(0, MAX_EDGES).map((edge, index) => normalizeEdge(edge, index, nodeIds, usedEdgeIds)).filter(Boolean);
|
|
773
802
|
return {
|
|
774
803
|
version: FLOWCHART_DOCUMENT_VERSION,
|
|
775
|
-
viewport: normalizeViewport(source.viewport),
|
|
804
|
+
viewport: normalizeViewport(source.viewport, limits.scaleBounds),
|
|
776
805
|
nodes,
|
|
777
806
|
edges
|
|
778
807
|
};
|
|
@@ -989,19 +1018,30 @@ function getPolylineLabelPoint(points) {
|
|
|
989
1018
|
const last = points[points.length - 1] || { x: 0, y: 0 };
|
|
990
1019
|
return { x: formatNumber(last.x), y: formatNumber(last.y) };
|
|
991
1020
|
}
|
|
992
|
-
function
|
|
1021
|
+
function routeMetrics(edgeScale = 1) {
|
|
1022
|
+
const scale = clamp(toFiniteNumber(edgeScale, 1), 0.01, 1);
|
|
1023
|
+
return {
|
|
1024
|
+
scale,
|
|
1025
|
+
minCurveArm: MIN_CURVE_ARM * scale,
|
|
1026
|
+
maxCurveArm: MAX_CURVE_ARM * scale,
|
|
1027
|
+
stub: ORTHOGONAL_STUB_LENGTH * scale,
|
|
1028
|
+
corner: ORTHOGONAL_CORNER_RADIUS * scale,
|
|
1029
|
+
clearance: ORTHOGONAL_CLEARANCE * scale
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
function curveControlPoint(point, port, target, curvature, metrics = routeMetrics(1)) {
|
|
993
1033
|
const normal = getPortNormal(port);
|
|
994
1034
|
const dx = target.x - point.x;
|
|
995
1035
|
const dy = target.y - point.y;
|
|
996
1036
|
const distance = Math.hypot(dx, dy) || 1;
|
|
997
1037
|
const align = (normal.x * dx + normal.y * dy) / distance;
|
|
998
1038
|
const ease = 0.55 + 0.45 * align;
|
|
999
|
-
const arm = clamp(distance * curvature * ease,
|
|
1039
|
+
const arm = clamp(distance * curvature * ease, metrics.minCurveArm, metrics.maxCurveArm);
|
|
1000
1040
|
return { x: point.x + normal.x * arm, y: point.y + normal.y * arm };
|
|
1001
1041
|
}
|
|
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);
|
|
1042
|
+
function buildCurvePath(fromPoint, toPoint, fromPort = "right", toPort = "left", metrics = routeMetrics(1)) {
|
|
1043
|
+
const controlA = curveControlPoint(fromPoint, fromPort, toPoint, EDGE_CURVATURE, metrics);
|
|
1044
|
+
const controlB = curveControlPoint(toPoint, toPort, fromPoint, EDGE_CURVATURE, metrics);
|
|
1005
1045
|
const labelX = 0.125 * fromPoint.x + 0.375 * controlA.x + 0.375 * controlB.x + 0.125 * toPoint.x;
|
|
1006
1046
|
const labelY = 0.125 * fromPoint.y + 0.375 * controlA.y + 0.375 * controlB.y + 0.125 * toPoint.y;
|
|
1007
1047
|
return {
|
|
@@ -1063,11 +1103,11 @@ function getOrthogonalMidpoints(fromStub, toStub, fromPort, toPort) {
|
|
|
1063
1103
|
}
|
|
1064
1104
|
return [{ x: fromStub.x, y: toStub.y }];
|
|
1065
1105
|
}
|
|
1066
|
-
function getOrthogonalPoints(fromPoint, toPoint, fromPort, toPort, fromRect, toRect) {
|
|
1106
|
+
function getOrthogonalPoints(fromPoint, toPoint, fromPort, toPort, fromRect, toRect, metrics = routeMetrics(1)) {
|
|
1067
1107
|
const fromNormal = getPortNormal(fromPort);
|
|
1068
1108
|
const toNormal = getPortNormal(toPort);
|
|
1069
|
-
const fromStub = offsetPoint(fromPoint, fromNormal,
|
|
1070
|
-
const toStub = offsetPoint(toPoint, toNormal,
|
|
1109
|
+
const fromStub = offsetPoint(fromPoint, fromNormal, metrics.stub);
|
|
1110
|
+
const toStub = offsetPoint(toPoint, toNormal, metrics.stub);
|
|
1071
1111
|
const sourceHorizontal = isHorizontalPort(fromPort);
|
|
1072
1112
|
const targetHorizontal = isHorizontalPort(toPort);
|
|
1073
1113
|
const mids = [];
|
|
@@ -1107,13 +1147,13 @@ function getOrthogonalPoints(fromPoint, toPoint, fromPort, toPort, fromRect, toR
|
|
|
1107
1147
|
);
|
|
1108
1148
|
return points;
|
|
1109
1149
|
}
|
|
1110
|
-
function buildOrthogonalPath(fromPoint, toPoint, fromPort = "right", toPort = "left", fromRect = null, toRect = null) {
|
|
1150
|
+
function buildOrthogonalPath(fromPoint, toPoint, fromPort = "right", toPort = "left", fromRect = null, toRect = null, metrics = routeMetrics(1)) {
|
|
1111
1151
|
let points;
|
|
1112
1152
|
if (fromRect && toRect) {
|
|
1113
|
-
points = getOrthogonalPoints(fromPoint, toPoint, fromPort, toPort, fromRect, toRect);
|
|
1153
|
+
points = getOrthogonalPoints(fromPoint, toPoint, fromPort, toPort, fromRect, toRect, metrics);
|
|
1114
1154
|
} else {
|
|
1115
|
-
const fromStub = offsetPoint(fromPoint, getPortNormal(fromPort),
|
|
1116
|
-
const toStub = offsetPoint(toPoint, getPortNormal(toPort),
|
|
1155
|
+
const fromStub = offsetPoint(fromPoint, getPortNormal(fromPort), metrics.stub);
|
|
1156
|
+
const toStub = offsetPoint(toPoint, getPortNormal(toPort), metrics.stub);
|
|
1117
1157
|
points = [];
|
|
1118
1158
|
[
|
|
1119
1159
|
fromPoint,
|
|
@@ -1125,7 +1165,7 @@ function buildOrthogonalPath(fromPoint, toPoint, fromPort = "right", toPort = "l
|
|
|
1125
1165
|
}
|
|
1126
1166
|
const label = getPolylineLabelPoint(points);
|
|
1127
1167
|
return {
|
|
1128
|
-
d: buildRoundedPath(points,
|
|
1168
|
+
d: buildRoundedPath(points, metrics.corner),
|
|
1129
1169
|
labelX: label.x,
|
|
1130
1170
|
labelY: label.y
|
|
1131
1171
|
};
|
|
@@ -1136,6 +1176,7 @@ function buildEdgePath(edge, fromNode = null, toNode = null) {
|
|
|
1136
1176
|
const fromPoint = edge?.fromPoint || (fromNode ? getPortPosition(fromNode, fromPort) : null);
|
|
1137
1177
|
const toPoint = edge?.toPoint || (toNode ? getPortPosition(toNode, toPort) : null);
|
|
1138
1178
|
const route = normalizeEdgeRoute(edge?.route);
|
|
1179
|
+
const metrics = routeMetrics(edgeVisualScale(fromNode, toNode));
|
|
1139
1180
|
if (!fromPoint || !toPoint) {
|
|
1140
1181
|
return { d: "", labelX: 0, labelY: 0 };
|
|
1141
1182
|
}
|
|
@@ -1143,15 +1184,17 @@ function buildEdgePath(edge, fromNode = null, toNode = null) {
|
|
|
1143
1184
|
return buildStraightPath(fromPoint, toPoint);
|
|
1144
1185
|
}
|
|
1145
1186
|
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);
|
|
1187
|
+
const fromRect = !edge?.fromPoint && fromNode ? getNodeRectBounds(fromNode, metrics.clearance) : null;
|
|
1188
|
+
const toRect = !edge?.toPoint && toNode ? getNodeRectBounds(toNode, metrics.clearance) : null;
|
|
1189
|
+
return buildOrthogonalPath(fromPoint, toPoint, fromPort, toPort, fromRect, toRect, metrics);
|
|
1149
1190
|
}
|
|
1150
|
-
return buildCurvePath(fromPoint, toPoint, fromPort, toPort);
|
|
1191
|
+
return buildCurvePath(fromPoint, toPoint, fromPort, toPort, metrics);
|
|
1151
1192
|
}
|
|
1152
|
-
function createArrowMarker(id, strokeWidth, reversed = false) {
|
|
1153
|
-
const
|
|
1154
|
-
const
|
|
1193
|
+
function createArrowMarker(id, strokeWidth, reversed = false, edgeScale = 1) {
|
|
1194
|
+
const scale = clamp(toFiniteNumber(edgeScale, 1), 0.01, 1);
|
|
1195
|
+
const effective = Math.max(0.02, strokeWidth * scale);
|
|
1196
|
+
const size = formatNumber(Math.max(9 * scale, effective * 4.4));
|
|
1197
|
+
const refInset = formatNumber(Math.max(1.5 * scale, effective * 0.85));
|
|
1155
1198
|
const marker = svgEl("marker", {
|
|
1156
1199
|
id,
|
|
1157
1200
|
markerWidth: size,
|
|
@@ -1169,9 +1212,11 @@ function createArrowMarker(id, strokeWidth, reversed = false) {
|
|
|
1169
1212
|
);
|
|
1170
1213
|
return marker;
|
|
1171
1214
|
}
|
|
1172
|
-
function createDotMarker(id, strokeWidth) {
|
|
1173
|
-
const
|
|
1174
|
-
const
|
|
1215
|
+
function createDotMarker(id, strokeWidth, edgeScale = 1) {
|
|
1216
|
+
const scale = clamp(toFiniteNumber(edgeScale, 1), 0.01, 1);
|
|
1217
|
+
const effective = Math.max(0.02, strokeWidth * scale);
|
|
1218
|
+
const size = formatNumber(Math.max(8 * scale, effective * 3.3));
|
|
1219
|
+
const radius = formatNumber(Math.max(2.75 * scale, effective * 1.45));
|
|
1175
1220
|
const marker = svgEl("marker", {
|
|
1176
1221
|
id,
|
|
1177
1222
|
markerWidth: size,
|
|
@@ -1428,7 +1473,23 @@ var VdFlowchart = class {
|
|
|
1428
1473
|
this.element = resolveElement(options.element || options.target);
|
|
1429
1474
|
this.readonly = Boolean(options.readonly);
|
|
1430
1475
|
this.gridSize = clamp(toFiniteNumber(options.gridSize, DEFAULT_GRID_SIZE), 12, 64);
|
|
1431
|
-
|
|
1476
|
+
const scaleBounds = normalizeBoundPair(
|
|
1477
|
+
options.minScale,
|
|
1478
|
+
options.maxScale,
|
|
1479
|
+
DEFAULT_MIN_SCALE,
|
|
1480
|
+
DEFAULT_MAX_SCALE
|
|
1481
|
+
);
|
|
1482
|
+
const sizeBounds = normalizeBoundPair(
|
|
1483
|
+
options.minNodeSize,
|
|
1484
|
+
options.maxNodeSize,
|
|
1485
|
+
DEFAULT_MIN_NODE_SIZE,
|
|
1486
|
+
DEFAULT_MAX_NODE_SIZE
|
|
1487
|
+
);
|
|
1488
|
+
this.minScale = scaleBounds.min;
|
|
1489
|
+
this.maxScale = scaleBounds.max;
|
|
1490
|
+
this.minNodeSize = sizeBounds.min;
|
|
1491
|
+
this.maxNodeSize = sizeBounds.max;
|
|
1492
|
+
this.documentData = normalizeDocument(options.data || {}, { scaleBounds, sizeBounds });
|
|
1432
1493
|
this.listeners = {};
|
|
1433
1494
|
this.selection = null;
|
|
1434
1495
|
this.selectedNodeIds = /* @__PURE__ */ new Set();
|
|
@@ -1656,10 +1717,10 @@ var VdFlowchart = class {
|
|
|
1656
1717
|
this.world = svgEl("g", { class: "vd-flowchart-world" });
|
|
1657
1718
|
this.gridRect = svgEl("rect", {
|
|
1658
1719
|
class: "vd-flowchart-grid",
|
|
1659
|
-
x: -
|
|
1660
|
-
y: -
|
|
1661
|
-
width:
|
|
1662
|
-
height:
|
|
1720
|
+
x: -DEFAULT_WORLD_EXTENT / 2,
|
|
1721
|
+
y: -DEFAULT_WORLD_EXTENT / 2,
|
|
1722
|
+
width: DEFAULT_WORLD_EXTENT,
|
|
1723
|
+
height: DEFAULT_WORLD_EXTENT,
|
|
1663
1724
|
fill: `url(#${this.gridPatternId})`
|
|
1664
1725
|
});
|
|
1665
1726
|
this.edgesLayer = svgEl("g", { class: "vd-flowchart-edges" });
|
|
@@ -2442,7 +2503,7 @@ var VdFlowchart = class {
|
|
|
2442
2503
|
const width = this.canvasEl.clientWidth || 800;
|
|
2443
2504
|
const height = this.canvasEl.clientHeight || 560;
|
|
2444
2505
|
this.scaleAround(
|
|
2445
|
-
clamp(scale,
|
|
2506
|
+
clamp(formatScale(scale), this.minScale, this.maxScale) / viewport.scale,
|
|
2446
2507
|
width / 2,
|
|
2447
2508
|
height / 2,
|
|
2448
2509
|
"viewport:zoom"
|
|
@@ -2477,7 +2538,8 @@ var VdFlowchart = class {
|
|
|
2477
2538
|
const node = normalizeNode(
|
|
2478
2539
|
{ ...data, id: void 0, x: data.x + 24, y: data.y + 24 },
|
|
2479
2540
|
this.documentData.nodes.length + index,
|
|
2480
|
-
usedNodeIds
|
|
2541
|
+
usedNodeIds,
|
|
2542
|
+
this.nodeSizeBounds()
|
|
2481
2543
|
);
|
|
2482
2544
|
idMap.set(data.id, node.id);
|
|
2483
2545
|
return node;
|
|
@@ -2509,7 +2571,8 @@ var VdFlowchart = class {
|
|
|
2509
2571
|
const node = normalizeNode(
|
|
2510
2572
|
{ ...data, id: void 0, collapsed: void 0, x: data.x + 24, y: data.y + 24 },
|
|
2511
2573
|
this.documentData.nodes.length,
|
|
2512
|
-
usedIds
|
|
2574
|
+
usedIds,
|
|
2575
|
+
this.nodeSizeBounds()
|
|
2513
2576
|
);
|
|
2514
2577
|
this.documentData.nodes.push(node);
|
|
2515
2578
|
this.select({ kind: "node", id: node.id });
|
|
@@ -3123,14 +3186,15 @@ var VdFlowchart = class {
|
|
|
3123
3186
|
if (this.activeTool === "arrow") return true;
|
|
3124
3187
|
return this.selection?.kind === "node" && this.selection.id === node.id && !this.hasMultiSelection();
|
|
3125
3188
|
}
|
|
3126
|
-
getMarkerId(markerType, position, strokeWidth) {
|
|
3189
|
+
getMarkerId(markerType, position, strokeWidth, edgeScale = 1) {
|
|
3127
3190
|
const width = normalizeEdgeStrokeWidth(strokeWidth);
|
|
3128
|
-
const
|
|
3191
|
+
const scale = clamp(formatNumber(Math.max(edgeScale, 0.01)), 0.01, 1);
|
|
3192
|
+
const key = `${markerType}:${position}:${width}:${scale}`;
|
|
3129
3193
|
if (this.markerIds.has(key)) {
|
|
3130
3194
|
return this.markerIds.get(key);
|
|
3131
3195
|
}
|
|
3132
3196
|
const markerId = nextId(`flowchart-${markerType}-${position}`);
|
|
3133
|
-
const marker = markerType === "dot" ? createDotMarker(markerId, width) : createArrowMarker(markerId, width, position === "start");
|
|
3197
|
+
const marker = markerType === "dot" ? createDotMarker(markerId, width, scale) : createArrowMarker(markerId, width, position === "start", scale);
|
|
3134
3198
|
this.markerDefs.appendChild(marker);
|
|
3135
3199
|
this.markerIds.set(key, markerId);
|
|
3136
3200
|
return markerId;
|
|
@@ -3138,8 +3202,15 @@ var VdFlowchart = class {
|
|
|
3138
3202
|
getEdgeStrokeWidth(edge) {
|
|
3139
3203
|
return normalizeEdgeStrokeWidth(edge?.strokeWidth);
|
|
3140
3204
|
}
|
|
3141
|
-
getEdgeHitStrokeWidth(edge) {
|
|
3142
|
-
|
|
3205
|
+
getEdgeHitStrokeWidth(edge, edgeScale = 1) {
|
|
3206
|
+
const visual = clamp(toFiniteNumber(edgeScale, 1), 0.01, 1);
|
|
3207
|
+
const base = this.getEdgeStrokeWidth(edge);
|
|
3208
|
+
const view = Math.max(this.documentData?.viewport?.scale || 1, 1e-4);
|
|
3209
|
+
const legacy = Math.max(EDGE_HIT_STROKE_MIN, base + 12);
|
|
3210
|
+
if (visual >= 0.999 && view >= DEFAULT_MIN_SCALE && view <= DEFAULT_MAX_SCALE) {
|
|
3211
|
+
return formatNumber(legacy);
|
|
3212
|
+
}
|
|
3213
|
+
return formatNumber(Math.max(base * visual, EDGE_HIT_STROKE_MIN / view));
|
|
3143
3214
|
}
|
|
3144
3215
|
findConnectionTarget(worldPoint, excludeNodeId, previousSnap = null, referencePoint = null) {
|
|
3145
3216
|
const snapPadding = CONNECTION_SNAP_PADDING / this.documentData.viewport.scale;
|
|
@@ -3196,17 +3267,37 @@ var VdFlowchart = class {
|
|
|
3196
3267
|
let width = original.width;
|
|
3197
3268
|
let height = original.height;
|
|
3198
3269
|
if (handle.includes("e")) {
|
|
3199
|
-
width = clampNodeWidth(
|
|
3270
|
+
width = clampNodeWidth(
|
|
3271
|
+
nextType,
|
|
3272
|
+
original.width + deltaX,
|
|
3273
|
+
original.width,
|
|
3274
|
+
this.nodeSizeBounds()
|
|
3275
|
+
);
|
|
3200
3276
|
}
|
|
3201
3277
|
if (handle.includes("s")) {
|
|
3202
|
-
height = clampNodeHeight(
|
|
3278
|
+
height = clampNodeHeight(
|
|
3279
|
+
nextType,
|
|
3280
|
+
original.height + deltaY,
|
|
3281
|
+
original.height,
|
|
3282
|
+
this.nodeSizeBounds()
|
|
3283
|
+
);
|
|
3203
3284
|
}
|
|
3204
3285
|
if (handle.includes("w")) {
|
|
3205
|
-
width = clampNodeWidth(
|
|
3286
|
+
width = clampNodeWidth(
|
|
3287
|
+
nextType,
|
|
3288
|
+
original.width - deltaX,
|
|
3289
|
+
original.width,
|
|
3290
|
+
this.nodeSizeBounds()
|
|
3291
|
+
);
|
|
3206
3292
|
x = original.x + original.width - width;
|
|
3207
3293
|
}
|
|
3208
3294
|
if (handle.includes("n")) {
|
|
3209
|
-
height = clampNodeHeight(
|
|
3295
|
+
height = clampNodeHeight(
|
|
3296
|
+
nextType,
|
|
3297
|
+
original.height - deltaY,
|
|
3298
|
+
original.height,
|
|
3299
|
+
this.nodeSizeBounds()
|
|
3300
|
+
);
|
|
3210
3301
|
y = original.y + original.height - height;
|
|
3211
3302
|
}
|
|
3212
3303
|
return {
|
|
@@ -3242,7 +3333,7 @@ var VdFlowchart = class {
|
|
|
3242
3333
|
scaleAround(factor, localX, localY, reason) {
|
|
3243
3334
|
const viewport = this.documentData.viewport;
|
|
3244
3335
|
const anchor = this.localToWorld(localX, localY);
|
|
3245
|
-
const nextScale = clamp(
|
|
3336
|
+
const nextScale = clamp(formatScale(viewport.scale * factor), this.minScale, this.maxScale);
|
|
3246
3337
|
if (nextScale === viewport.scale) return;
|
|
3247
3338
|
viewport.scale = nextScale;
|
|
3248
3339
|
viewport.x = formatNumber(localX - anchor.x * nextScale);
|
|
@@ -3425,19 +3516,20 @@ var VdFlowchart = class {
|
|
|
3425
3516
|
}
|
|
3426
3517
|
const viewport = this.documentData.viewport;
|
|
3427
3518
|
const scale = viewport.scale;
|
|
3519
|
+
const visual = nodeVisualScale(node);
|
|
3428
3520
|
const textarea = this.textEditor.textarea;
|
|
3429
3521
|
const width = node.width * scale;
|
|
3430
3522
|
const height = node.height * scale;
|
|
3431
3523
|
const metrics = getNodeFontMetrics(node);
|
|
3432
|
-
const lineHeight = metrics.lineHeight * scale;
|
|
3524
|
+
const lineHeight = metrics.lineHeight * visual * scale;
|
|
3433
3525
|
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);
|
|
3526
|
+
const horizontalPadding = (node.type === "textbox" ? 16 : 12) * visual * scale;
|
|
3527
|
+
const verticalPadding = node.type === "textbox" ? 12 * visual * scale : Math.max(4 * visual, (height - lineCount * lineHeight) / 2);
|
|
3436
3528
|
textarea.style.left = `${formatNumber(viewport.x + node.x * scale)}px`;
|
|
3437
3529
|
textarea.style.top = `${formatNumber(viewport.y + node.y * scale)}px`;
|
|
3438
3530
|
textarea.style.width = `${formatNumber(width)}px`;
|
|
3439
3531
|
textarea.style.height = `${formatNumber(height)}px`;
|
|
3440
|
-
textarea.style.fontSize = `${formatNumber(metrics.fontSize * scale)}px`;
|
|
3532
|
+
textarea.style.fontSize = `${formatNumber(metrics.fontSize * visual * scale)}px`;
|
|
3441
3533
|
textarea.style.lineHeight = `${formatNumber(lineHeight)}px`;
|
|
3442
3534
|
textarea.style.padding = `${formatNumber(verticalPadding)}px ${formatNumber(horizontalPadding)}px`;
|
|
3443
3535
|
}
|
|
@@ -3449,14 +3541,16 @@ var VdFlowchart = class {
|
|
|
3449
3541
|
this.stopTextEdit({ commit: false });
|
|
3450
3542
|
return;
|
|
3451
3543
|
}
|
|
3544
|
+
const visual = edgeVisualScale(fromNode, toNode);
|
|
3452
3545
|
const { labelX, labelY } = buildEdgePath(edge, fromNode, toNode);
|
|
3453
3546
|
const viewport = this.documentData.viewport;
|
|
3454
3547
|
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;
|
|
3548
|
+
const fontSize = Math.max(11 * visual, 13 * visual * viewport.scale);
|
|
3549
|
+
const width = clamp(textarea.value.length * fontSize * 0.62 + 28 * visual, 96 * visual, 280);
|
|
3550
|
+
const height = fontSize * 1.4 + 12 * visual;
|
|
3551
|
+
const labelOffset = 8 * visual;
|
|
3458
3552
|
textarea.style.left = `${formatNumber(viewport.x + labelX * viewport.scale - width / 2)}px`;
|
|
3459
|
-
textarea.style.top = `${formatNumber(viewport.y + (labelY -
|
|
3553
|
+
textarea.style.top = `${formatNumber(viewport.y + (labelY - labelOffset) * viewport.scale - height / 2)}px`;
|
|
3460
3554
|
textarea.style.width = `${formatNumber(width)}px`;
|
|
3461
3555
|
textarea.style.height = `${formatNumber(height)}px`;
|
|
3462
3556
|
textarea.style.fontSize = `${formatNumber(fontSize)}px`;
|
|
@@ -3465,6 +3559,7 @@ var VdFlowchart = class {
|
|
|
3465
3559
|
}
|
|
3466
3560
|
render(options = {}) {
|
|
3467
3561
|
if (this.destroyed) return;
|
|
3562
|
+
this.syncGrid();
|
|
3468
3563
|
const shouldRenderScene = options.scene !== false;
|
|
3469
3564
|
const shouldRenderInspector = options.inspector !== false;
|
|
3470
3565
|
const shouldRenderJson = options.json !== false;
|
|
@@ -3488,6 +3583,8 @@ var VdFlowchart = class {
|
|
|
3488
3583
|
clearChildren(this.previewLayer);
|
|
3489
3584
|
clearChildren(this.nodesLayer);
|
|
3490
3585
|
clearChildren(this.overlayLayer);
|
|
3586
|
+
clearChildren(this.markerDefs);
|
|
3587
|
+
this.markerIds = /* @__PURE__ */ new Map();
|
|
3491
3588
|
const hidden = this.getHiddenNodeIds();
|
|
3492
3589
|
this.sceneHidden = hidden;
|
|
3493
3590
|
this.sceneOutgoing = this.getOutgoingMap();
|
|
@@ -3551,17 +3648,18 @@ var VdFlowchart = class {
|
|
|
3551
3648
|
}
|
|
3552
3649
|
}
|
|
3553
3650
|
}
|
|
3554
|
-
applyEdgeMarkers(pathElement, edge) {
|
|
3651
|
+
applyEdgeMarkers(pathElement, edge, edgeScale = 1) {
|
|
3652
|
+
const width = this.getEdgeStrokeWidth(edge);
|
|
3555
3653
|
if (edge.startMarker && edge.startMarker !== "none") {
|
|
3556
3654
|
pathElement.setAttribute(
|
|
3557
3655
|
"marker-start",
|
|
3558
|
-
`url(#${this.getMarkerId(edge.startMarker, "start",
|
|
3656
|
+
`url(#${this.getMarkerId(edge.startMarker, "start", width, edgeScale)})`
|
|
3559
3657
|
);
|
|
3560
3658
|
}
|
|
3561
3659
|
if (edge.endMarker && edge.endMarker !== "none") {
|
|
3562
3660
|
pathElement.setAttribute(
|
|
3563
3661
|
"marker-end",
|
|
3564
|
-
`url(#${this.getMarkerId(edge.endMarker, "end",
|
|
3662
|
+
`url(#${this.getMarkerId(edge.endMarker, "end", width, edgeScale)})`
|
|
3565
3663
|
);
|
|
3566
3664
|
}
|
|
3567
3665
|
}
|
|
@@ -3569,6 +3667,7 @@ var VdFlowchart = class {
|
|
|
3569
3667
|
const fromNode = nodeMap.get(edge.from.nodeId);
|
|
3570
3668
|
const toNode = nodeMap.get(edge.to.nodeId);
|
|
3571
3669
|
if (!fromNode || !toNode) return null;
|
|
3670
|
+
const visual = edgeVisualScale(fromNode, toNode);
|
|
3572
3671
|
const edgePath = buildEdgePath(edge, fromNode, toNode);
|
|
3573
3672
|
const selected = this.selection?.kind === "edge" && this.selection.id === edge.id;
|
|
3574
3673
|
const inReconnectMode = this.reconnectEdgeId === edge.id;
|
|
@@ -3576,13 +3675,14 @@ var VdFlowchart = class {
|
|
|
3576
3675
|
class: `vd-flowchart-edge${selected ? " is-selected" : ""}${inReconnectMode ? " is-reconnecting" : ""}`,
|
|
3577
3676
|
"data-edge-id": edge.id
|
|
3578
3677
|
});
|
|
3579
|
-
const
|
|
3678
|
+
const baseStroke = this.getEdgeStrokeWidth(edge);
|
|
3679
|
+
const strokeWidth = visual < 0.999 ? formatNumber(Math.max(0.02, baseStroke * visual)) : baseStroke;
|
|
3580
3680
|
if (selected) {
|
|
3581
3681
|
group.appendChild(
|
|
3582
3682
|
svgEl("path", {
|
|
3583
3683
|
class: "vd-flowchart-edge-selection",
|
|
3584
3684
|
d: edgePath.d,
|
|
3585
|
-
"stroke-width": formatNumber(strokeWidth + 7)
|
|
3685
|
+
"stroke-width": visual < 0.999 ? formatNumber((baseStroke + 7) * visual) : formatNumber(strokeWidth + 7)
|
|
3586
3686
|
})
|
|
3587
3687
|
);
|
|
3588
3688
|
}
|
|
@@ -3592,23 +3692,28 @@ var VdFlowchart = class {
|
|
|
3592
3692
|
"stroke-width": strokeWidth,
|
|
3593
3693
|
"data-edge-id": edge.id
|
|
3594
3694
|
});
|
|
3595
|
-
this.applyEdgeMarkers(visiblePath, edge);
|
|
3695
|
+
this.applyEdgeMarkers(visiblePath, edge, visual);
|
|
3596
3696
|
const hitPath = svgEl("path", {
|
|
3597
3697
|
class: "vd-flowchart-edge-hit",
|
|
3598
3698
|
d: edgePath.d,
|
|
3599
|
-
"stroke-width": this.getEdgeHitStrokeWidth(edge),
|
|
3699
|
+
"stroke-width": this.getEdgeHitStrokeWidth(edge, visual),
|
|
3600
3700
|
"data-edge-id": edge.id
|
|
3601
3701
|
});
|
|
3602
3702
|
group.appendChild(visiblePath);
|
|
3603
3703
|
group.appendChild(hitPath);
|
|
3604
3704
|
if (edge.label && this.textEditor?.edgeId !== edge.id) {
|
|
3705
|
+
const labelOffset = visual < 0.999 ? formatNumber(8 * visual) : 8;
|
|
3605
3706
|
const label = svgEl("text", {
|
|
3606
3707
|
class: "vd-flowchart-edge-label",
|
|
3607
3708
|
x: edgePath.labelX,
|
|
3608
|
-
y: edgePath.labelY -
|
|
3709
|
+
y: edgePath.labelY - labelOffset,
|
|
3609
3710
|
"text-anchor": "middle",
|
|
3610
3711
|
"data-edge-id": edge.id
|
|
3611
3712
|
});
|
|
3713
|
+
if (visual < 0.999) {
|
|
3714
|
+
label.style.fontSize = `${formatNumber(Math.max(0.06, 12 * visual))}px`;
|
|
3715
|
+
label.style.strokeWidth = `${formatNumber(Math.max(0.03, 5 * visual))}px`;
|
|
3716
|
+
}
|
|
3612
3717
|
label.textContent = edge.label;
|
|
3613
3718
|
group.appendChild(label);
|
|
3614
3719
|
}
|
|
@@ -3647,18 +3752,26 @@ var VdFlowchart = class {
|
|
|
3647
3752
|
renderPreviewEdge(interaction) {
|
|
3648
3753
|
const fromPort = interaction.fromPort || interaction.source?.port || "right";
|
|
3649
3754
|
const toPort = interaction.toPort || interaction.target?.port || "left";
|
|
3650
|
-
const
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3755
|
+
const fromNode = interaction.source?.nodeId ? this.findNode(interaction.source.nodeId) : null;
|
|
3756
|
+
const toNode = interaction.target?.nodeId ? this.findNode(interaction.target.nodeId) : null;
|
|
3757
|
+
const visual = edgeVisualScale(fromNode, toNode);
|
|
3758
|
+
const edgePath = buildEdgePath(
|
|
3759
|
+
{
|
|
3760
|
+
strokeWidth: interaction.strokeWidth,
|
|
3761
|
+
route: interaction.route,
|
|
3762
|
+
fromPoint: interaction.fromPoint,
|
|
3763
|
+
toPoint: interaction.toPoint,
|
|
3764
|
+
from: { port: fromPort },
|
|
3765
|
+
to: { port: toPort }
|
|
3766
|
+
},
|
|
3767
|
+
fromNode,
|
|
3768
|
+
toNode
|
|
3769
|
+
);
|
|
3770
|
+
const baseStroke = this.getEdgeStrokeWidth(interaction);
|
|
3658
3771
|
return svgEl("path", {
|
|
3659
3772
|
class: "vd-flowchart-preview-path",
|
|
3660
3773
|
d: edgePath.d,
|
|
3661
|
-
"stroke-width":
|
|
3774
|
+
"stroke-width": visual < 0.999 ? formatNumber(Math.max(0.02, baseStroke * visual)) : baseStroke
|
|
3662
3775
|
});
|
|
3663
3776
|
}
|
|
3664
3777
|
renderNode(node) {
|
|
@@ -3673,17 +3786,21 @@ var VdFlowchart = class {
|
|
|
3673
3786
|
"data-node-id": node.id
|
|
3674
3787
|
});
|
|
3675
3788
|
const portsVisible = this.shouldShowNodePorts(node);
|
|
3789
|
+
const visual = nodeVisualScale(node);
|
|
3790
|
+
const corner = node.type === "rounded-rect" || node.type === "textbox" ? 16 : node.type === "junction" ? node.width / 2 : 6;
|
|
3676
3791
|
const hitbox = svgEl("rect", {
|
|
3677
3792
|
x: 0,
|
|
3678
3793
|
y: 0,
|
|
3679
3794
|
width: node.width,
|
|
3680
3795
|
height: node.height,
|
|
3681
|
-
rx:
|
|
3796
|
+
rx: visual < 0.999 && node.type !== "junction" ? formatNumber(corner * visual) : corner,
|
|
3682
3797
|
fill: "transparent",
|
|
3683
3798
|
"data-node-id": node.id
|
|
3684
3799
|
});
|
|
3685
3800
|
group.appendChild(hitbox);
|
|
3686
|
-
|
|
3801
|
+
const shape = this.renderNodeShape(node);
|
|
3802
|
+
this.applyScaledNodeShape(node, shape);
|
|
3803
|
+
group.appendChild(shape);
|
|
3687
3804
|
group.appendChild(this.renderNodeText(node));
|
|
3688
3805
|
const scale = this.documentData.viewport.scale || 1;
|
|
3689
3806
|
if (soloSelected && !this.readonly && SELECTION_BOX_TYPES.has(node.type)) {
|
|
@@ -3875,6 +3992,21 @@ var VdFlowchart = class {
|
|
|
3875
3992
|
});
|
|
3876
3993
|
return group;
|
|
3877
3994
|
}
|
|
3995
|
+
applyScaledNodeShape(node, shape) {
|
|
3996
|
+
const visual = nodeVisualScale(node);
|
|
3997
|
+
if (!shape || visual >= 0.999) return;
|
|
3998
|
+
const baseStroke = node.type === "junction" ? 1.25 : 1.5;
|
|
3999
|
+
shape.style.strokeWidth = String(formatNumber(Math.max(0.03, baseStroke * visual)));
|
|
4000
|
+
if (node.type === "textbox") {
|
|
4001
|
+
shape.style.strokeDasharray = `${formatNumber(8 * visual)} ${formatNumber(5 * visual)}`;
|
|
4002
|
+
}
|
|
4003
|
+
const baseRadius = node.type === "rounded-rect" ? 18 : node.type === "textbox" ? 14 : node.type === "rect" ? 2 : 0;
|
|
4004
|
+
if (baseRadius > 0) {
|
|
4005
|
+
const scaled = String(formatNumber(baseRadius * visual));
|
|
4006
|
+
shape.setAttribute("rx", scaled);
|
|
4007
|
+
shape.setAttribute("ry", scaled);
|
|
4008
|
+
}
|
|
4009
|
+
}
|
|
3878
4010
|
renderNodeShape(node) {
|
|
3879
4011
|
const baseClass = `vd-flowchart-node-shape vd-flowchart-node-shape--${node.type}`;
|
|
3880
4012
|
if (node.type === "rounded-rect") {
|
|
@@ -3951,21 +4083,28 @@ var VdFlowchart = class {
|
|
|
3951
4083
|
if (node.type === "junction" || !node.text) {
|
|
3952
4084
|
return textElement;
|
|
3953
4085
|
}
|
|
4086
|
+
const visual = nodeVisualScale(node);
|
|
4087
|
+
const metrics = getNodeFontMetrics(node);
|
|
4088
|
+
const lineHeight = visual < 0.999 ? Math.max(0.06, metrics.lineHeight * visual) : metrics.lineHeight;
|
|
4089
|
+
if (visual < 0.999) {
|
|
4090
|
+
textElement.style.fontSize = `${formatNumber(Math.max(0.05, metrics.fontSize * visual))}px`;
|
|
4091
|
+
}
|
|
4092
|
+
const equivalentWidth = visual < 0.999 ? node.width / visual : node.width;
|
|
3954
4093
|
if (node.type === "textbox") {
|
|
3955
|
-
const lines2 = wrapText(node.text, estimateChars(
|
|
3956
|
-
const
|
|
4094
|
+
const lines2 = wrapText(node.text, estimateChars(equivalentWidth - 32));
|
|
4095
|
+
const padX = visual < 0.999 ? formatNumber(16 * visual) : 16;
|
|
4096
|
+
const startY2 = visual < 0.999 ? formatNumber(28 * visual) : 28;
|
|
3957
4097
|
lines2.forEach((line, index) => {
|
|
3958
4098
|
const span = svgEl("tspan", {
|
|
3959
|
-
x:
|
|
3960
|
-
y:
|
|
4099
|
+
x: padX,
|
|
4100
|
+
y: visual < 0.999 ? formatNumber(startY2 + index * lineHeight) : startY2 + index * lineHeight
|
|
3961
4101
|
});
|
|
3962
4102
|
span.textContent = line;
|
|
3963
4103
|
textElement.appendChild(span);
|
|
3964
4104
|
});
|
|
3965
4105
|
return textElement;
|
|
3966
4106
|
}
|
|
3967
|
-
const lines = wrapText(node.text, estimateChars(
|
|
3968
|
-
const { lineHeight } = getNodeFontMetrics(node);
|
|
4107
|
+
const lines = wrapText(node.text, estimateChars(equivalentWidth));
|
|
3969
4108
|
const totalHeight = (lines.length - 1) * lineHeight;
|
|
3970
4109
|
const startY = node.height / 2 - totalHeight / 2;
|
|
3971
4110
|
textElement.setAttribute("text-anchor", "middle");
|
|
@@ -3973,7 +4112,7 @@ var VdFlowchart = class {
|
|
|
3973
4112
|
lines.forEach((line, index) => {
|
|
3974
4113
|
const span = svgEl("tspan", {
|
|
3975
4114
|
x: node.width / 2,
|
|
3976
|
-
y: startY + index * lineHeight,
|
|
4115
|
+
y: visual < 0.999 ? formatNumber(startY + index * lineHeight) : startY + index * lineHeight,
|
|
3977
4116
|
"text-anchor": "middle",
|
|
3978
4117
|
"dominant-baseline": "middle"
|
|
3979
4118
|
});
|
|
@@ -4025,12 +4164,12 @@ var VdFlowchart = class {
|
|
|
4025
4164
|
textArea.setAttribute("data-field", "node-text");
|
|
4026
4165
|
const widthInput = createElement("input", { value: node.width, type: "number" });
|
|
4027
4166
|
widthInput.setAttribute("data-field", "node-width");
|
|
4028
|
-
widthInput.setAttribute("min", String(
|
|
4029
|
-
widthInput.setAttribute("max", String(
|
|
4167
|
+
widthInput.setAttribute("min", String(this.minNodeSize));
|
|
4168
|
+
widthInput.setAttribute("max", String(this.maxNodeSize));
|
|
4030
4169
|
const heightInput = createElement("input", { value: node.height, type: "number" });
|
|
4031
4170
|
heightInput.setAttribute("data-field", "node-height");
|
|
4032
|
-
heightInput.setAttribute("min", String(
|
|
4033
|
-
heightInput.setAttribute("max", String(
|
|
4171
|
+
heightInput.setAttribute("min", String(this.minNodeSize));
|
|
4172
|
+
heightInput.setAttribute("max", String(this.maxNodeSize));
|
|
4034
4173
|
this.selectionFields.appendChild(createField("Type", typeSelect));
|
|
4035
4174
|
if (isNodeTextEditable(node)) {
|
|
4036
4175
|
this.selectionFields.appendChild(createField("Text", textArea));
|
|
@@ -4344,8 +4483,8 @@ var VdFlowchart = class {
|
|
|
4344
4483
|
this.reconnectEdgeId = null;
|
|
4345
4484
|
const currentViewport = deepClone(this.documentData.viewport);
|
|
4346
4485
|
const previousSelection = this.selection;
|
|
4347
|
-
this.documentData = normalizeDocument(snapshot);
|
|
4348
|
-
this.documentData.viewport = normalizeViewport(currentViewport);
|
|
4486
|
+
this.documentData = normalizeDocument(snapshot, this.normalizationLimits());
|
|
4487
|
+
this.documentData.viewport = normalizeViewport(currentViewport, this.scaleBounds());
|
|
4349
4488
|
this.selection = this.resolvePreservedSelection(previousSelection, true);
|
|
4350
4489
|
this.render();
|
|
4351
4490
|
this.emitChange(reason);
|
|
@@ -4422,7 +4561,8 @@ var VdFlowchart = class {
|
|
|
4422
4561
|
y: partialNode.y == null ? fallbackY : partialNode.y
|
|
4423
4562
|
},
|
|
4424
4563
|
this.documentData.nodes.length,
|
|
4425
|
-
usedIds
|
|
4564
|
+
usedIds,
|
|
4565
|
+
this.nodeSizeBounds()
|
|
4426
4566
|
);
|
|
4427
4567
|
this.documentData.nodes.push(node);
|
|
4428
4568
|
this.select({ kind: "node", id: node.id });
|
|
@@ -4675,8 +4815,8 @@ var VdFlowchart = class {
|
|
|
4675
4815
|
node.x = patch.x == null ? node.x : formatNumber(toFiniteNumber(patch.x, node.x));
|
|
4676
4816
|
node.y = patch.y == null ? node.y : formatNumber(toFiniteNumber(patch.y, node.y));
|
|
4677
4817
|
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);
|
|
4818
|
+
node.width = clampNodeWidth(nextType, patch.width, node.width, this.nodeSizeBounds());
|
|
4819
|
+
node.height = clampNodeHeight(nextType, patch.height, node.height, this.nodeSizeBounds());
|
|
4680
4820
|
if (isPlainObject(patch.data)) {
|
|
4681
4821
|
node.data = deepClone(patch.data);
|
|
4682
4822
|
}
|
|
@@ -4865,8 +5005,45 @@ var VdFlowchart = class {
|
|
|
4865
5005
|
if (this.selection.kind === "node") return this.removeNode(this.selection.id);
|
|
4866
5006
|
return this.removeEdge(this.selection.id);
|
|
4867
5007
|
}
|
|
5008
|
+
nodeSizeBounds() {
|
|
5009
|
+
return { min: this.minNodeSize, max: this.maxNodeSize };
|
|
5010
|
+
}
|
|
5011
|
+
scaleBounds() {
|
|
5012
|
+
return { min: this.minScale, max: this.maxScale };
|
|
5013
|
+
}
|
|
5014
|
+
normalizationLimits() {
|
|
5015
|
+
return { scaleBounds: this.scaleBounds(), sizeBounds: this.nodeSizeBounds() };
|
|
5016
|
+
}
|
|
5017
|
+
hasWideCamera() {
|
|
5018
|
+
return this.minScale < DEFAULT_MIN_SCALE || this.maxScale > DEFAULT_MAX_SCALE;
|
|
5019
|
+
}
|
|
5020
|
+
syncGrid() {
|
|
5021
|
+
const wide = this.hasWideCamera();
|
|
5022
|
+
const extent = wide ? WIDE_WORLD_EXTENT : DEFAULT_WORLD_EXTENT;
|
|
5023
|
+
this.gridRect?.setAttribute("x", String(-extent / 2));
|
|
5024
|
+
this.gridRect?.setAttribute("y", String(-extent / 2));
|
|
5025
|
+
this.gridRect?.setAttribute("width", String(extent));
|
|
5026
|
+
this.gridRect?.setAttribute("height", String(extent));
|
|
5027
|
+
const pattern = this.svg?.querySelector("pattern");
|
|
5028
|
+
const path = pattern?.querySelector("path");
|
|
5029
|
+
if (!pattern || !path) return;
|
|
5030
|
+
let tile = this.gridSize;
|
|
5031
|
+
if (wide) {
|
|
5032
|
+
const scale = Math.max(this.minScale, this.documentData?.viewport?.scale || 1);
|
|
5033
|
+
while (tile * scale < 12 && tile < extent / 8) tile *= 4;
|
|
5034
|
+
while (tile * scale > 48 && tile > 1.5) tile /= 4;
|
|
5035
|
+
tile = formatNumber(tile);
|
|
5036
|
+
path.setAttribute("vector-effect", "non-scaling-stroke");
|
|
5037
|
+
} else {
|
|
5038
|
+
path.removeAttribute("vector-effect");
|
|
5039
|
+
}
|
|
5040
|
+
path.setAttribute("stroke-width", "1");
|
|
5041
|
+
pattern.setAttribute("width", String(tile));
|
|
5042
|
+
pattern.setAttribute("height", String(tile));
|
|
5043
|
+
path.setAttribute("d", `M ${tile} 0 L 0 0 0 ${tile}`);
|
|
5044
|
+
}
|
|
4868
5045
|
setViewport(viewport) {
|
|
4869
|
-
this.documentData.viewport = normalizeViewport(viewport);
|
|
5046
|
+
this.documentData.viewport = normalizeViewport(viewport, this.scaleBounds());
|
|
4870
5047
|
this.render({ inspector: false, json: true });
|
|
4871
5048
|
this.emitViewportChange("viewport:set");
|
|
4872
5049
|
return this;
|
|
@@ -4884,7 +5061,7 @@ var VdFlowchart = class {
|
|
|
4884
5061
|
return this;
|
|
4885
5062
|
}
|
|
4886
5063
|
resetView() {
|
|
4887
|
-
this.documentData.viewport = normalizeViewport({ x: 0, y: 0, scale: 1 });
|
|
5064
|
+
this.documentData.viewport = normalizeViewport({ x: 0, y: 0, scale: 1 }, this.scaleBounds());
|
|
4888
5065
|
this.render({ inspector: false, json: true });
|
|
4889
5066
|
this.emitViewportChange("viewport:reset");
|
|
4890
5067
|
return this;
|
|
@@ -4900,30 +5077,64 @@ var VdFlowchart = class {
|
|
|
4900
5077
|
const padding = 80;
|
|
4901
5078
|
const contentWidth = Math.max(1, bounds.right - bounds.left);
|
|
4902
5079
|
const contentHeight = Math.max(1, bounds.bottom - bounds.top);
|
|
4903
|
-
const scale =
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
5080
|
+
const scale = formatScale(
|
|
5081
|
+
clamp(
|
|
5082
|
+
Math.min((width - padding * 2) / contentWidth, (height - padding * 2) / contentHeight),
|
|
5083
|
+
this.minScale,
|
|
5084
|
+
this.maxScale
|
|
5085
|
+
)
|
|
4907
5086
|
);
|
|
4908
5087
|
this.documentData.viewport = {
|
|
4909
5088
|
x: formatNumber(width / 2 - (bounds.left + bounds.right) / 2 * scale),
|
|
4910
5089
|
y: formatNumber(height / 2 - (bounds.top + bounds.bottom) / 2 * scale),
|
|
4911
|
-
scale
|
|
5090
|
+
scale
|
|
4912
5091
|
};
|
|
4913
5092
|
this.render({ inspector: false, json: true });
|
|
4914
5093
|
this.emitViewportChange("viewport:fit");
|
|
4915
5094
|
return this;
|
|
4916
5095
|
}
|
|
5096
|
+
fitNode(nodeId, padding = 48) {
|
|
5097
|
+
const node = this.findNode(nodeId);
|
|
5098
|
+
if (!node || this.isNodeHidden(node.id)) return this;
|
|
5099
|
+
const pad = Math.max(0, toFiniteNumber(padding, 48));
|
|
5100
|
+
const width = this.canvasEl.clientWidth || 800;
|
|
5101
|
+
const height = this.canvasEl.clientHeight || 560;
|
|
5102
|
+
const availableWidth = Math.max(1, width - pad * 2);
|
|
5103
|
+
const availableHeight = Math.max(1, height - pad * 2);
|
|
5104
|
+
const scale = formatScale(
|
|
5105
|
+
clamp(
|
|
5106
|
+
Math.min(
|
|
5107
|
+
availableWidth / Math.max(1, node.width),
|
|
5108
|
+
availableHeight / Math.max(1, node.height)
|
|
5109
|
+
),
|
|
5110
|
+
this.minScale,
|
|
5111
|
+
this.maxScale
|
|
5112
|
+
)
|
|
5113
|
+
);
|
|
5114
|
+
const centerX = node.x + node.width / 2;
|
|
5115
|
+
const centerY = node.y + node.height / 2;
|
|
5116
|
+
this.documentData.viewport = {
|
|
5117
|
+
x: formatNumber(width / 2 - centerX * scale),
|
|
5118
|
+
y: formatNumber(height / 2 - centerY * scale),
|
|
5119
|
+
scale
|
|
5120
|
+
};
|
|
5121
|
+
this.render({ inspector: false, json: true });
|
|
5122
|
+
this.emitViewportChange("viewport:fit-node");
|
|
5123
|
+
return this;
|
|
5124
|
+
}
|
|
4917
5125
|
clear() {
|
|
4918
5126
|
this.stopTextEdit({ commit: false });
|
|
4919
5127
|
this.activeTool = null;
|
|
4920
5128
|
this.reconnectEdgeId = null;
|
|
4921
5129
|
this.clipboard = null;
|
|
4922
|
-
this.documentData = normalizeDocument(
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
5130
|
+
this.documentData = normalizeDocument(
|
|
5131
|
+
{
|
|
5132
|
+
nodes: [],
|
|
5133
|
+
edges: [],
|
|
5134
|
+
viewport: { x: 0, y: 0, scale: 1 }
|
|
5135
|
+
},
|
|
5136
|
+
this.normalizationLimits()
|
|
5137
|
+
);
|
|
4927
5138
|
this.selection = null;
|
|
4928
5139
|
this.render();
|
|
4929
5140
|
this.emitChange("clear");
|
|
@@ -4958,6 +5169,33 @@ var VdFlowchart = class {
|
|
|
4958
5169
|
if ("snapGuides" in options) this.snapGuides = options.snapGuides !== false;
|
|
4959
5170
|
if ("minimap" in options) this.setMinimapEnabled(options.minimap !== false);
|
|
4960
5171
|
if ("autoLayout" in options) this.autoLayout = Boolean(options.autoLayout);
|
|
5172
|
+
if ("minScale" in options || "maxScale" in options || "minNodeSize" in options || "maxNodeSize" in options) {
|
|
5173
|
+
const nextScale = normalizeBoundPair(
|
|
5174
|
+
"minScale" in options ? options.minScale : this.minScale,
|
|
5175
|
+
"maxScale" in options ? options.maxScale : this.maxScale,
|
|
5176
|
+
DEFAULT_MIN_SCALE,
|
|
5177
|
+
DEFAULT_MAX_SCALE
|
|
5178
|
+
);
|
|
5179
|
+
const nextSize = normalizeBoundPair(
|
|
5180
|
+
"minNodeSize" in options ? options.minNodeSize : this.minNodeSize,
|
|
5181
|
+
"maxNodeSize" in options ? options.maxNodeSize : this.maxNodeSize,
|
|
5182
|
+
DEFAULT_MIN_NODE_SIZE,
|
|
5183
|
+
DEFAULT_MAX_NODE_SIZE
|
|
5184
|
+
);
|
|
5185
|
+
this.minScale = nextScale.min;
|
|
5186
|
+
this.maxScale = nextScale.max;
|
|
5187
|
+
this.minNodeSize = nextSize.min;
|
|
5188
|
+
this.maxNodeSize = nextSize.max;
|
|
5189
|
+
const clamped = clamp(
|
|
5190
|
+
formatScale(this.documentData.viewport.scale),
|
|
5191
|
+
this.minScale,
|
|
5192
|
+
this.maxScale
|
|
5193
|
+
);
|
|
5194
|
+
if (clamped !== this.documentData.viewport.scale) {
|
|
5195
|
+
this.documentData.viewport.scale = clamped;
|
|
5196
|
+
this.pendingBoundsEvent = true;
|
|
5197
|
+
}
|
|
5198
|
+
}
|
|
4961
5199
|
this.syncCanvasLabel();
|
|
4962
5200
|
if (this.shortcutsHelpOpen) this.renderShortcutsHelp();
|
|
4963
5201
|
if ("history" in options && options.history !== false !== this.historyEnabled) {
|
|
@@ -4975,11 +5213,15 @@ var VdFlowchart = class {
|
|
|
4975
5213
|
}
|
|
4976
5214
|
}
|
|
4977
5215
|
this.render();
|
|
5216
|
+
if (this.pendingBoundsEvent) {
|
|
5217
|
+
this.pendingBoundsEvent = false;
|
|
5218
|
+
this.emitViewportChange("viewport:bounds");
|
|
5219
|
+
}
|
|
4978
5220
|
this.updateHistoryButtons();
|
|
4979
5221
|
return this;
|
|
4980
5222
|
}
|
|
4981
5223
|
load(data, options = {}) {
|
|
4982
|
-
const documentData = normalizeDocument(data);
|
|
5224
|
+
const documentData = normalizeDocument(data, this.normalizationLimits());
|
|
4983
5225
|
if (options.silent && JSON.stringify(documentData) === JSON.stringify(this.documentData)) {
|
|
4984
5226
|
return this;
|
|
4985
5227
|
}
|
|
@@ -5037,7 +5279,11 @@ var EDITOR_OPTION_KEYS = [
|
|
|
5037
5279
|
"keyboardShortcuts",
|
|
5038
5280
|
"snapGuides",
|
|
5039
5281
|
"minimap",
|
|
5040
|
-
"autoLayout"
|
|
5282
|
+
"autoLayout",
|
|
5283
|
+
"minScale",
|
|
5284
|
+
"maxScale",
|
|
5285
|
+
"minNodeSize",
|
|
5286
|
+
"maxNodeSize"
|
|
5041
5287
|
];
|
|
5042
5288
|
var VdFlowchart2 = defineComponent({
|
|
5043
5289
|
name: "VdFlowchart",
|
|
@@ -5063,13 +5309,29 @@ var VdFlowchart2 = defineComponent({
|
|
|
5063
5309
|
/** Show the overview minimap in the canvas corner (default true). */
|
|
5064
5310
|
minimap: { type: Boolean, default: true },
|
|
5065
5311
|
/** Re-run the current layout after keyboard or handle insertions (default false). */
|
|
5066
|
-
autoLayout: { type: Boolean, default: false }
|
|
5312
|
+
autoLayout: { type: Boolean, default: false },
|
|
5313
|
+
/** Lowest camera scale. Defaults to 0.35. Set it together with maxScale. */
|
|
5314
|
+
minScale: { type: Number, default: void 0 },
|
|
5315
|
+
/** Highest camera scale. Defaults to 3. Set it together with minScale. */
|
|
5316
|
+
maxScale: { type: Number, default: void 0 },
|
|
5317
|
+
/** Smallest resizable-node side. Defaults to 56. Set it together with maxNodeSize. */
|
|
5318
|
+
minNodeSize: { type: Number, default: void 0 },
|
|
5319
|
+
/** Largest resizable-node side. Defaults to 420. Set it together with minNodeSize. */
|
|
5320
|
+
maxNodeSize: { type: Number, default: void 0 }
|
|
5067
5321
|
},
|
|
5068
5322
|
emits: ["change", "select", "viewport", "connect", "ready"],
|
|
5069
5323
|
setup(props, { emit, expose }) {
|
|
5070
5324
|
const el = ref(null);
|
|
5071
5325
|
let instance = null;
|
|
5072
|
-
const editorOptions = () => Object.fromEntries(
|
|
5326
|
+
const editorOptions = () => Object.fromEntries(
|
|
5327
|
+
EDITOR_OPTION_KEYS.flatMap((key) => {
|
|
5328
|
+
const value = props[key];
|
|
5329
|
+
if ((key === "minScale" || key === "maxScale" || key === "minNodeSize" || key === "maxNodeSize") && value == null) {
|
|
5330
|
+
return [];
|
|
5331
|
+
}
|
|
5332
|
+
return [[key, value]];
|
|
5333
|
+
})
|
|
5334
|
+
);
|
|
5073
5335
|
const create = () => {
|
|
5074
5336
|
instance = new VdFlowchart({
|
|
5075
5337
|
element: el.value,
|
|
@@ -5111,7 +5373,8 @@ var VdFlowchart2 = defineComponent({
|
|
|
5111
5373
|
redo: () => instance?.redo(),
|
|
5112
5374
|
canUndo: () => Boolean(instance?.canUndo()),
|
|
5113
5375
|
canRedo: () => Boolean(instance?.canRedo()),
|
|
5114
|
-
layout: (mode, options) => instance?.layout(mode, options)
|
|
5376
|
+
layout: (mode, options) => instance?.layout(mode, options),
|
|
5377
|
+
fitNode: (nodeId, padding) => instance?.fitNode(nodeId, padding)
|
|
5115
5378
|
});
|
|
5116
5379
|
return () => h("div", { ref: el, class: "vd-flowchart" });
|
|
5117
5380
|
}
|