@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.cjs
CHANGED
|
@@ -398,11 +398,12 @@ function createToolbarButton({ action, label, icon, disabled = false }) {
|
|
|
398
398
|
return button;
|
|
399
399
|
}
|
|
400
400
|
var DEFAULT_GRID_SIZE = 24;
|
|
401
|
-
var
|
|
402
|
-
var
|
|
403
|
-
var
|
|
404
|
-
var
|
|
405
|
-
var
|
|
401
|
+
var DEFAULT_MIN_SCALE = 0.35;
|
|
402
|
+
var DEFAULT_MAX_SCALE = 3;
|
|
403
|
+
var DEFAULT_MIN_NODE_SIZE = 56;
|
|
404
|
+
var DEFAULT_MAX_NODE_SIZE = 420;
|
|
405
|
+
var DEFAULT_WORLD_EXTENT = 12e3;
|
|
406
|
+
var WIDE_WORLD_EXTENT = 24e4;
|
|
406
407
|
var MAX_NODES = 1e4;
|
|
407
408
|
var MAX_EDGES = 1e4;
|
|
408
409
|
var RESIZE_HANDLES = ["n", "ne", "e", "se", "s", "sw", "w", "nw"];
|
|
@@ -434,7 +435,7 @@ var EDGE_STROKE_PRESETS = [
|
|
|
434
435
|
{ id: "medium", label: "Medium", width: DEFAULT_EDGE_STROKE_WIDTH },
|
|
435
436
|
{ id: "bold", label: "Bold", width: 3.5 }
|
|
436
437
|
];
|
|
437
|
-
var VD_FLOWCHART_VERSION = "1.
|
|
438
|
+
var VD_FLOWCHART_VERSION = "1.5.0";
|
|
438
439
|
var FLOWCHART_DOCUMENT_VERSION = "1.3.0";
|
|
439
440
|
var FLOWCHART_NODE_TYPES = [
|
|
440
441
|
"rounded-rect",
|
|
@@ -575,6 +576,17 @@ function deepClone(value) {
|
|
|
575
576
|
function formatNumber(value) {
|
|
576
577
|
return Number(value.toFixed(2));
|
|
577
578
|
}
|
|
579
|
+
function formatScale(value) {
|
|
580
|
+
return Number(toFiniteNumber(value, 1).toFixed(4));
|
|
581
|
+
}
|
|
582
|
+
function normalizeBoundPair(min, max, fallbackMin, fallbackMax) {
|
|
583
|
+
const low = Number(min);
|
|
584
|
+
const high = Number(max);
|
|
585
|
+
if (!Number.isFinite(low) || !Number.isFinite(high) || low <= 0 || high <= 0 || low >= high) {
|
|
586
|
+
return { min: fallbackMin, max: fallbackMax };
|
|
587
|
+
}
|
|
588
|
+
return { min: low, max: high };
|
|
589
|
+
}
|
|
578
590
|
function sanitizeId(value) {
|
|
579
591
|
if (value == null) return "";
|
|
580
592
|
return String(value).trim();
|
|
@@ -632,21 +644,36 @@ function normalizeNodeType(type) {
|
|
|
632
644
|
function getNodeSpec(type) {
|
|
633
645
|
return DEFAULT_NODE_SPECS[normalizeNodeType(type)];
|
|
634
646
|
}
|
|
635
|
-
function
|
|
647
|
+
function nodeVisualScale(node) {
|
|
648
|
+
if (!node || node.type === "junction") return 1;
|
|
649
|
+
const spec = getNodeSpec(node.type);
|
|
650
|
+
const width = spec.width > 0 ? toFiniteNumber(node.width, spec.width) / spec.width : 1;
|
|
651
|
+
const height = spec.height > 0 ? toFiniteNumber(node.height, spec.height) / spec.height : 1;
|
|
652
|
+
return clamp(Math.min(1, width, height), 0.01, 1);
|
|
653
|
+
}
|
|
654
|
+
function edgeVisualScale(fromNode, toNode) {
|
|
655
|
+
if (fromNode && toNode) return Math.min(nodeVisualScale(fromNode), nodeVisualScale(toNode));
|
|
656
|
+
if (fromNode) return nodeVisualScale(fromNode);
|
|
657
|
+
if (toNode) return nodeVisualScale(toNode);
|
|
658
|
+
return 1;
|
|
659
|
+
}
|
|
660
|
+
function getNodeSizeBounds(type, sizeBounds) {
|
|
636
661
|
const spec = getNodeSpec(type);
|
|
662
|
+
const min = sizeBounds?.min ?? DEFAULT_MIN_NODE_SIZE;
|
|
663
|
+
const max = sizeBounds?.max ?? DEFAULT_MAX_NODE_SIZE;
|
|
637
664
|
return {
|
|
638
|
-
minWidth: spec.minWidth ??
|
|
639
|
-
minHeight: spec.minHeight ??
|
|
640
|
-
maxWidth: spec.maxWidth ??
|
|
641
|
-
maxHeight: spec.maxHeight ??
|
|
665
|
+
minWidth: spec.minWidth ?? min,
|
|
666
|
+
minHeight: spec.minHeight ?? min,
|
|
667
|
+
maxWidth: spec.maxWidth ?? max,
|
|
668
|
+
maxHeight: spec.maxHeight ?? max
|
|
642
669
|
};
|
|
643
670
|
}
|
|
644
|
-
function clampNodeWidth(type, width, fallback) {
|
|
645
|
-
const bounds = getNodeSizeBounds(type);
|
|
671
|
+
function clampNodeWidth(type, width, fallback, sizeBounds) {
|
|
672
|
+
const bounds = getNodeSizeBounds(type, sizeBounds);
|
|
646
673
|
return clamp(toFiniteNumber(width, fallback), bounds.minWidth, bounds.maxWidth);
|
|
647
674
|
}
|
|
648
|
-
function clampNodeHeight(type, height, fallback) {
|
|
649
|
-
const bounds = getNodeSizeBounds(type);
|
|
675
|
+
function clampNodeHeight(type, height, fallback, sizeBounds) {
|
|
676
|
+
const bounds = getNodeSizeBounds(type, sizeBounds);
|
|
650
677
|
return clamp(toFiniteNumber(height, fallback), bounds.minHeight, bounds.maxHeight);
|
|
651
678
|
}
|
|
652
679
|
function isNodeResizable(nodeOrType) {
|
|
@@ -670,14 +697,16 @@ function getStrokePresetWidth(presetId) {
|
|
|
670
697
|
const preset = EDGE_STROKE_PRESETS.find((item) => item.id === presetId);
|
|
671
698
|
return preset?.width ?? DEFAULT_EDGE_STROKE_WIDTH;
|
|
672
699
|
}
|
|
673
|
-
function normalizeViewport(viewport) {
|
|
700
|
+
function normalizeViewport(viewport, scaleBounds) {
|
|
701
|
+
const min = scaleBounds?.min ?? DEFAULT_MIN_SCALE;
|
|
702
|
+
const max = scaleBounds?.max ?? DEFAULT_MAX_SCALE;
|
|
674
703
|
return {
|
|
675
704
|
x: toFiniteNumber(viewport?.x, 0),
|
|
676
705
|
y: toFiniteNumber(viewport?.y, 0),
|
|
677
|
-
scale: clamp(toFiniteNumber(viewport?.scale, 1),
|
|
706
|
+
scale: clamp(formatScale(toFiniteNumber(viewport?.scale, 1)), min, max)
|
|
678
707
|
};
|
|
679
708
|
}
|
|
680
|
-
function normalizeNode(rawNode, index, usedIds) {
|
|
709
|
+
function normalizeNode(rawNode, index, usedIds, sizeBounds) {
|
|
681
710
|
const type = normalizeNodeType(rawNode?.type);
|
|
682
711
|
const spec = getNodeSpec(type);
|
|
683
712
|
const node = {
|
|
@@ -685,8 +714,8 @@ function normalizeNode(rawNode, index, usedIds) {
|
|
|
685
714
|
type,
|
|
686
715
|
x: toFiniteNumber(rawNode?.x, index * 28),
|
|
687
716
|
y: toFiniteNumber(rawNode?.y, index * 18),
|
|
688
|
-
width: clampNodeWidth(type, rawNode?.width, spec.width),
|
|
689
|
-
height: clampNodeHeight(type, rawNode?.height, spec.height),
|
|
717
|
+
width: clampNodeWidth(type, rawNode?.width, spec.width, sizeBounds),
|
|
718
|
+
height: clampNodeHeight(type, rawNode?.height, spec.height, sizeBounds),
|
|
690
719
|
text: rawNode?.text == null ? spec.text : String(rawNode.text),
|
|
691
720
|
data: isPlainObject(rawNode?.data) ? deepClone(rawNode.data) : {}
|
|
692
721
|
};
|
|
@@ -779,7 +808,7 @@ function isSupportedDocumentVersion(value) {
|
|
|
779
808
|
if (parsed.minor === max.minor && parsed.patch > max.patch) return false;
|
|
780
809
|
return true;
|
|
781
810
|
}
|
|
782
|
-
function normalizeDocument(input) {
|
|
811
|
+
function normalizeDocument(input, limits = {}) {
|
|
783
812
|
let source = input;
|
|
784
813
|
if (typeof source === "string") {
|
|
785
814
|
try {
|
|
@@ -802,13 +831,13 @@ function normalizeDocument(input) {
|
|
|
802
831
|
}
|
|
803
832
|
}
|
|
804
833
|
const usedNodeIds = /* @__PURE__ */ new Set();
|
|
805
|
-
const nodes = (Array.isArray(source.nodes) ? source.nodes : []).slice(0, MAX_NODES).map((node, index) => normalizeNode(node, index, usedNodeIds));
|
|
834
|
+
const nodes = (Array.isArray(source.nodes) ? source.nodes : []).slice(0, MAX_NODES).map((node, index) => normalizeNode(node, index, usedNodeIds, limits.sizeBounds));
|
|
806
835
|
const nodeIds = new Set(nodes.map((node) => node.id));
|
|
807
836
|
const usedEdgeIds = /* @__PURE__ */ new Set();
|
|
808
837
|
const edges = (Array.isArray(source.edges) ? source.edges : []).slice(0, MAX_EDGES).map((edge, index) => normalizeEdge(edge, index, nodeIds, usedEdgeIds)).filter(Boolean);
|
|
809
838
|
return {
|
|
810
839
|
version: FLOWCHART_DOCUMENT_VERSION,
|
|
811
|
-
viewport: normalizeViewport(source.viewport),
|
|
840
|
+
viewport: normalizeViewport(source.viewport, limits.scaleBounds),
|
|
812
841
|
nodes,
|
|
813
842
|
edges
|
|
814
843
|
};
|
|
@@ -1025,19 +1054,30 @@ function getPolylineLabelPoint(points) {
|
|
|
1025
1054
|
const last = points[points.length - 1] || { x: 0, y: 0 };
|
|
1026
1055
|
return { x: formatNumber(last.x), y: formatNumber(last.y) };
|
|
1027
1056
|
}
|
|
1028
|
-
function
|
|
1057
|
+
function routeMetrics(edgeScale = 1) {
|
|
1058
|
+
const scale = clamp(toFiniteNumber(edgeScale, 1), 0.01, 1);
|
|
1059
|
+
return {
|
|
1060
|
+
scale,
|
|
1061
|
+
minCurveArm: MIN_CURVE_ARM * scale,
|
|
1062
|
+
maxCurveArm: MAX_CURVE_ARM * scale,
|
|
1063
|
+
stub: ORTHOGONAL_STUB_LENGTH * scale,
|
|
1064
|
+
corner: ORTHOGONAL_CORNER_RADIUS * scale,
|
|
1065
|
+
clearance: ORTHOGONAL_CLEARANCE * scale
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
function curveControlPoint(point, port, target, curvature, metrics = routeMetrics(1)) {
|
|
1029
1069
|
const normal = getPortNormal(port);
|
|
1030
1070
|
const dx = target.x - point.x;
|
|
1031
1071
|
const dy = target.y - point.y;
|
|
1032
1072
|
const distance = Math.hypot(dx, dy) || 1;
|
|
1033
1073
|
const align = (normal.x * dx + normal.y * dy) / distance;
|
|
1034
1074
|
const ease = 0.55 + 0.45 * align;
|
|
1035
|
-
const arm = clamp(distance * curvature * ease,
|
|
1075
|
+
const arm = clamp(distance * curvature * ease, metrics.minCurveArm, metrics.maxCurveArm);
|
|
1036
1076
|
return { x: point.x + normal.x * arm, y: point.y + normal.y * arm };
|
|
1037
1077
|
}
|
|
1038
|
-
function buildCurvePath(fromPoint, toPoint, fromPort = "right", toPort = "left") {
|
|
1039
|
-
const controlA = curveControlPoint(fromPoint, fromPort, toPoint, EDGE_CURVATURE);
|
|
1040
|
-
const controlB = curveControlPoint(toPoint, toPort, fromPoint, EDGE_CURVATURE);
|
|
1078
|
+
function buildCurvePath(fromPoint, toPoint, fromPort = "right", toPort = "left", metrics = routeMetrics(1)) {
|
|
1079
|
+
const controlA = curveControlPoint(fromPoint, fromPort, toPoint, EDGE_CURVATURE, metrics);
|
|
1080
|
+
const controlB = curveControlPoint(toPoint, toPort, fromPoint, EDGE_CURVATURE, metrics);
|
|
1041
1081
|
const labelX = 0.125 * fromPoint.x + 0.375 * controlA.x + 0.375 * controlB.x + 0.125 * toPoint.x;
|
|
1042
1082
|
const labelY = 0.125 * fromPoint.y + 0.375 * controlA.y + 0.375 * controlB.y + 0.125 * toPoint.y;
|
|
1043
1083
|
return {
|
|
@@ -1099,11 +1139,11 @@ function getOrthogonalMidpoints(fromStub, toStub, fromPort, toPort) {
|
|
|
1099
1139
|
}
|
|
1100
1140
|
return [{ x: fromStub.x, y: toStub.y }];
|
|
1101
1141
|
}
|
|
1102
|
-
function getOrthogonalPoints(fromPoint, toPoint, fromPort, toPort, fromRect, toRect) {
|
|
1142
|
+
function getOrthogonalPoints(fromPoint, toPoint, fromPort, toPort, fromRect, toRect, metrics = routeMetrics(1)) {
|
|
1103
1143
|
const fromNormal = getPortNormal(fromPort);
|
|
1104
1144
|
const toNormal = getPortNormal(toPort);
|
|
1105
|
-
const fromStub = offsetPoint(fromPoint, fromNormal,
|
|
1106
|
-
const toStub = offsetPoint(toPoint, toNormal,
|
|
1145
|
+
const fromStub = offsetPoint(fromPoint, fromNormal, metrics.stub);
|
|
1146
|
+
const toStub = offsetPoint(toPoint, toNormal, metrics.stub);
|
|
1107
1147
|
const sourceHorizontal = isHorizontalPort(fromPort);
|
|
1108
1148
|
const targetHorizontal = isHorizontalPort(toPort);
|
|
1109
1149
|
const mids = [];
|
|
@@ -1143,13 +1183,13 @@ function getOrthogonalPoints(fromPoint, toPoint, fromPort, toPort, fromRect, toR
|
|
|
1143
1183
|
);
|
|
1144
1184
|
return points;
|
|
1145
1185
|
}
|
|
1146
|
-
function buildOrthogonalPath(fromPoint, toPoint, fromPort = "right", toPort = "left", fromRect = null, toRect = null) {
|
|
1186
|
+
function buildOrthogonalPath(fromPoint, toPoint, fromPort = "right", toPort = "left", fromRect = null, toRect = null, metrics = routeMetrics(1)) {
|
|
1147
1187
|
let points;
|
|
1148
1188
|
if (fromRect && toRect) {
|
|
1149
|
-
points = getOrthogonalPoints(fromPoint, toPoint, fromPort, toPort, fromRect, toRect);
|
|
1189
|
+
points = getOrthogonalPoints(fromPoint, toPoint, fromPort, toPort, fromRect, toRect, metrics);
|
|
1150
1190
|
} else {
|
|
1151
|
-
const fromStub = offsetPoint(fromPoint, getPortNormal(fromPort),
|
|
1152
|
-
const toStub = offsetPoint(toPoint, getPortNormal(toPort),
|
|
1191
|
+
const fromStub = offsetPoint(fromPoint, getPortNormal(fromPort), metrics.stub);
|
|
1192
|
+
const toStub = offsetPoint(toPoint, getPortNormal(toPort), metrics.stub);
|
|
1153
1193
|
points = [];
|
|
1154
1194
|
[
|
|
1155
1195
|
fromPoint,
|
|
@@ -1161,7 +1201,7 @@ function buildOrthogonalPath(fromPoint, toPoint, fromPort = "right", toPort = "l
|
|
|
1161
1201
|
}
|
|
1162
1202
|
const label = getPolylineLabelPoint(points);
|
|
1163
1203
|
return {
|
|
1164
|
-
d: buildRoundedPath(points,
|
|
1204
|
+
d: buildRoundedPath(points, metrics.corner),
|
|
1165
1205
|
labelX: label.x,
|
|
1166
1206
|
labelY: label.y
|
|
1167
1207
|
};
|
|
@@ -1172,6 +1212,7 @@ function buildEdgePath(edge, fromNode = null, toNode = null) {
|
|
|
1172
1212
|
const fromPoint = edge?.fromPoint || (fromNode ? getPortPosition(fromNode, fromPort) : null);
|
|
1173
1213
|
const toPoint = edge?.toPoint || (toNode ? getPortPosition(toNode, toPort) : null);
|
|
1174
1214
|
const route = normalizeEdgeRoute(edge?.route);
|
|
1215
|
+
const metrics = routeMetrics(edgeVisualScale(fromNode, toNode));
|
|
1175
1216
|
if (!fromPoint || !toPoint) {
|
|
1176
1217
|
return { d: "", labelX: 0, labelY: 0 };
|
|
1177
1218
|
}
|
|
@@ -1179,15 +1220,17 @@ function buildEdgePath(edge, fromNode = null, toNode = null) {
|
|
|
1179
1220
|
return buildStraightPath(fromPoint, toPoint);
|
|
1180
1221
|
}
|
|
1181
1222
|
if (route === "orthogonal") {
|
|
1182
|
-
const fromRect = !edge?.fromPoint && fromNode ? getNodeRectBounds(fromNode,
|
|
1183
|
-
const toRect = !edge?.toPoint && toNode ? getNodeRectBounds(toNode,
|
|
1184
|
-
return buildOrthogonalPath(fromPoint, toPoint, fromPort, toPort, fromRect, toRect);
|
|
1223
|
+
const fromRect = !edge?.fromPoint && fromNode ? getNodeRectBounds(fromNode, metrics.clearance) : null;
|
|
1224
|
+
const toRect = !edge?.toPoint && toNode ? getNodeRectBounds(toNode, metrics.clearance) : null;
|
|
1225
|
+
return buildOrthogonalPath(fromPoint, toPoint, fromPort, toPort, fromRect, toRect, metrics);
|
|
1185
1226
|
}
|
|
1186
|
-
return buildCurvePath(fromPoint, toPoint, fromPort, toPort);
|
|
1227
|
+
return buildCurvePath(fromPoint, toPoint, fromPort, toPort, metrics);
|
|
1187
1228
|
}
|
|
1188
|
-
function createArrowMarker(id, strokeWidth, reversed = false) {
|
|
1189
|
-
const
|
|
1190
|
-
const
|
|
1229
|
+
function createArrowMarker(id, strokeWidth, reversed = false, edgeScale = 1) {
|
|
1230
|
+
const scale = clamp(toFiniteNumber(edgeScale, 1), 0.01, 1);
|
|
1231
|
+
const effective = Math.max(0.02, strokeWidth * scale);
|
|
1232
|
+
const size = formatNumber(Math.max(9 * scale, effective * 4.4));
|
|
1233
|
+
const refInset = formatNumber(Math.max(1.5 * scale, effective * 0.85));
|
|
1191
1234
|
const marker = svgEl("marker", {
|
|
1192
1235
|
id,
|
|
1193
1236
|
markerWidth: size,
|
|
@@ -1205,9 +1248,11 @@ function createArrowMarker(id, strokeWidth, reversed = false) {
|
|
|
1205
1248
|
);
|
|
1206
1249
|
return marker;
|
|
1207
1250
|
}
|
|
1208
|
-
function createDotMarker(id, strokeWidth) {
|
|
1209
|
-
const
|
|
1210
|
-
const
|
|
1251
|
+
function createDotMarker(id, strokeWidth, edgeScale = 1) {
|
|
1252
|
+
const scale = clamp(toFiniteNumber(edgeScale, 1), 0.01, 1);
|
|
1253
|
+
const effective = Math.max(0.02, strokeWidth * scale);
|
|
1254
|
+
const size = formatNumber(Math.max(8 * scale, effective * 3.3));
|
|
1255
|
+
const radius = formatNumber(Math.max(2.75 * scale, effective * 1.45));
|
|
1211
1256
|
const marker = svgEl("marker", {
|
|
1212
1257
|
id,
|
|
1213
1258
|
markerWidth: size,
|
|
@@ -1464,7 +1509,23 @@ var VdFlowchart = class {
|
|
|
1464
1509
|
this.element = resolveElement(options.element || options.target);
|
|
1465
1510
|
this.readonly = Boolean(options.readonly);
|
|
1466
1511
|
this.gridSize = clamp(toFiniteNumber(options.gridSize, DEFAULT_GRID_SIZE), 12, 64);
|
|
1467
|
-
|
|
1512
|
+
const scaleBounds = normalizeBoundPair(
|
|
1513
|
+
options.minScale,
|
|
1514
|
+
options.maxScale,
|
|
1515
|
+
DEFAULT_MIN_SCALE,
|
|
1516
|
+
DEFAULT_MAX_SCALE
|
|
1517
|
+
);
|
|
1518
|
+
const sizeBounds = normalizeBoundPair(
|
|
1519
|
+
options.minNodeSize,
|
|
1520
|
+
options.maxNodeSize,
|
|
1521
|
+
DEFAULT_MIN_NODE_SIZE,
|
|
1522
|
+
DEFAULT_MAX_NODE_SIZE
|
|
1523
|
+
);
|
|
1524
|
+
this.minScale = scaleBounds.min;
|
|
1525
|
+
this.maxScale = scaleBounds.max;
|
|
1526
|
+
this.minNodeSize = sizeBounds.min;
|
|
1527
|
+
this.maxNodeSize = sizeBounds.max;
|
|
1528
|
+
this.documentData = normalizeDocument(options.data || {}, { scaleBounds, sizeBounds });
|
|
1468
1529
|
this.listeners = {};
|
|
1469
1530
|
this.selection = null;
|
|
1470
1531
|
this.selectedNodeIds = /* @__PURE__ */ new Set();
|
|
@@ -1692,10 +1753,10 @@ var VdFlowchart = class {
|
|
|
1692
1753
|
this.world = svgEl("g", { class: "vd-flowchart-world" });
|
|
1693
1754
|
this.gridRect = svgEl("rect", {
|
|
1694
1755
|
class: "vd-flowchart-grid",
|
|
1695
|
-
x: -
|
|
1696
|
-
y: -
|
|
1697
|
-
width:
|
|
1698
|
-
height:
|
|
1756
|
+
x: -DEFAULT_WORLD_EXTENT / 2,
|
|
1757
|
+
y: -DEFAULT_WORLD_EXTENT / 2,
|
|
1758
|
+
width: DEFAULT_WORLD_EXTENT,
|
|
1759
|
+
height: DEFAULT_WORLD_EXTENT,
|
|
1699
1760
|
fill: `url(#${this.gridPatternId})`
|
|
1700
1761
|
});
|
|
1701
1762
|
this.edgesLayer = svgEl("g", { class: "vd-flowchart-edges" });
|
|
@@ -2478,7 +2539,7 @@ var VdFlowchart = class {
|
|
|
2478
2539
|
const width = this.canvasEl.clientWidth || 800;
|
|
2479
2540
|
const height = this.canvasEl.clientHeight || 560;
|
|
2480
2541
|
this.scaleAround(
|
|
2481
|
-
clamp(scale,
|
|
2542
|
+
clamp(formatScale(scale), this.minScale, this.maxScale) / viewport.scale,
|
|
2482
2543
|
width / 2,
|
|
2483
2544
|
height / 2,
|
|
2484
2545
|
"viewport:zoom"
|
|
@@ -2513,7 +2574,8 @@ var VdFlowchart = class {
|
|
|
2513
2574
|
const node = normalizeNode(
|
|
2514
2575
|
{ ...data, id: void 0, x: data.x + 24, y: data.y + 24 },
|
|
2515
2576
|
this.documentData.nodes.length + index,
|
|
2516
|
-
usedNodeIds
|
|
2577
|
+
usedNodeIds,
|
|
2578
|
+
this.nodeSizeBounds()
|
|
2517
2579
|
);
|
|
2518
2580
|
idMap.set(data.id, node.id);
|
|
2519
2581
|
return node;
|
|
@@ -2545,7 +2607,8 @@ var VdFlowchart = class {
|
|
|
2545
2607
|
const node = normalizeNode(
|
|
2546
2608
|
{ ...data, id: void 0, collapsed: void 0, x: data.x + 24, y: data.y + 24 },
|
|
2547
2609
|
this.documentData.nodes.length,
|
|
2548
|
-
usedIds
|
|
2610
|
+
usedIds,
|
|
2611
|
+
this.nodeSizeBounds()
|
|
2549
2612
|
);
|
|
2550
2613
|
this.documentData.nodes.push(node);
|
|
2551
2614
|
this.select({ kind: "node", id: node.id });
|
|
@@ -3159,14 +3222,15 @@ var VdFlowchart = class {
|
|
|
3159
3222
|
if (this.activeTool === "arrow") return true;
|
|
3160
3223
|
return this.selection?.kind === "node" && this.selection.id === node.id && !this.hasMultiSelection();
|
|
3161
3224
|
}
|
|
3162
|
-
getMarkerId(markerType, position, strokeWidth) {
|
|
3225
|
+
getMarkerId(markerType, position, strokeWidth, edgeScale = 1) {
|
|
3163
3226
|
const width = normalizeEdgeStrokeWidth(strokeWidth);
|
|
3164
|
-
const
|
|
3227
|
+
const scale = clamp(formatNumber(Math.max(edgeScale, 0.01)), 0.01, 1);
|
|
3228
|
+
const key = `${markerType}:${position}:${width}:${scale}`;
|
|
3165
3229
|
if (this.markerIds.has(key)) {
|
|
3166
3230
|
return this.markerIds.get(key);
|
|
3167
3231
|
}
|
|
3168
3232
|
const markerId = nextId(`flowchart-${markerType}-${position}`);
|
|
3169
|
-
const marker = markerType === "dot" ? createDotMarker(markerId, width) : createArrowMarker(markerId, width, position === "start");
|
|
3233
|
+
const marker = markerType === "dot" ? createDotMarker(markerId, width, scale) : createArrowMarker(markerId, width, position === "start", scale);
|
|
3170
3234
|
this.markerDefs.appendChild(marker);
|
|
3171
3235
|
this.markerIds.set(key, markerId);
|
|
3172
3236
|
return markerId;
|
|
@@ -3174,8 +3238,15 @@ var VdFlowchart = class {
|
|
|
3174
3238
|
getEdgeStrokeWidth(edge) {
|
|
3175
3239
|
return normalizeEdgeStrokeWidth(edge?.strokeWidth);
|
|
3176
3240
|
}
|
|
3177
|
-
getEdgeHitStrokeWidth(edge) {
|
|
3178
|
-
|
|
3241
|
+
getEdgeHitStrokeWidth(edge, edgeScale = 1) {
|
|
3242
|
+
const visual = clamp(toFiniteNumber(edgeScale, 1), 0.01, 1);
|
|
3243
|
+
const base = this.getEdgeStrokeWidth(edge);
|
|
3244
|
+
const view = Math.max(this.documentData?.viewport?.scale || 1, 1e-4);
|
|
3245
|
+
const legacy = Math.max(EDGE_HIT_STROKE_MIN, base + 12);
|
|
3246
|
+
if (visual >= 0.999 && view >= DEFAULT_MIN_SCALE && view <= DEFAULT_MAX_SCALE) {
|
|
3247
|
+
return formatNumber(legacy);
|
|
3248
|
+
}
|
|
3249
|
+
return formatNumber(Math.max(base * visual, EDGE_HIT_STROKE_MIN / view));
|
|
3179
3250
|
}
|
|
3180
3251
|
findConnectionTarget(worldPoint, excludeNodeId, previousSnap = null, referencePoint = null) {
|
|
3181
3252
|
const snapPadding = CONNECTION_SNAP_PADDING / this.documentData.viewport.scale;
|
|
@@ -3232,17 +3303,37 @@ var VdFlowchart = class {
|
|
|
3232
3303
|
let width = original.width;
|
|
3233
3304
|
let height = original.height;
|
|
3234
3305
|
if (handle.includes("e")) {
|
|
3235
|
-
width = clampNodeWidth(
|
|
3306
|
+
width = clampNodeWidth(
|
|
3307
|
+
nextType,
|
|
3308
|
+
original.width + deltaX,
|
|
3309
|
+
original.width,
|
|
3310
|
+
this.nodeSizeBounds()
|
|
3311
|
+
);
|
|
3236
3312
|
}
|
|
3237
3313
|
if (handle.includes("s")) {
|
|
3238
|
-
height = clampNodeHeight(
|
|
3314
|
+
height = clampNodeHeight(
|
|
3315
|
+
nextType,
|
|
3316
|
+
original.height + deltaY,
|
|
3317
|
+
original.height,
|
|
3318
|
+
this.nodeSizeBounds()
|
|
3319
|
+
);
|
|
3239
3320
|
}
|
|
3240
3321
|
if (handle.includes("w")) {
|
|
3241
|
-
width = clampNodeWidth(
|
|
3322
|
+
width = clampNodeWidth(
|
|
3323
|
+
nextType,
|
|
3324
|
+
original.width - deltaX,
|
|
3325
|
+
original.width,
|
|
3326
|
+
this.nodeSizeBounds()
|
|
3327
|
+
);
|
|
3242
3328
|
x = original.x + original.width - width;
|
|
3243
3329
|
}
|
|
3244
3330
|
if (handle.includes("n")) {
|
|
3245
|
-
height = clampNodeHeight(
|
|
3331
|
+
height = clampNodeHeight(
|
|
3332
|
+
nextType,
|
|
3333
|
+
original.height - deltaY,
|
|
3334
|
+
original.height,
|
|
3335
|
+
this.nodeSizeBounds()
|
|
3336
|
+
);
|
|
3246
3337
|
y = original.y + original.height - height;
|
|
3247
3338
|
}
|
|
3248
3339
|
return {
|
|
@@ -3278,7 +3369,7 @@ var VdFlowchart = class {
|
|
|
3278
3369
|
scaleAround(factor, localX, localY, reason) {
|
|
3279
3370
|
const viewport = this.documentData.viewport;
|
|
3280
3371
|
const anchor = this.localToWorld(localX, localY);
|
|
3281
|
-
const nextScale = clamp(
|
|
3372
|
+
const nextScale = clamp(formatScale(viewport.scale * factor), this.minScale, this.maxScale);
|
|
3282
3373
|
if (nextScale === viewport.scale) return;
|
|
3283
3374
|
viewport.scale = nextScale;
|
|
3284
3375
|
viewport.x = formatNumber(localX - anchor.x * nextScale);
|
|
@@ -3461,19 +3552,20 @@ var VdFlowchart = class {
|
|
|
3461
3552
|
}
|
|
3462
3553
|
const viewport = this.documentData.viewport;
|
|
3463
3554
|
const scale = viewport.scale;
|
|
3555
|
+
const visual = nodeVisualScale(node);
|
|
3464
3556
|
const textarea = this.textEditor.textarea;
|
|
3465
3557
|
const width = node.width * scale;
|
|
3466
3558
|
const height = node.height * scale;
|
|
3467
3559
|
const metrics = getNodeFontMetrics(node);
|
|
3468
|
-
const lineHeight = metrics.lineHeight * scale;
|
|
3560
|
+
const lineHeight = metrics.lineHeight * visual * scale;
|
|
3469
3561
|
const lineCount = Math.max(1, textarea.value.split(/\r?\n/).length);
|
|
3470
|
-
const horizontalPadding = (node.type === "textbox" ? 16 : 12) * scale;
|
|
3471
|
-
const verticalPadding = node.type === "textbox" ? 12 * scale : Math.max(4, (height - lineCount * lineHeight) / 2);
|
|
3562
|
+
const horizontalPadding = (node.type === "textbox" ? 16 : 12) * visual * scale;
|
|
3563
|
+
const verticalPadding = node.type === "textbox" ? 12 * visual * scale : Math.max(4 * visual, (height - lineCount * lineHeight) / 2);
|
|
3472
3564
|
textarea.style.left = `${formatNumber(viewport.x + node.x * scale)}px`;
|
|
3473
3565
|
textarea.style.top = `${formatNumber(viewport.y + node.y * scale)}px`;
|
|
3474
3566
|
textarea.style.width = `${formatNumber(width)}px`;
|
|
3475
3567
|
textarea.style.height = `${formatNumber(height)}px`;
|
|
3476
|
-
textarea.style.fontSize = `${formatNumber(metrics.fontSize * scale)}px`;
|
|
3568
|
+
textarea.style.fontSize = `${formatNumber(metrics.fontSize * visual * scale)}px`;
|
|
3477
3569
|
textarea.style.lineHeight = `${formatNumber(lineHeight)}px`;
|
|
3478
3570
|
textarea.style.padding = `${formatNumber(verticalPadding)}px ${formatNumber(horizontalPadding)}px`;
|
|
3479
3571
|
}
|
|
@@ -3485,14 +3577,16 @@ var VdFlowchart = class {
|
|
|
3485
3577
|
this.stopTextEdit({ commit: false });
|
|
3486
3578
|
return;
|
|
3487
3579
|
}
|
|
3580
|
+
const visual = edgeVisualScale(fromNode, toNode);
|
|
3488
3581
|
const { labelX, labelY } = buildEdgePath(edge, fromNode, toNode);
|
|
3489
3582
|
const viewport = this.documentData.viewport;
|
|
3490
3583
|
const textarea = this.textEditor.textarea;
|
|
3491
|
-
const fontSize = Math.max(11, 13 * viewport.scale);
|
|
3492
|
-
const width = clamp(textarea.value.length * fontSize * 0.62 + 28, 96, 280);
|
|
3493
|
-
const height = fontSize * 1.4 + 12;
|
|
3584
|
+
const fontSize = Math.max(11 * visual, 13 * visual * viewport.scale);
|
|
3585
|
+
const width = clamp(textarea.value.length * fontSize * 0.62 + 28 * visual, 96 * visual, 280);
|
|
3586
|
+
const height = fontSize * 1.4 + 12 * visual;
|
|
3587
|
+
const labelOffset = 8 * visual;
|
|
3494
3588
|
textarea.style.left = `${formatNumber(viewport.x + labelX * viewport.scale - width / 2)}px`;
|
|
3495
|
-
textarea.style.top = `${formatNumber(viewport.y + (labelY -
|
|
3589
|
+
textarea.style.top = `${formatNumber(viewport.y + (labelY - labelOffset) * viewport.scale - height / 2)}px`;
|
|
3496
3590
|
textarea.style.width = `${formatNumber(width)}px`;
|
|
3497
3591
|
textarea.style.height = `${formatNumber(height)}px`;
|
|
3498
3592
|
textarea.style.fontSize = `${formatNumber(fontSize)}px`;
|
|
@@ -3501,6 +3595,7 @@ var VdFlowchart = class {
|
|
|
3501
3595
|
}
|
|
3502
3596
|
render(options = {}) {
|
|
3503
3597
|
if (this.destroyed) return;
|
|
3598
|
+
this.syncGrid();
|
|
3504
3599
|
const shouldRenderScene = options.scene !== false;
|
|
3505
3600
|
const shouldRenderInspector = options.inspector !== false;
|
|
3506
3601
|
const shouldRenderJson = options.json !== false;
|
|
@@ -3524,6 +3619,8 @@ var VdFlowchart = class {
|
|
|
3524
3619
|
clearChildren(this.previewLayer);
|
|
3525
3620
|
clearChildren(this.nodesLayer);
|
|
3526
3621
|
clearChildren(this.overlayLayer);
|
|
3622
|
+
clearChildren(this.markerDefs);
|
|
3623
|
+
this.markerIds = /* @__PURE__ */ new Map();
|
|
3527
3624
|
const hidden = this.getHiddenNodeIds();
|
|
3528
3625
|
this.sceneHidden = hidden;
|
|
3529
3626
|
this.sceneOutgoing = this.getOutgoingMap();
|
|
@@ -3587,17 +3684,18 @@ var VdFlowchart = class {
|
|
|
3587
3684
|
}
|
|
3588
3685
|
}
|
|
3589
3686
|
}
|
|
3590
|
-
applyEdgeMarkers(pathElement, edge) {
|
|
3687
|
+
applyEdgeMarkers(pathElement, edge, edgeScale = 1) {
|
|
3688
|
+
const width = this.getEdgeStrokeWidth(edge);
|
|
3591
3689
|
if (edge.startMarker && edge.startMarker !== "none") {
|
|
3592
3690
|
pathElement.setAttribute(
|
|
3593
3691
|
"marker-start",
|
|
3594
|
-
`url(#${this.getMarkerId(edge.startMarker, "start",
|
|
3692
|
+
`url(#${this.getMarkerId(edge.startMarker, "start", width, edgeScale)})`
|
|
3595
3693
|
);
|
|
3596
3694
|
}
|
|
3597
3695
|
if (edge.endMarker && edge.endMarker !== "none") {
|
|
3598
3696
|
pathElement.setAttribute(
|
|
3599
3697
|
"marker-end",
|
|
3600
|
-
`url(#${this.getMarkerId(edge.endMarker, "end",
|
|
3698
|
+
`url(#${this.getMarkerId(edge.endMarker, "end", width, edgeScale)})`
|
|
3601
3699
|
);
|
|
3602
3700
|
}
|
|
3603
3701
|
}
|
|
@@ -3605,6 +3703,7 @@ var VdFlowchart = class {
|
|
|
3605
3703
|
const fromNode = nodeMap.get(edge.from.nodeId);
|
|
3606
3704
|
const toNode = nodeMap.get(edge.to.nodeId);
|
|
3607
3705
|
if (!fromNode || !toNode) return null;
|
|
3706
|
+
const visual = edgeVisualScale(fromNode, toNode);
|
|
3608
3707
|
const edgePath = buildEdgePath(edge, fromNode, toNode);
|
|
3609
3708
|
const selected = this.selection?.kind === "edge" && this.selection.id === edge.id;
|
|
3610
3709
|
const inReconnectMode = this.reconnectEdgeId === edge.id;
|
|
@@ -3612,13 +3711,14 @@ var VdFlowchart = class {
|
|
|
3612
3711
|
class: `vd-flowchart-edge${selected ? " is-selected" : ""}${inReconnectMode ? " is-reconnecting" : ""}`,
|
|
3613
3712
|
"data-edge-id": edge.id
|
|
3614
3713
|
});
|
|
3615
|
-
const
|
|
3714
|
+
const baseStroke = this.getEdgeStrokeWidth(edge);
|
|
3715
|
+
const strokeWidth = visual < 0.999 ? formatNumber(Math.max(0.02, baseStroke * visual)) : baseStroke;
|
|
3616
3716
|
if (selected) {
|
|
3617
3717
|
group.appendChild(
|
|
3618
3718
|
svgEl("path", {
|
|
3619
3719
|
class: "vd-flowchart-edge-selection",
|
|
3620
3720
|
d: edgePath.d,
|
|
3621
|
-
"stroke-width": formatNumber(strokeWidth + 7)
|
|
3721
|
+
"stroke-width": visual < 0.999 ? formatNumber((baseStroke + 7) * visual) : formatNumber(strokeWidth + 7)
|
|
3622
3722
|
})
|
|
3623
3723
|
);
|
|
3624
3724
|
}
|
|
@@ -3628,23 +3728,28 @@ var VdFlowchart = class {
|
|
|
3628
3728
|
"stroke-width": strokeWidth,
|
|
3629
3729
|
"data-edge-id": edge.id
|
|
3630
3730
|
});
|
|
3631
|
-
this.applyEdgeMarkers(visiblePath, edge);
|
|
3731
|
+
this.applyEdgeMarkers(visiblePath, edge, visual);
|
|
3632
3732
|
const hitPath = svgEl("path", {
|
|
3633
3733
|
class: "vd-flowchart-edge-hit",
|
|
3634
3734
|
d: edgePath.d,
|
|
3635
|
-
"stroke-width": this.getEdgeHitStrokeWidth(edge),
|
|
3735
|
+
"stroke-width": this.getEdgeHitStrokeWidth(edge, visual),
|
|
3636
3736
|
"data-edge-id": edge.id
|
|
3637
3737
|
});
|
|
3638
3738
|
group.appendChild(visiblePath);
|
|
3639
3739
|
group.appendChild(hitPath);
|
|
3640
3740
|
if (edge.label && this.textEditor?.edgeId !== edge.id) {
|
|
3741
|
+
const labelOffset = visual < 0.999 ? formatNumber(8 * visual) : 8;
|
|
3641
3742
|
const label = svgEl("text", {
|
|
3642
3743
|
class: "vd-flowchart-edge-label",
|
|
3643
3744
|
x: edgePath.labelX,
|
|
3644
|
-
y: edgePath.labelY -
|
|
3745
|
+
y: edgePath.labelY - labelOffset,
|
|
3645
3746
|
"text-anchor": "middle",
|
|
3646
3747
|
"data-edge-id": edge.id
|
|
3647
3748
|
});
|
|
3749
|
+
if (visual < 0.999) {
|
|
3750
|
+
label.style.fontSize = `${formatNumber(Math.max(0.06, 12 * visual))}px`;
|
|
3751
|
+
label.style.strokeWidth = `${formatNumber(Math.max(0.03, 5 * visual))}px`;
|
|
3752
|
+
}
|
|
3648
3753
|
label.textContent = edge.label;
|
|
3649
3754
|
group.appendChild(label);
|
|
3650
3755
|
}
|
|
@@ -3683,18 +3788,26 @@ var VdFlowchart = class {
|
|
|
3683
3788
|
renderPreviewEdge(interaction) {
|
|
3684
3789
|
const fromPort = interaction.fromPort || interaction.source?.port || "right";
|
|
3685
3790
|
const toPort = interaction.toPort || interaction.target?.port || "left";
|
|
3686
|
-
const
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3791
|
+
const fromNode = interaction.source?.nodeId ? this.findNode(interaction.source.nodeId) : null;
|
|
3792
|
+
const toNode = interaction.target?.nodeId ? this.findNode(interaction.target.nodeId) : null;
|
|
3793
|
+
const visual = edgeVisualScale(fromNode, toNode);
|
|
3794
|
+
const edgePath = buildEdgePath(
|
|
3795
|
+
{
|
|
3796
|
+
strokeWidth: interaction.strokeWidth,
|
|
3797
|
+
route: interaction.route,
|
|
3798
|
+
fromPoint: interaction.fromPoint,
|
|
3799
|
+
toPoint: interaction.toPoint,
|
|
3800
|
+
from: { port: fromPort },
|
|
3801
|
+
to: { port: toPort }
|
|
3802
|
+
},
|
|
3803
|
+
fromNode,
|
|
3804
|
+
toNode
|
|
3805
|
+
);
|
|
3806
|
+
const baseStroke = this.getEdgeStrokeWidth(interaction);
|
|
3694
3807
|
return svgEl("path", {
|
|
3695
3808
|
class: "vd-flowchart-preview-path",
|
|
3696
3809
|
d: edgePath.d,
|
|
3697
|
-
"stroke-width":
|
|
3810
|
+
"stroke-width": visual < 0.999 ? formatNumber(Math.max(0.02, baseStroke * visual)) : baseStroke
|
|
3698
3811
|
});
|
|
3699
3812
|
}
|
|
3700
3813
|
renderNode(node) {
|
|
@@ -3709,17 +3822,21 @@ var VdFlowchart = class {
|
|
|
3709
3822
|
"data-node-id": node.id
|
|
3710
3823
|
});
|
|
3711
3824
|
const portsVisible = this.shouldShowNodePorts(node);
|
|
3825
|
+
const visual = nodeVisualScale(node);
|
|
3826
|
+
const corner = node.type === "rounded-rect" || node.type === "textbox" ? 16 : node.type === "junction" ? node.width / 2 : 6;
|
|
3712
3827
|
const hitbox = svgEl("rect", {
|
|
3713
3828
|
x: 0,
|
|
3714
3829
|
y: 0,
|
|
3715
3830
|
width: node.width,
|
|
3716
3831
|
height: node.height,
|
|
3717
|
-
rx:
|
|
3832
|
+
rx: visual < 0.999 && node.type !== "junction" ? formatNumber(corner * visual) : corner,
|
|
3718
3833
|
fill: "transparent",
|
|
3719
3834
|
"data-node-id": node.id
|
|
3720
3835
|
});
|
|
3721
3836
|
group.appendChild(hitbox);
|
|
3722
|
-
|
|
3837
|
+
const shape = this.renderNodeShape(node);
|
|
3838
|
+
this.applyScaledNodeShape(node, shape);
|
|
3839
|
+
group.appendChild(shape);
|
|
3723
3840
|
group.appendChild(this.renderNodeText(node));
|
|
3724
3841
|
const scale = this.documentData.viewport.scale || 1;
|
|
3725
3842
|
if (soloSelected && !this.readonly && SELECTION_BOX_TYPES.has(node.type)) {
|
|
@@ -3911,6 +4028,21 @@ var VdFlowchart = class {
|
|
|
3911
4028
|
});
|
|
3912
4029
|
return group;
|
|
3913
4030
|
}
|
|
4031
|
+
applyScaledNodeShape(node, shape) {
|
|
4032
|
+
const visual = nodeVisualScale(node);
|
|
4033
|
+
if (!shape || visual >= 0.999) return;
|
|
4034
|
+
const baseStroke = node.type === "junction" ? 1.25 : 1.5;
|
|
4035
|
+
shape.style.strokeWidth = String(formatNumber(Math.max(0.03, baseStroke * visual)));
|
|
4036
|
+
if (node.type === "textbox") {
|
|
4037
|
+
shape.style.strokeDasharray = `${formatNumber(8 * visual)} ${formatNumber(5 * visual)}`;
|
|
4038
|
+
}
|
|
4039
|
+
const baseRadius = node.type === "rounded-rect" ? 18 : node.type === "textbox" ? 14 : node.type === "rect" ? 2 : 0;
|
|
4040
|
+
if (baseRadius > 0) {
|
|
4041
|
+
const scaled = String(formatNumber(baseRadius * visual));
|
|
4042
|
+
shape.setAttribute("rx", scaled);
|
|
4043
|
+
shape.setAttribute("ry", scaled);
|
|
4044
|
+
}
|
|
4045
|
+
}
|
|
3914
4046
|
renderNodeShape(node) {
|
|
3915
4047
|
const baseClass = `vd-flowchart-node-shape vd-flowchart-node-shape--${node.type}`;
|
|
3916
4048
|
if (node.type === "rounded-rect") {
|
|
@@ -3987,21 +4119,28 @@ var VdFlowchart = class {
|
|
|
3987
4119
|
if (node.type === "junction" || !node.text) {
|
|
3988
4120
|
return textElement;
|
|
3989
4121
|
}
|
|
4122
|
+
const visual = nodeVisualScale(node);
|
|
4123
|
+
const metrics = getNodeFontMetrics(node);
|
|
4124
|
+
const lineHeight = visual < 0.999 ? Math.max(0.06, metrics.lineHeight * visual) : metrics.lineHeight;
|
|
4125
|
+
if (visual < 0.999) {
|
|
4126
|
+
textElement.style.fontSize = `${formatNumber(Math.max(0.05, metrics.fontSize * visual))}px`;
|
|
4127
|
+
}
|
|
4128
|
+
const equivalentWidth = visual < 0.999 ? node.width / visual : node.width;
|
|
3990
4129
|
if (node.type === "textbox") {
|
|
3991
|
-
const lines2 = wrapText(node.text, estimateChars(
|
|
3992
|
-
const
|
|
4130
|
+
const lines2 = wrapText(node.text, estimateChars(equivalentWidth - 32));
|
|
4131
|
+
const padX = visual < 0.999 ? formatNumber(16 * visual) : 16;
|
|
4132
|
+
const startY2 = visual < 0.999 ? formatNumber(28 * visual) : 28;
|
|
3993
4133
|
lines2.forEach((line, index) => {
|
|
3994
4134
|
const span = svgEl("tspan", {
|
|
3995
|
-
x:
|
|
3996
|
-
y:
|
|
4135
|
+
x: padX,
|
|
4136
|
+
y: visual < 0.999 ? formatNumber(startY2 + index * lineHeight) : startY2 + index * lineHeight
|
|
3997
4137
|
});
|
|
3998
4138
|
span.textContent = line;
|
|
3999
4139
|
textElement.appendChild(span);
|
|
4000
4140
|
});
|
|
4001
4141
|
return textElement;
|
|
4002
4142
|
}
|
|
4003
|
-
const lines = wrapText(node.text, estimateChars(
|
|
4004
|
-
const { lineHeight } = getNodeFontMetrics(node);
|
|
4143
|
+
const lines = wrapText(node.text, estimateChars(equivalentWidth));
|
|
4005
4144
|
const totalHeight = (lines.length - 1) * lineHeight;
|
|
4006
4145
|
const startY = node.height / 2 - totalHeight / 2;
|
|
4007
4146
|
textElement.setAttribute("text-anchor", "middle");
|
|
@@ -4009,7 +4148,7 @@ var VdFlowchart = class {
|
|
|
4009
4148
|
lines.forEach((line, index) => {
|
|
4010
4149
|
const span = svgEl("tspan", {
|
|
4011
4150
|
x: node.width / 2,
|
|
4012
|
-
y: startY + index * lineHeight,
|
|
4151
|
+
y: visual < 0.999 ? formatNumber(startY + index * lineHeight) : startY + index * lineHeight,
|
|
4013
4152
|
"text-anchor": "middle",
|
|
4014
4153
|
"dominant-baseline": "middle"
|
|
4015
4154
|
});
|
|
@@ -4061,12 +4200,12 @@ var VdFlowchart = class {
|
|
|
4061
4200
|
textArea.setAttribute("data-field", "node-text");
|
|
4062
4201
|
const widthInput = createElement("input", { value: node.width, type: "number" });
|
|
4063
4202
|
widthInput.setAttribute("data-field", "node-width");
|
|
4064
|
-
widthInput.setAttribute("min", String(
|
|
4065
|
-
widthInput.setAttribute("max", String(
|
|
4203
|
+
widthInput.setAttribute("min", String(this.minNodeSize));
|
|
4204
|
+
widthInput.setAttribute("max", String(this.maxNodeSize));
|
|
4066
4205
|
const heightInput = createElement("input", { value: node.height, type: "number" });
|
|
4067
4206
|
heightInput.setAttribute("data-field", "node-height");
|
|
4068
|
-
heightInput.setAttribute("min", String(
|
|
4069
|
-
heightInput.setAttribute("max", String(
|
|
4207
|
+
heightInput.setAttribute("min", String(this.minNodeSize));
|
|
4208
|
+
heightInput.setAttribute("max", String(this.maxNodeSize));
|
|
4070
4209
|
this.selectionFields.appendChild(createField("Type", typeSelect));
|
|
4071
4210
|
if (isNodeTextEditable(node)) {
|
|
4072
4211
|
this.selectionFields.appendChild(createField("Text", textArea));
|
|
@@ -4380,8 +4519,8 @@ var VdFlowchart = class {
|
|
|
4380
4519
|
this.reconnectEdgeId = null;
|
|
4381
4520
|
const currentViewport = deepClone(this.documentData.viewport);
|
|
4382
4521
|
const previousSelection = this.selection;
|
|
4383
|
-
this.documentData = normalizeDocument(snapshot);
|
|
4384
|
-
this.documentData.viewport = normalizeViewport(currentViewport);
|
|
4522
|
+
this.documentData = normalizeDocument(snapshot, this.normalizationLimits());
|
|
4523
|
+
this.documentData.viewport = normalizeViewport(currentViewport, this.scaleBounds());
|
|
4385
4524
|
this.selection = this.resolvePreservedSelection(previousSelection, true);
|
|
4386
4525
|
this.render();
|
|
4387
4526
|
this.emitChange(reason);
|
|
@@ -4458,7 +4597,8 @@ var VdFlowchart = class {
|
|
|
4458
4597
|
y: partialNode.y == null ? fallbackY : partialNode.y
|
|
4459
4598
|
},
|
|
4460
4599
|
this.documentData.nodes.length,
|
|
4461
|
-
usedIds
|
|
4600
|
+
usedIds,
|
|
4601
|
+
this.nodeSizeBounds()
|
|
4462
4602
|
);
|
|
4463
4603
|
this.documentData.nodes.push(node);
|
|
4464
4604
|
this.select({ kind: "node", id: node.id });
|
|
@@ -4711,8 +4851,8 @@ var VdFlowchart = class {
|
|
|
4711
4851
|
node.x = patch.x == null ? node.x : formatNumber(toFiniteNumber(patch.x, node.x));
|
|
4712
4852
|
node.y = patch.y == null ? node.y : formatNumber(toFiniteNumber(patch.y, node.y));
|
|
4713
4853
|
node.text = isNodeTextEditable(nextType) ? patch.text == null ? node.text : String(patch.text) : getNodeSpec(nextType).text;
|
|
4714
|
-
node.width = clampNodeWidth(nextType, patch.width, node.width);
|
|
4715
|
-
node.height = clampNodeHeight(nextType, patch.height, node.height);
|
|
4854
|
+
node.width = clampNodeWidth(nextType, patch.width, node.width, this.nodeSizeBounds());
|
|
4855
|
+
node.height = clampNodeHeight(nextType, patch.height, node.height, this.nodeSizeBounds());
|
|
4716
4856
|
if (isPlainObject(patch.data)) {
|
|
4717
4857
|
node.data = deepClone(patch.data);
|
|
4718
4858
|
}
|
|
@@ -4901,8 +5041,45 @@ var VdFlowchart = class {
|
|
|
4901
5041
|
if (this.selection.kind === "node") return this.removeNode(this.selection.id);
|
|
4902
5042
|
return this.removeEdge(this.selection.id);
|
|
4903
5043
|
}
|
|
5044
|
+
nodeSizeBounds() {
|
|
5045
|
+
return { min: this.minNodeSize, max: this.maxNodeSize };
|
|
5046
|
+
}
|
|
5047
|
+
scaleBounds() {
|
|
5048
|
+
return { min: this.minScale, max: this.maxScale };
|
|
5049
|
+
}
|
|
5050
|
+
normalizationLimits() {
|
|
5051
|
+
return { scaleBounds: this.scaleBounds(), sizeBounds: this.nodeSizeBounds() };
|
|
5052
|
+
}
|
|
5053
|
+
hasWideCamera() {
|
|
5054
|
+
return this.minScale < DEFAULT_MIN_SCALE || this.maxScale > DEFAULT_MAX_SCALE;
|
|
5055
|
+
}
|
|
5056
|
+
syncGrid() {
|
|
5057
|
+
const wide = this.hasWideCamera();
|
|
5058
|
+
const extent = wide ? WIDE_WORLD_EXTENT : DEFAULT_WORLD_EXTENT;
|
|
5059
|
+
this.gridRect?.setAttribute("x", String(-extent / 2));
|
|
5060
|
+
this.gridRect?.setAttribute("y", String(-extent / 2));
|
|
5061
|
+
this.gridRect?.setAttribute("width", String(extent));
|
|
5062
|
+
this.gridRect?.setAttribute("height", String(extent));
|
|
5063
|
+
const pattern = this.svg?.querySelector("pattern");
|
|
5064
|
+
const path = pattern?.querySelector("path");
|
|
5065
|
+
if (!pattern || !path) return;
|
|
5066
|
+
let tile = this.gridSize;
|
|
5067
|
+
if (wide) {
|
|
5068
|
+
const scale = Math.max(this.minScale, this.documentData?.viewport?.scale || 1);
|
|
5069
|
+
while (tile * scale < 12 && tile < extent / 8) tile *= 4;
|
|
5070
|
+
while (tile * scale > 48 && tile > 1.5) tile /= 4;
|
|
5071
|
+
tile = formatNumber(tile);
|
|
5072
|
+
path.setAttribute("vector-effect", "non-scaling-stroke");
|
|
5073
|
+
} else {
|
|
5074
|
+
path.removeAttribute("vector-effect");
|
|
5075
|
+
}
|
|
5076
|
+
path.setAttribute("stroke-width", "1");
|
|
5077
|
+
pattern.setAttribute("width", String(tile));
|
|
5078
|
+
pattern.setAttribute("height", String(tile));
|
|
5079
|
+
path.setAttribute("d", `M ${tile} 0 L 0 0 0 ${tile}`);
|
|
5080
|
+
}
|
|
4904
5081
|
setViewport(viewport) {
|
|
4905
|
-
this.documentData.viewport = normalizeViewport(viewport);
|
|
5082
|
+
this.documentData.viewport = normalizeViewport(viewport, this.scaleBounds());
|
|
4906
5083
|
this.render({ inspector: false, json: true });
|
|
4907
5084
|
this.emitViewportChange("viewport:set");
|
|
4908
5085
|
return this;
|
|
@@ -4920,7 +5097,7 @@ var VdFlowchart = class {
|
|
|
4920
5097
|
return this;
|
|
4921
5098
|
}
|
|
4922
5099
|
resetView() {
|
|
4923
|
-
this.documentData.viewport = normalizeViewport({ x: 0, y: 0, scale: 1 });
|
|
5100
|
+
this.documentData.viewport = normalizeViewport({ x: 0, y: 0, scale: 1 }, this.scaleBounds());
|
|
4924
5101
|
this.render({ inspector: false, json: true });
|
|
4925
5102
|
this.emitViewportChange("viewport:reset");
|
|
4926
5103
|
return this;
|
|
@@ -4936,30 +5113,64 @@ var VdFlowchart = class {
|
|
|
4936
5113
|
const padding = 80;
|
|
4937
5114
|
const contentWidth = Math.max(1, bounds.right - bounds.left);
|
|
4938
5115
|
const contentHeight = Math.max(1, bounds.bottom - bounds.top);
|
|
4939
|
-
const scale =
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
5116
|
+
const scale = formatScale(
|
|
5117
|
+
clamp(
|
|
5118
|
+
Math.min((width - padding * 2) / contentWidth, (height - padding * 2) / contentHeight),
|
|
5119
|
+
this.minScale,
|
|
5120
|
+
this.maxScale
|
|
5121
|
+
)
|
|
4943
5122
|
);
|
|
4944
5123
|
this.documentData.viewport = {
|
|
4945
5124
|
x: formatNumber(width / 2 - (bounds.left + bounds.right) / 2 * scale),
|
|
4946
5125
|
y: formatNumber(height / 2 - (bounds.top + bounds.bottom) / 2 * scale),
|
|
4947
|
-
scale
|
|
5126
|
+
scale
|
|
4948
5127
|
};
|
|
4949
5128
|
this.render({ inspector: false, json: true });
|
|
4950
5129
|
this.emitViewportChange("viewport:fit");
|
|
4951
5130
|
return this;
|
|
4952
5131
|
}
|
|
5132
|
+
fitNode(nodeId, padding = 48) {
|
|
5133
|
+
const node = this.findNode(nodeId);
|
|
5134
|
+
if (!node || this.isNodeHidden(node.id)) return this;
|
|
5135
|
+
const pad = Math.max(0, toFiniteNumber(padding, 48));
|
|
5136
|
+
const width = this.canvasEl.clientWidth || 800;
|
|
5137
|
+
const height = this.canvasEl.clientHeight || 560;
|
|
5138
|
+
const availableWidth = Math.max(1, width - pad * 2);
|
|
5139
|
+
const availableHeight = Math.max(1, height - pad * 2);
|
|
5140
|
+
const scale = formatScale(
|
|
5141
|
+
clamp(
|
|
5142
|
+
Math.min(
|
|
5143
|
+
availableWidth / Math.max(1, node.width),
|
|
5144
|
+
availableHeight / Math.max(1, node.height)
|
|
5145
|
+
),
|
|
5146
|
+
this.minScale,
|
|
5147
|
+
this.maxScale
|
|
5148
|
+
)
|
|
5149
|
+
);
|
|
5150
|
+
const centerX = node.x + node.width / 2;
|
|
5151
|
+
const centerY = node.y + node.height / 2;
|
|
5152
|
+
this.documentData.viewport = {
|
|
5153
|
+
x: formatNumber(width / 2 - centerX * scale),
|
|
5154
|
+
y: formatNumber(height / 2 - centerY * scale),
|
|
5155
|
+
scale
|
|
5156
|
+
};
|
|
5157
|
+
this.render({ inspector: false, json: true });
|
|
5158
|
+
this.emitViewportChange("viewport:fit-node");
|
|
5159
|
+
return this;
|
|
5160
|
+
}
|
|
4953
5161
|
clear() {
|
|
4954
5162
|
this.stopTextEdit({ commit: false });
|
|
4955
5163
|
this.activeTool = null;
|
|
4956
5164
|
this.reconnectEdgeId = null;
|
|
4957
5165
|
this.clipboard = null;
|
|
4958
|
-
this.documentData = normalizeDocument(
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
5166
|
+
this.documentData = normalizeDocument(
|
|
5167
|
+
{
|
|
5168
|
+
nodes: [],
|
|
5169
|
+
edges: [],
|
|
5170
|
+
viewport: { x: 0, y: 0, scale: 1 }
|
|
5171
|
+
},
|
|
5172
|
+
this.normalizationLimits()
|
|
5173
|
+
);
|
|
4963
5174
|
this.selection = null;
|
|
4964
5175
|
this.render();
|
|
4965
5176
|
this.emitChange("clear");
|
|
@@ -4994,6 +5205,33 @@ var VdFlowchart = class {
|
|
|
4994
5205
|
if ("snapGuides" in options) this.snapGuides = options.snapGuides !== false;
|
|
4995
5206
|
if ("minimap" in options) this.setMinimapEnabled(options.minimap !== false);
|
|
4996
5207
|
if ("autoLayout" in options) this.autoLayout = Boolean(options.autoLayout);
|
|
5208
|
+
if ("minScale" in options || "maxScale" in options || "minNodeSize" in options || "maxNodeSize" in options) {
|
|
5209
|
+
const nextScale = normalizeBoundPair(
|
|
5210
|
+
"minScale" in options ? options.minScale : this.minScale,
|
|
5211
|
+
"maxScale" in options ? options.maxScale : this.maxScale,
|
|
5212
|
+
DEFAULT_MIN_SCALE,
|
|
5213
|
+
DEFAULT_MAX_SCALE
|
|
5214
|
+
);
|
|
5215
|
+
const nextSize = normalizeBoundPair(
|
|
5216
|
+
"minNodeSize" in options ? options.minNodeSize : this.minNodeSize,
|
|
5217
|
+
"maxNodeSize" in options ? options.maxNodeSize : this.maxNodeSize,
|
|
5218
|
+
DEFAULT_MIN_NODE_SIZE,
|
|
5219
|
+
DEFAULT_MAX_NODE_SIZE
|
|
5220
|
+
);
|
|
5221
|
+
this.minScale = nextScale.min;
|
|
5222
|
+
this.maxScale = nextScale.max;
|
|
5223
|
+
this.minNodeSize = nextSize.min;
|
|
5224
|
+
this.maxNodeSize = nextSize.max;
|
|
5225
|
+
const clamped = clamp(
|
|
5226
|
+
formatScale(this.documentData.viewport.scale),
|
|
5227
|
+
this.minScale,
|
|
5228
|
+
this.maxScale
|
|
5229
|
+
);
|
|
5230
|
+
if (clamped !== this.documentData.viewport.scale) {
|
|
5231
|
+
this.documentData.viewport.scale = clamped;
|
|
5232
|
+
this.pendingBoundsEvent = true;
|
|
5233
|
+
}
|
|
5234
|
+
}
|
|
4997
5235
|
this.syncCanvasLabel();
|
|
4998
5236
|
if (this.shortcutsHelpOpen) this.renderShortcutsHelp();
|
|
4999
5237
|
if ("history" in options && options.history !== false !== this.historyEnabled) {
|
|
@@ -5011,11 +5249,15 @@ var VdFlowchart = class {
|
|
|
5011
5249
|
}
|
|
5012
5250
|
}
|
|
5013
5251
|
this.render();
|
|
5252
|
+
if (this.pendingBoundsEvent) {
|
|
5253
|
+
this.pendingBoundsEvent = false;
|
|
5254
|
+
this.emitViewportChange("viewport:bounds");
|
|
5255
|
+
}
|
|
5014
5256
|
this.updateHistoryButtons();
|
|
5015
5257
|
return this;
|
|
5016
5258
|
}
|
|
5017
5259
|
load(data, options = {}) {
|
|
5018
|
-
const documentData = normalizeDocument(data);
|
|
5260
|
+
const documentData = normalizeDocument(data, this.normalizationLimits());
|
|
5019
5261
|
if (options.silent && JSON.stringify(documentData) === JSON.stringify(this.documentData)) {
|
|
5020
5262
|
return this;
|
|
5021
5263
|
}
|
|
@@ -5073,7 +5315,11 @@ var EDITOR_OPTION_KEYS = [
|
|
|
5073
5315
|
"keyboardShortcuts",
|
|
5074
5316
|
"snapGuides",
|
|
5075
5317
|
"minimap",
|
|
5076
|
-
"autoLayout"
|
|
5318
|
+
"autoLayout",
|
|
5319
|
+
"minScale",
|
|
5320
|
+
"maxScale",
|
|
5321
|
+
"minNodeSize",
|
|
5322
|
+
"maxNodeSize"
|
|
5077
5323
|
];
|
|
5078
5324
|
var VdFlowchart2 = (0, import_vue.defineComponent)({
|
|
5079
5325
|
name: "VdFlowchart",
|
|
@@ -5099,13 +5345,29 @@ var VdFlowchart2 = (0, import_vue.defineComponent)({
|
|
|
5099
5345
|
/** Show the overview minimap in the canvas corner (default true). */
|
|
5100
5346
|
minimap: { type: Boolean, default: true },
|
|
5101
5347
|
/** Re-run the current layout after keyboard or handle insertions (default false). */
|
|
5102
|
-
autoLayout: { type: Boolean, default: false }
|
|
5348
|
+
autoLayout: { type: Boolean, default: false },
|
|
5349
|
+
/** Lowest camera scale. Defaults to 0.35. Set it together with maxScale. */
|
|
5350
|
+
minScale: { type: Number, default: void 0 },
|
|
5351
|
+
/** Highest camera scale. Defaults to 3. Set it together with minScale. */
|
|
5352
|
+
maxScale: { type: Number, default: void 0 },
|
|
5353
|
+
/** Smallest resizable-node side. Defaults to 56. Set it together with maxNodeSize. */
|
|
5354
|
+
minNodeSize: { type: Number, default: void 0 },
|
|
5355
|
+
/** Largest resizable-node side. Defaults to 420. Set it together with minNodeSize. */
|
|
5356
|
+
maxNodeSize: { type: Number, default: void 0 }
|
|
5103
5357
|
},
|
|
5104
5358
|
emits: ["change", "select", "viewport", "connect", "ready"],
|
|
5105
5359
|
setup(props, { emit, expose }) {
|
|
5106
5360
|
const el = (0, import_vue.ref)(null);
|
|
5107
5361
|
let instance = null;
|
|
5108
|
-
const editorOptions = () => Object.fromEntries(
|
|
5362
|
+
const editorOptions = () => Object.fromEntries(
|
|
5363
|
+
EDITOR_OPTION_KEYS.flatMap((key) => {
|
|
5364
|
+
const value = props[key];
|
|
5365
|
+
if ((key === "minScale" || key === "maxScale" || key === "minNodeSize" || key === "maxNodeSize") && value == null) {
|
|
5366
|
+
return [];
|
|
5367
|
+
}
|
|
5368
|
+
return [[key, value]];
|
|
5369
|
+
})
|
|
5370
|
+
);
|
|
5109
5371
|
const create = () => {
|
|
5110
5372
|
instance = new VdFlowchart({
|
|
5111
5373
|
element: el.value,
|
|
@@ -5147,7 +5409,8 @@ var VdFlowchart2 = (0, import_vue.defineComponent)({
|
|
|
5147
5409
|
redo: () => instance?.redo(),
|
|
5148
5410
|
canUndo: () => Boolean(instance?.canUndo()),
|
|
5149
5411
|
canRedo: () => Boolean(instance?.canRedo()),
|
|
5150
|
-
layout: (mode, options) => instance?.layout(mode, options)
|
|
5412
|
+
layout: (mode, options) => instance?.layout(mode, options),
|
|
5413
|
+
fitNode: (nodeId, padding) => instance?.fitNode(nodeId, padding)
|
|
5151
5414
|
});
|
|
5152
5415
|
return () => (0, import_vue.h)("div", { ref: el, class: "vd-flowchart" });
|
|
5153
5416
|
}
|