@ngroznykh/papirus 0.9.3 → 0.9.4
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/dist/constants.d.ts +4 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/ConnectionManager.d.ts +3 -0
- package/dist/core/ConnectionManager.d.ts.map +1 -1
- package/dist/core/DiagramRenderer.d.ts +2 -1
- package/dist/core/DiagramRenderer.d.ts.map +1 -1
- package/dist/core/InteractionManager.d.ts.map +1 -1
- package/dist/elements/Edge.d.ts.map +1 -1
- package/dist/elements/Node.d.ts +29 -4
- package/dist/elements/Node.d.ts.map +1 -1
- package/dist/elements/TextLabel.d.ts.map +1 -1
- package/dist/elements/composite/CContainer.d.ts +14 -0
- package/dist/elements/composite/CContainer.d.ts.map +1 -1
- package/dist/elements/composite/CompositeNode.d.ts +4 -0
- package/dist/elements/composite/CompositeNode.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/papirus.js +456 -168
- package/dist/papirus.js.map +1 -1
- package/dist/types.d.ts +6 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/Serializer.d.ts.map +1 -1
- package/dist/utils/SvgExporter.d.ts.map +1 -1
- package/dist/utils/contentBounds.d.ts +1 -0
- package/dist/utils/contentBounds.d.ts.map +1 -1
- package/dist/utils/edgePath.d.ts +3 -1
- package/dist/utils/edgePath.d.ts.map +1 -1
- package/dist/utils/labelPlacement.d.ts +13 -0
- package/dist/utils/labelPlacement.d.ts.map +1 -0
- package/dist/utils/style.d.ts +2 -0
- package/dist/utils/style.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/papirus.js
CHANGED
|
@@ -1456,7 +1456,7 @@ function getContentBounds({ nodes, edges, groups, includeInvisible = false }) {
|
|
|
1456
1456
|
let hasContent = false;
|
|
1457
1457
|
const consider = (source) => {
|
|
1458
1458
|
if (!includeInvisible && !source.visible) return;
|
|
1459
|
-
const bounds = source.getBounds();
|
|
1459
|
+
const bounds = source.getVisualBounds?.() ?? source.getBounds();
|
|
1460
1460
|
minX = Math.min(minX, bounds.x);
|
|
1461
1461
|
minY = Math.min(minY, bounds.y);
|
|
1462
1462
|
maxX = Math.max(maxX, bounds.x + bounds.width);
|
|
@@ -1671,6 +1671,133 @@ var NavigationManager = class extends EventEmitter {
|
|
|
1671
1671
|
}
|
|
1672
1672
|
};
|
|
1673
1673
|
//#endregion
|
|
1674
|
+
//#region src/utils/edgePath.ts
|
|
1675
|
+
/**
|
|
1676
|
+
* Point and tangent angle at a normalized position along an edge path (0..1 by length).
|
|
1677
|
+
*/
|
|
1678
|
+
function getPathPointAt(path, type, position) {
|
|
1679
|
+
if (type !== "bezier" || path.length < 4) return getPointAlongPolyline(path, position);
|
|
1680
|
+
const samples = [];
|
|
1681
|
+
const steps = 20;
|
|
1682
|
+
for (let i = 1; i + 2 < path.length; i += 3) {
|
|
1683
|
+
const start = path[i - 1];
|
|
1684
|
+
const control1 = path[i];
|
|
1685
|
+
const control2 = path[i + 1];
|
|
1686
|
+
const end = path[i + 2];
|
|
1687
|
+
for (let sample = 0; sample <= steps; sample++) {
|
|
1688
|
+
const t = sample / steps;
|
|
1689
|
+
if (samples.length > 0 && t === 0) continue;
|
|
1690
|
+
samples.push(bezierPoint(start, control1, control2, end, t));
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
return getPointAlongPolyline(samples, position);
|
|
1694
|
+
}
|
|
1695
|
+
function getPointAlongPolyline(path, position) {
|
|
1696
|
+
if (path.length === 0) return {
|
|
1697
|
+
point: {
|
|
1698
|
+
x: 0,
|
|
1699
|
+
y: 0
|
|
1700
|
+
},
|
|
1701
|
+
angle: 0
|
|
1702
|
+
};
|
|
1703
|
+
if (path.length === 1) return {
|
|
1704
|
+
point: path[0],
|
|
1705
|
+
angle: 0
|
|
1706
|
+
};
|
|
1707
|
+
let totalLength = 0;
|
|
1708
|
+
const segments = [];
|
|
1709
|
+
for (let i = 1; i < path.length; i++) {
|
|
1710
|
+
const start = path[i - 1];
|
|
1711
|
+
const end = path[i];
|
|
1712
|
+
const length = Math.hypot(end.x - start.x, end.y - start.y);
|
|
1713
|
+
segments.push({
|
|
1714
|
+
start,
|
|
1715
|
+
end,
|
|
1716
|
+
length
|
|
1717
|
+
});
|
|
1718
|
+
totalLength += length;
|
|
1719
|
+
}
|
|
1720
|
+
const targetLength = totalLength * Math.max(0, Math.min(1, position));
|
|
1721
|
+
let accumulated = 0;
|
|
1722
|
+
for (const segment of segments) {
|
|
1723
|
+
if (accumulated + segment.length >= targetLength) {
|
|
1724
|
+
const segmentPosition = segment.length > 0 ? (targetLength - accumulated) / segment.length : 0;
|
|
1725
|
+
return {
|
|
1726
|
+
point: {
|
|
1727
|
+
x: segment.start.x + segmentPosition * (segment.end.x - segment.start.x),
|
|
1728
|
+
y: segment.start.y + segmentPosition * (segment.end.y - segment.start.y)
|
|
1729
|
+
},
|
|
1730
|
+
angle: Math.atan2(segment.end.y - segment.start.y, segment.end.x - segment.start.x)
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
accumulated += segment.length;
|
|
1734
|
+
}
|
|
1735
|
+
const lastSegment = segments[segments.length - 1];
|
|
1736
|
+
return {
|
|
1737
|
+
point: lastSegment.end,
|
|
1738
|
+
angle: Math.atan2(lastSegment.end.y - lastSegment.start.y, lastSegment.end.x - lastSegment.start.x)
|
|
1739
|
+
};
|
|
1740
|
+
}
|
|
1741
|
+
/** Map tangent angle to a cardinal direction used by polyline routing. */
|
|
1742
|
+
function directionFromAngle(angle) {
|
|
1743
|
+
const twoPi = Math.PI * 2;
|
|
1744
|
+
const a = (angle % twoPi + twoPi) % twoPi;
|
|
1745
|
+
if (a >= Math.PI * .25 && a < Math.PI * .75) return "bottom";
|
|
1746
|
+
if (a >= Math.PI * .75 && a < Math.PI * 1.25) return "left";
|
|
1747
|
+
if (a >= Math.PI * 1.25 && a < Math.PI * 1.75) return "top";
|
|
1748
|
+
return "right";
|
|
1749
|
+
}
|
|
1750
|
+
function closestPointOnPolyline(polyline, point) {
|
|
1751
|
+
if (polyline.length < 2) return null;
|
|
1752
|
+
let totalLength = 0;
|
|
1753
|
+
for (let i = 1; i < polyline.length; i++) {
|
|
1754
|
+
const start = polyline[i - 1];
|
|
1755
|
+
const end = polyline[i];
|
|
1756
|
+
totalLength += Math.hypot(end.x - start.x, end.y - start.y);
|
|
1757
|
+
}
|
|
1758
|
+
let bestPoint = polyline[0];
|
|
1759
|
+
let bestParam = 0;
|
|
1760
|
+
let bestDist = Infinity;
|
|
1761
|
+
let accumulated = 0;
|
|
1762
|
+
for (let i = 1; i < polyline.length; i++) {
|
|
1763
|
+
const start = polyline[i - 1];
|
|
1764
|
+
const end = polyline[i];
|
|
1765
|
+
const dx = end.x - start.x;
|
|
1766
|
+
const dy = end.y - start.y;
|
|
1767
|
+
const lengthSq = dx * dx + dy * dy;
|
|
1768
|
+
const segLen = Math.sqrt(lengthSq);
|
|
1769
|
+
const t = lengthSq === 0 ? 0 : Math.max(0, Math.min(1, ((point.x - start.x) * dx + (point.y - start.y) * dy) / lengthSq));
|
|
1770
|
+
const proj = {
|
|
1771
|
+
x: start.x + t * dx,
|
|
1772
|
+
y: start.y + t * dy
|
|
1773
|
+
};
|
|
1774
|
+
const dist = Math.hypot(point.x - proj.x, point.y - proj.y);
|
|
1775
|
+
if (dist < bestDist) {
|
|
1776
|
+
bestDist = dist;
|
|
1777
|
+
bestPoint = proj;
|
|
1778
|
+
bestParam = totalLength > 0 ? (accumulated + t * segLen) / totalLength : 0;
|
|
1779
|
+
}
|
|
1780
|
+
accumulated += segLen;
|
|
1781
|
+
}
|
|
1782
|
+
return {
|
|
1783
|
+
point: bestPoint,
|
|
1784
|
+
pathParam: bestParam,
|
|
1785
|
+
distance: bestDist
|
|
1786
|
+
};
|
|
1787
|
+
}
|
|
1788
|
+
/**
|
|
1789
|
+
* Nearest point on an edge path to `point`.
|
|
1790
|
+
* Polylines project onto real segments so a click on a long orthogonal stroke
|
|
1791
|
+
* is not lost between length samples. Bezier paths are densified, then projected.
|
|
1792
|
+
*/
|
|
1793
|
+
function getClosestPointOnPath(path, type, point, samples = 48) {
|
|
1794
|
+
if (path.length < 2) return null;
|
|
1795
|
+
if (type !== "bezier" || path.length < 4) return closestPointOnPolyline(path, point);
|
|
1796
|
+
const densified = [];
|
|
1797
|
+
for (let i = 0; i <= samples; i++) densified.push(getPathPointAt(path, type, i / samples).point);
|
|
1798
|
+
return closestPointOnPolyline(densified, point);
|
|
1799
|
+
}
|
|
1800
|
+
//#endregion
|
|
1674
1801
|
//#region src/utils/direction.ts
|
|
1675
1802
|
/**
|
|
1676
1803
|
* Map outline param [0,1) to cardinal direction.
|
|
@@ -2048,7 +2175,18 @@ var ConnectionManager = class extends EventEmitter {
|
|
|
2048
2175
|
let snappedPoint = point;
|
|
2049
2176
|
let snappedDir;
|
|
2050
2177
|
let targetNode = this.getNodeAtPoint(point, true);
|
|
2051
|
-
|
|
2178
|
+
const edgeDrop = this.originalEdgeEndpoint?.edgeId ? this.findEdgeDropTarget(point, {
|
|
2179
|
+
maxScreenPx: 40,
|
|
2180
|
+
skipEdgeIds: new Set([edge.id])
|
|
2181
|
+
}) : null;
|
|
2182
|
+
if (edgeDrop) {
|
|
2183
|
+
const at = edgeDrop.edge.getPointAt(edgeDrop.pathParam);
|
|
2184
|
+
snappedPoint = edgeDrop.point;
|
|
2185
|
+
snappedDir = at ? directionFromAngle(at.angle) : void 0;
|
|
2186
|
+
targetNode = null;
|
|
2187
|
+
this.reconnectingOutlineParam = null;
|
|
2188
|
+
this.reconnectingTargetNodeId = null;
|
|
2189
|
+
} else if (this.attachToOutline) {
|
|
2052
2190
|
const tolerance = 40 / Math.max(this.renderer.zoom, 1e-4);
|
|
2053
2191
|
const toleranceSq = tolerance * tolerance;
|
|
2054
2192
|
let bestDistSq = Infinity;
|
|
@@ -2215,6 +2353,9 @@ var ConnectionManager = class extends EventEmitter {
|
|
|
2215
2353
|
this.previewTargetNodeId = null;
|
|
2216
2354
|
this.previewTargetEdgeId = null;
|
|
2217
2355
|
this.previewTargetPathParam = null;
|
|
2356
|
+
const rawNode = this.getRawNodeAtPoint(cursorPoint);
|
|
2357
|
+
const forbiddenFill = !!this._connectionValidator && !!this.sourceNode && !!rawNode && !this._connectionValidator(this.sourceNode.id, rawNode.id);
|
|
2358
|
+
const searchGroupFill = !!rawNode && (forbiddenFill || this.nodeContainsSource(rawNode) || this.nodeLooksLikeContainer(rawNode));
|
|
2218
2359
|
let targetNode = this.getNodeAtPoint(cursorPoint, false);
|
|
2219
2360
|
if (this.attachToOutline) {
|
|
2220
2361
|
const tolerance = 40 / Math.max(this.renderer.zoom, 1e-4);
|
|
@@ -2258,7 +2399,7 @@ var ConnectionManager = class extends EventEmitter {
|
|
|
2258
2399
|
}
|
|
2259
2400
|
}
|
|
2260
2401
|
}
|
|
2261
|
-
const edgeDrop = this.findEdgeDropTarget(cursorPoint);
|
|
2402
|
+
const edgeDrop = this.findEdgeDropTarget(cursorPoint, searchGroupFill ? { maxScreenPx: 40 } : void 0);
|
|
2262
2403
|
if (edgeDrop) {
|
|
2263
2404
|
let nodeDistance = Infinity;
|
|
2264
2405
|
if (targetNode) {
|
|
@@ -2266,7 +2407,7 @@ var ConnectionManager = class extends EventEmitter {
|
|
|
2266
2407
|
const dy = cursorPoint.y - snappedPoint.y;
|
|
2267
2408
|
nodeDistance = Math.sqrt(dx * dx + dy * dy);
|
|
2268
2409
|
}
|
|
2269
|
-
if (!targetNode || edgeDrop.distance
|
|
2410
|
+
if (searchGroupFill || !targetNode || edgeDrop.distance <= nodeDistance) {
|
|
2270
2411
|
targetNode = null;
|
|
2271
2412
|
this.previewTargetNodeId = null;
|
|
2272
2413
|
this.previewTargetOutlineParam = null;
|
|
@@ -2275,6 +2416,12 @@ var ConnectionManager = class extends EventEmitter {
|
|
|
2275
2416
|
this.previewTargetEdgeId = edgeDrop.edge.id;
|
|
2276
2417
|
this.previewTargetPathParam = edgeDrop.pathParam;
|
|
2277
2418
|
}
|
|
2419
|
+
} else if (searchGroupFill) {
|
|
2420
|
+
targetNode = null;
|
|
2421
|
+
this.previewTargetNodeId = null;
|
|
2422
|
+
this.previewTargetOutlineParam = null;
|
|
2423
|
+
this.previewTargetAnchorId = null;
|
|
2424
|
+
snappedPoint = cursorPoint;
|
|
2278
2425
|
}
|
|
2279
2426
|
if (targetNode) {
|
|
2280
2427
|
const forbidden = this._connectionValidator && this.sourceNode && !this._connectionValidator(this.sourceNode.id, targetNode.id);
|
|
@@ -2336,9 +2483,23 @@ var ConnectionManager = class extends EventEmitter {
|
|
|
2336
2483
|
y: event.worldY
|
|
2337
2484
|
};
|
|
2338
2485
|
let reconnected = false;
|
|
2486
|
+
const edgeDrop = this.originalEdgeEndpoint?.edgeId ? this.findEdgeDropTarget(point, {
|
|
2487
|
+
maxScreenPx: 40,
|
|
2488
|
+
skipEdgeIds: new Set([this.reconnectingEdge.id])
|
|
2489
|
+
}) : null;
|
|
2490
|
+
if (edgeDrop) {
|
|
2491
|
+
const endpoint = {
|
|
2492
|
+
edgeId: edgeDrop.edgeId,
|
|
2493
|
+
pathParam: edgeDrop.pathParam
|
|
2494
|
+
};
|
|
2495
|
+
if (this.reconnectingEndpoint === "start") this.reconnectingEdge.from = endpoint;
|
|
2496
|
+
else this.reconnectingEdge.to = endpoint;
|
|
2497
|
+
reconnected = true;
|
|
2498
|
+
this.emit("edgeReconnect", this.reconnectingEdge, this.reconnectingEndpoint);
|
|
2499
|
+
}
|
|
2339
2500
|
let node = this.getNodeAtPoint(point, true);
|
|
2340
2501
|
if (!node && this.attachToOutline && this.reconnectingTargetNodeId) node = this.renderer.getNode(this.reconnectingTargetNodeId) ?? null;
|
|
2341
|
-
if (node) {
|
|
2502
|
+
if (!reconnected && node) {
|
|
2342
2503
|
if (this.attachToOutline && this.reconnectingOutlineParam !== null) {
|
|
2343
2504
|
const endpoint = {
|
|
2344
2505
|
nodeId: node.id,
|
|
@@ -2543,11 +2704,12 @@ var ConnectionManager = class extends EventEmitter {
|
|
|
2543
2704
|
/**
|
|
2544
2705
|
* Find the nearest edge under the cursor for junction / note→relation drops.
|
|
2545
2706
|
*/
|
|
2546
|
-
findEdgeDropTarget(point) {
|
|
2547
|
-
const tolerance = Math.max(14, 20 / Math.max(this.renderer.zoom, 1e-4));
|
|
2707
|
+
findEdgeDropTarget(point, options) {
|
|
2708
|
+
const tolerance = options?.maxScreenPx === void 0 ? Math.max(14, 20 / Math.max(this.renderer.zoom, 1e-4)) : options.maxScreenPx / Math.max(this.renderer.zoom, 1e-4);
|
|
2548
2709
|
let best = null;
|
|
2549
2710
|
for (const edge of this.renderer.edges.values()) {
|
|
2550
2711
|
if (!edge.visible || edge.path.length < 2) continue;
|
|
2712
|
+
if (options?.skipEdgeIds?.has(edge.id)) continue;
|
|
2551
2713
|
const closest = edge.getClosestPointOnPath(point);
|
|
2552
2714
|
if (!closest || closest.distance > tolerance) continue;
|
|
2553
2715
|
if (!best || closest.distance < best.distance) best = {
|
|
@@ -2806,6 +2968,26 @@ var ConnectionManager = class extends EventEmitter {
|
|
|
2806
2968
|
}
|
|
2807
2969
|
return null;
|
|
2808
2970
|
}
|
|
2971
|
+
getRawNodeAtPoint(point) {
|
|
2972
|
+
const nodes = Array.from(this.renderer.nodes.values());
|
|
2973
|
+
for (let i = nodes.length - 1; i >= 0; i--) {
|
|
2974
|
+
const node = nodes[i];
|
|
2975
|
+
if (node.visible && node.hitTest(point)) return node;
|
|
2976
|
+
}
|
|
2977
|
+
return null;
|
|
2978
|
+
}
|
|
2979
|
+
nodeLooksLikeContainer(node, source = this.sourceNode) {
|
|
2980
|
+
if (!source || source.id === node.id) return false;
|
|
2981
|
+
return node.width * node.height >= Math.max(source.width * source.height, 1) * 4;
|
|
2982
|
+
}
|
|
2983
|
+
nodeContainsSource(node, source = this.sourceNode) {
|
|
2984
|
+
if (!source || source.id === node.id) return false;
|
|
2985
|
+
const sourceBounds = source.getBounds();
|
|
2986
|
+
const nodeBounds = node.getBounds();
|
|
2987
|
+
const cx = sourceBounds.x + sourceBounds.width / 2;
|
|
2988
|
+
const cy = sourceBounds.y + sourceBounds.height / 2;
|
|
2989
|
+
return cx >= nodeBounds.x && cx <= nodeBounds.x + nodeBounds.width && cy >= nodeBounds.y && cy <= nodeBounds.y + nodeBounds.height;
|
|
2990
|
+
}
|
|
2809
2991
|
startOutlineConnection(node, attach) {
|
|
2810
2992
|
const { point: outlinePoint, param } = node.getClosestPointOnOutline(attach);
|
|
2811
2993
|
this.sourceNode = node;
|
|
@@ -2940,6 +3122,9 @@ function getHistoryShortcut(event, keys = DEFAULT_HISTORY_SHORTCUT_KEYS) {
|
|
|
2940
3122
|
}
|
|
2941
3123
|
//#endregion
|
|
2942
3124
|
//#region src/utils/style.ts
|
|
3125
|
+
function canvasFontFromTextStyle(style) {
|
|
3126
|
+
return `${style.fontStyle ?? "normal"} ${style.fontWeight ?? "normal"} ${style.fontSize ?? 14}px ${style.fontFamily ?? "sans-serif"}`;
|
|
3127
|
+
}
|
|
2943
3128
|
/**
|
|
2944
3129
|
* Shallow compare two objects
|
|
2945
3130
|
*/
|
|
@@ -3039,6 +3224,7 @@ var DEFAULT_STYLE = {
|
|
|
3039
3224
|
fontSize: 14,
|
|
3040
3225
|
fontFamily: "sans-serif",
|
|
3041
3226
|
fontWeight: "normal",
|
|
3227
|
+
fontStyle: "normal",
|
|
3042
3228
|
color: "#000000",
|
|
3043
3229
|
opacity: 1,
|
|
3044
3230
|
align: "center",
|
|
@@ -3283,9 +3469,7 @@ var TextLabel = class {
|
|
|
3283
3469
|
this.render(ctx, bounds);
|
|
3284
3470
|
}
|
|
3285
3471
|
applyStyle(ctx) {
|
|
3286
|
-
|
|
3287
|
-
const fontFamily = this._style.fontFamily ?? "sans-serif";
|
|
3288
|
-
ctx.font = `${this._style.fontWeight ?? "normal"} ${fontSize}px ${fontFamily}`;
|
|
3472
|
+
ctx.font = canvasFontFromTextStyle(this._style);
|
|
3289
3473
|
ctx.textAlign = this._style.align ?? "center";
|
|
3290
3474
|
ctx.textBaseline = this._style.baseline ?? "middle";
|
|
3291
3475
|
}
|
|
@@ -3919,109 +4103,6 @@ var Element = class {
|
|
|
3919
4103
|
}
|
|
3920
4104
|
};
|
|
3921
4105
|
//#endregion
|
|
3922
|
-
//#region src/utils/edgePath.ts
|
|
3923
|
-
/**
|
|
3924
|
-
* Point and tangent angle at a normalized position along an edge path (0..1 by length).
|
|
3925
|
-
*/
|
|
3926
|
-
function getPathPointAt(path, type, position) {
|
|
3927
|
-
if (type !== "bezier" || path.length < 4) return getPointAlongPolyline(path, position);
|
|
3928
|
-
const samples = [];
|
|
3929
|
-
const steps = 20;
|
|
3930
|
-
for (let i = 1; i + 2 < path.length; i += 3) {
|
|
3931
|
-
const start = path[i - 1];
|
|
3932
|
-
const control1 = path[i];
|
|
3933
|
-
const control2 = path[i + 1];
|
|
3934
|
-
const end = path[i + 2];
|
|
3935
|
-
for (let sample = 0; sample <= steps; sample++) {
|
|
3936
|
-
const t = sample / steps;
|
|
3937
|
-
if (samples.length > 0 && t === 0) continue;
|
|
3938
|
-
samples.push(bezierPoint(start, control1, control2, end, t));
|
|
3939
|
-
}
|
|
3940
|
-
}
|
|
3941
|
-
return getPointAlongPolyline(samples, position);
|
|
3942
|
-
}
|
|
3943
|
-
function getPointAlongPolyline(path, position) {
|
|
3944
|
-
if (path.length === 0) return {
|
|
3945
|
-
point: {
|
|
3946
|
-
x: 0,
|
|
3947
|
-
y: 0
|
|
3948
|
-
},
|
|
3949
|
-
angle: 0
|
|
3950
|
-
};
|
|
3951
|
-
if (path.length === 1) return {
|
|
3952
|
-
point: path[0],
|
|
3953
|
-
angle: 0
|
|
3954
|
-
};
|
|
3955
|
-
let totalLength = 0;
|
|
3956
|
-
const segments = [];
|
|
3957
|
-
for (let i = 1; i < path.length; i++) {
|
|
3958
|
-
const start = path[i - 1];
|
|
3959
|
-
const end = path[i];
|
|
3960
|
-
const length = Math.hypot(end.x - start.x, end.y - start.y);
|
|
3961
|
-
segments.push({
|
|
3962
|
-
start,
|
|
3963
|
-
end,
|
|
3964
|
-
length
|
|
3965
|
-
});
|
|
3966
|
-
totalLength += length;
|
|
3967
|
-
}
|
|
3968
|
-
const targetLength = totalLength * Math.max(0, Math.min(1, position));
|
|
3969
|
-
let accumulated = 0;
|
|
3970
|
-
for (const segment of segments) {
|
|
3971
|
-
if (accumulated + segment.length >= targetLength) {
|
|
3972
|
-
const segmentPosition = segment.length > 0 ? (targetLength - accumulated) / segment.length : 0;
|
|
3973
|
-
return {
|
|
3974
|
-
point: {
|
|
3975
|
-
x: segment.start.x + segmentPosition * (segment.end.x - segment.start.x),
|
|
3976
|
-
y: segment.start.y + segmentPosition * (segment.end.y - segment.start.y)
|
|
3977
|
-
},
|
|
3978
|
-
angle: Math.atan2(segment.end.y - segment.start.y, segment.end.x - segment.start.x)
|
|
3979
|
-
};
|
|
3980
|
-
}
|
|
3981
|
-
accumulated += segment.length;
|
|
3982
|
-
}
|
|
3983
|
-
const lastSegment = segments[segments.length - 1];
|
|
3984
|
-
return {
|
|
3985
|
-
point: lastSegment.end,
|
|
3986
|
-
angle: Math.atan2(lastSegment.end.y - lastSegment.start.y, lastSegment.end.x - lastSegment.start.x)
|
|
3987
|
-
};
|
|
3988
|
-
}
|
|
3989
|
-
/** Map tangent angle to a cardinal direction used by polyline routing. */
|
|
3990
|
-
function directionFromAngle(angle) {
|
|
3991
|
-
const twoPi = Math.PI * 2;
|
|
3992
|
-
const a = (angle % twoPi + twoPi) % twoPi;
|
|
3993
|
-
if (a >= Math.PI * .25 && a < Math.PI * .75) return "bottom";
|
|
3994
|
-
if (a >= Math.PI * .75 && a < Math.PI * 1.25) return "left";
|
|
3995
|
-
if (a >= Math.PI * 1.25 && a < Math.PI * 1.75) return "top";
|
|
3996
|
-
return "right";
|
|
3997
|
-
}
|
|
3998
|
-
/**
|
|
3999
|
-
* Nearest point on an edge path to `point`, by sampling along normalized length.
|
|
4000
|
-
*/
|
|
4001
|
-
function getClosestPointOnPath(path, type, point, samples = 48) {
|
|
4002
|
-
if (path.length < 2) return null;
|
|
4003
|
-
let bestPoint = getPathPointAt(path, type, 0).point;
|
|
4004
|
-
let bestParam = 0;
|
|
4005
|
-
let bestDistSq = Infinity;
|
|
4006
|
-
for (let i = 0; i <= samples; i++) {
|
|
4007
|
-
const pathParam = i / samples;
|
|
4008
|
-
const at = getPathPointAt(path, type, pathParam).point;
|
|
4009
|
-
const dx = at.x - point.x;
|
|
4010
|
-
const dy = at.y - point.y;
|
|
4011
|
-
const distSq = dx * dx + dy * dy;
|
|
4012
|
-
if (distSq < bestDistSq) {
|
|
4013
|
-
bestDistSq = distSq;
|
|
4014
|
-
bestPoint = at;
|
|
4015
|
-
bestParam = pathParam;
|
|
4016
|
-
}
|
|
4017
|
-
}
|
|
4018
|
-
return {
|
|
4019
|
-
point: bestPoint,
|
|
4020
|
-
pathParam: bestParam,
|
|
4021
|
-
distance: Math.sqrt(bestDistSq)
|
|
4022
|
-
};
|
|
4023
|
-
}
|
|
4024
|
-
//#endregion
|
|
4025
4106
|
//#region src/elements/paths/StraightPathStrategy.ts
|
|
4026
4107
|
/**
|
|
4027
4108
|
* Straight line path strategy
|
|
@@ -7083,7 +7164,12 @@ var Edge = class extends Element {
|
|
|
7083
7164
|
* Nearest point on this edge's path to `point` (world coordinates).
|
|
7084
7165
|
*/
|
|
7085
7166
|
getClosestPointOnPath(point) {
|
|
7086
|
-
|
|
7167
|
+
const onPath = getClosestPointOnPath(this._path, this._type, point);
|
|
7168
|
+
if (onPath) return onPath;
|
|
7169
|
+
const start = this._fromPoint;
|
|
7170
|
+
const end = this._toPoint;
|
|
7171
|
+
if (start.x === end.x && start.y === end.y) return null;
|
|
7172
|
+
return getClosestPointOnPath([start, end], "straight", point);
|
|
7087
7173
|
}
|
|
7088
7174
|
/**
|
|
7089
7175
|
* Get world position of label center along path.
|
|
@@ -7940,7 +8026,13 @@ var InteractionManager = class {
|
|
|
7940
8026
|
}
|
|
7941
8027
|
if ("typeName" in hitElement) {
|
|
7942
8028
|
if (hitElement.typeName === "composite") {
|
|
7943
|
-
|
|
8029
|
+
const composite = hitElement;
|
|
8030
|
+
if (composite.usesExternalLabel()) {
|
|
8031
|
+
const editText = composite.label?.editableText ?? composite.label?.text ?? "";
|
|
8032
|
+
this.startLabelEdit("node", composite.id, editText, composite.getLabelPosition());
|
|
8033
|
+
return;
|
|
8034
|
+
}
|
|
8035
|
+
this.startCompositeNameEdit(composite);
|
|
7944
8036
|
return;
|
|
7945
8037
|
}
|
|
7946
8038
|
const editText = hitElement.label?.editableText ?? hitElement.label?.text ?? "";
|
|
@@ -9607,19 +9699,27 @@ var DiagramRenderer = class extends EventEmitter {
|
|
|
9607
9699
|
return this._groups.get(groupId);
|
|
9608
9700
|
}
|
|
9609
9701
|
/**
|
|
9610
|
-
* Find element at a point
|
|
9702
|
+
* Find element at a point. Edges are painted above nodes, so a stroke over a
|
|
9703
|
+
* nested pool/lane fill is hit before the covering container.
|
|
9611
9704
|
*/
|
|
9612
9705
|
getElementAtPoint(worldPoint) {
|
|
9706
|
+
let bestEdge;
|
|
9707
|
+
let bestDist = Infinity;
|
|
9708
|
+
for (const edge of this._edges.values()) {
|
|
9709
|
+
if (!edge.visible) continue;
|
|
9710
|
+
const tolerance = Math.max((edge.style.strokeWidth ?? 2) * 2, 8) / Math.max(this._zoom, 1e-4);
|
|
9711
|
+
const closest = edge.getClosestPointOnPath(worldPoint);
|
|
9712
|
+
if (closest && closest.distance <= tolerance && closest.distance < bestDist) {
|
|
9713
|
+
bestDist = closest.distance;
|
|
9714
|
+
bestEdge = edge;
|
|
9715
|
+
}
|
|
9716
|
+
}
|
|
9717
|
+
if (bestEdge) return bestEdge;
|
|
9613
9718
|
const nodesArray = Array.from(this._nodes.values());
|
|
9614
9719
|
for (let i = nodesArray.length - 1; i >= 0; i--) {
|
|
9615
9720
|
const node = nodesArray[i];
|
|
9616
9721
|
if (node.visible && node.hitTest(worldPoint)) return node;
|
|
9617
9722
|
}
|
|
9618
|
-
for (const edge of this._edges.values()) {
|
|
9619
|
-
if (!edge.visible) continue;
|
|
9620
|
-
const tolerance = Math.max((edge.style.strokeWidth ?? 2) * 2, 8) / Math.max(this._zoom, 1e-4);
|
|
9621
|
-
if (edge.hitTestWithTolerance(worldPoint, tolerance)) return edge;
|
|
9622
|
-
}
|
|
9623
9723
|
for (const group of this._groups.values()) if (group.visible && group.hitTest(worldPoint)) return group;
|
|
9624
9724
|
}
|
|
9625
9725
|
/**
|
|
@@ -10635,6 +10735,52 @@ var NodeImage = class {
|
|
|
10635
10735
|
}
|
|
10636
10736
|
};
|
|
10637
10737
|
//#endregion
|
|
10738
|
+
//#region src/utils/labelPlacement.ts
|
|
10739
|
+
var DEFAULT_LABEL_GAP = 4;
|
|
10740
|
+
function isExternalLabelPlacement(placement) {
|
|
10741
|
+
return placement === "top" || placement === "bottom" || placement === "left" || placement === "right";
|
|
10742
|
+
}
|
|
10743
|
+
function externalLabelWrapWidth(shapeWidth) {
|
|
10744
|
+
return Math.max(shapeWidth, 80);
|
|
10745
|
+
}
|
|
10746
|
+
function resolveExternalLabelBounds(shape, placement, gap, measured) {
|
|
10747
|
+
const width = Math.max(0, measured.width);
|
|
10748
|
+
const height = Math.max(0, measured.height);
|
|
10749
|
+
const safeGap = Number.isFinite(gap) ? gap : 4;
|
|
10750
|
+
switch (placement) {
|
|
10751
|
+
case "top": return {
|
|
10752
|
+
x: shape.x + (shape.width - width) / 2,
|
|
10753
|
+
y: shape.y - safeGap - height,
|
|
10754
|
+
width,
|
|
10755
|
+
height
|
|
10756
|
+
};
|
|
10757
|
+
case "bottom": return {
|
|
10758
|
+
x: shape.x + (shape.width - width) / 2,
|
|
10759
|
+
y: shape.y + shape.height + safeGap,
|
|
10760
|
+
width,
|
|
10761
|
+
height
|
|
10762
|
+
};
|
|
10763
|
+
case "left": return {
|
|
10764
|
+
x: shape.x - safeGap - width,
|
|
10765
|
+
y: shape.y + (shape.height - height) / 2,
|
|
10766
|
+
width,
|
|
10767
|
+
height
|
|
10768
|
+
};
|
|
10769
|
+
case "right": return {
|
|
10770
|
+
x: shape.x + shape.width + safeGap,
|
|
10771
|
+
y: shape.y + (shape.height - height) / 2,
|
|
10772
|
+
width,
|
|
10773
|
+
height
|
|
10774
|
+
};
|
|
10775
|
+
}
|
|
10776
|
+
}
|
|
10777
|
+
function unionBounds(a, b) {
|
|
10778
|
+
return rectUnion(a, b);
|
|
10779
|
+
}
|
|
10780
|
+
function pointInBounds(point, bounds) {
|
|
10781
|
+
return pointInRect(point, bounds);
|
|
10782
|
+
}
|
|
10783
|
+
//#endregion
|
|
10638
10784
|
//#region src/utils/resolveContentInset.ts
|
|
10639
10785
|
function axisFactor(node, base) {
|
|
10640
10786
|
if (base === void 0 || !(base > 0) || !Number.isFinite(base)) return 1;
|
|
@@ -10707,6 +10853,8 @@ var Node = class extends Element {
|
|
|
10707
10853
|
this._contentInset = this.normalizeContentInset(options.contentInset);
|
|
10708
10854
|
this._contentInsetScale = this.normalizeContentInsetScale(options.contentInsetScale);
|
|
10709
10855
|
this._contentInsetBaseSize = this.normalizeContentInsetBaseSize(options.contentInsetBaseSize);
|
|
10856
|
+
this._labelPlacement = options.labelPlacement ?? "center";
|
|
10857
|
+
this._labelGap = options.labelGap !== void 0 && Number.isFinite(options.labelGap) ? Math.max(0, options.labelGap) : 4;
|
|
10710
10858
|
if (options.label !== void 0) if (typeof options.label === "string") this._label = new TextLabel({
|
|
10711
10859
|
text: options.label,
|
|
10712
10860
|
onChange: () => this.markDirty()
|
|
@@ -10757,6 +10905,33 @@ var Node = class extends Element {
|
|
|
10757
10905
|
get ports() {
|
|
10758
10906
|
return this._ports;
|
|
10759
10907
|
}
|
|
10908
|
+
get labelPlacement() {
|
|
10909
|
+
return this._labelPlacement;
|
|
10910
|
+
}
|
|
10911
|
+
set labelPlacement(value) {
|
|
10912
|
+
if (this._labelPlacement !== value) {
|
|
10913
|
+
this._labelPlacement = value;
|
|
10914
|
+
this.markDirty();
|
|
10915
|
+
}
|
|
10916
|
+
}
|
|
10917
|
+
get labelGap() {
|
|
10918
|
+
return this._labelGap;
|
|
10919
|
+
}
|
|
10920
|
+
set labelGap(value) {
|
|
10921
|
+
const next = Number.isFinite(value) ? Math.max(0, value) : 4;
|
|
10922
|
+
if (this._labelGap !== next) {
|
|
10923
|
+
this._labelGap = next;
|
|
10924
|
+
this.markDirty();
|
|
10925
|
+
}
|
|
10926
|
+
}
|
|
10927
|
+
usesExternalLabel() {
|
|
10928
|
+
return isExternalLabelPlacement(this._labelPlacement) && this._label !== void 0;
|
|
10929
|
+
}
|
|
10930
|
+
getVisualBounds() {
|
|
10931
|
+
const shape = this.getBounds();
|
|
10932
|
+
if (!this.usesExternalLabel()) return shape;
|
|
10933
|
+
return unionBounds(shape, this.getResolvedLabelBounds());
|
|
10934
|
+
}
|
|
10760
10935
|
/**
|
|
10761
10936
|
* Text label
|
|
10762
10937
|
*/
|
|
@@ -10980,13 +11155,15 @@ var Node = class extends Element {
|
|
|
10980
11155
|
* Calculate layout for icon and label.
|
|
10981
11156
|
* Label always gets full content area; icon is placed within the same content area.
|
|
10982
11157
|
*/
|
|
10983
|
-
calculateContentLayout(bounds, iconBoxSize,
|
|
11158
|
+
calculateContentLayout(bounds, iconBoxSize, labelSize) {
|
|
11159
|
+
const iconContainer = this.getIconContainerBounds(bounds);
|
|
10984
11160
|
const contentBounds = this.getLabelContainerBounds(bounds);
|
|
10985
|
-
let iconBounds =
|
|
10986
|
-
if (this._icon && iconBoxSize) iconBounds = this.getIconBounds(
|
|
11161
|
+
let iconBounds = iconContainer;
|
|
11162
|
+
if (this._icon && iconBoxSize) iconBounds = this.getIconBounds(iconContainer, iconBoxSize, this._icon.placement);
|
|
11163
|
+
const labelBounds = isExternalLabelPlacement(this._labelPlacement) && this._label && labelSize ? resolveExternalLabelBounds(bounds, this._labelPlacement, this._labelGap, labelSize) : contentBounds;
|
|
10987
11164
|
return {
|
|
10988
11165
|
iconBounds,
|
|
10989
|
-
labelBounds
|
|
11166
|
+
labelBounds
|
|
10990
11167
|
};
|
|
10991
11168
|
}
|
|
10992
11169
|
/**
|
|
@@ -10996,14 +11173,14 @@ var Node = class extends Element {
|
|
|
10996
11173
|
getLabelBoundsForExport(ctx) {
|
|
10997
11174
|
if (!this._label) return null;
|
|
10998
11175
|
const bounds = this.getBounds();
|
|
10999
|
-
const
|
|
11176
|
+
const wrapWidth = this.getLabelWrapWidth(bounds);
|
|
11000
11177
|
const iconBoxSize = this._icon ? this.getIconBoxSize() : void 0;
|
|
11001
|
-
this._label.setAutoMaxWidth(
|
|
11178
|
+
this._label.setAutoMaxWidth(wrapWidth);
|
|
11002
11179
|
const labelSize = this._label.measure(ctx);
|
|
11003
11180
|
const { labelBounds } = this.calculateContentLayout(bounds, iconBoxSize, labelSize);
|
|
11004
11181
|
return {
|
|
11005
11182
|
bounds: labelBounds,
|
|
11006
|
-
lines: this._label.getWrappedLines(ctx, Math.max(0,
|
|
11183
|
+
lines: this._label.getWrappedLines(ctx, Math.max(0, wrapWidth))
|
|
11007
11184
|
};
|
|
11008
11185
|
}
|
|
11009
11186
|
/**
|
|
@@ -11016,10 +11193,10 @@ var Node = class extends Element {
|
|
|
11016
11193
|
ctx.globalAlpha = 1;
|
|
11017
11194
|
}
|
|
11018
11195
|
/**
|
|
11019
|
-
* Get world position of label center (
|
|
11196
|
+
* Get world position of label center (content area or external label box).
|
|
11020
11197
|
*/
|
|
11021
11198
|
getLabelPosition() {
|
|
11022
|
-
const bounds = this.
|
|
11199
|
+
const bounds = this.getResolvedLabelBounds();
|
|
11023
11200
|
return {
|
|
11024
11201
|
x: bounds.x + bounds.width / 2,
|
|
11025
11202
|
y: bounds.y + bounds.height / 2
|
|
@@ -11034,13 +11211,14 @@ var Node = class extends Element {
|
|
|
11034
11211
|
let bounds = this.getBounds();
|
|
11035
11212
|
this.renderBadges(ctx, bounds);
|
|
11036
11213
|
const iconBoxSize = this._icon ? this.getIconBoxSize() : void 0;
|
|
11037
|
-
|
|
11214
|
+
const externalLabel = this.usesExternalLabel();
|
|
11215
|
+
if (this._label) this._label.setAutoMaxWidth(this.getLabelWrapWidth(bounds));
|
|
11038
11216
|
const labelSize = this._label ? this._label.measure(ctx) : void 0;
|
|
11039
|
-
if (labelSize || iconBoxSize) {
|
|
11217
|
+
if ((labelSize || iconBoxSize) && !externalLabel) {
|
|
11040
11218
|
this.ensureContentsFit(labelSize, iconBoxSize);
|
|
11041
11219
|
if (this._label && iconBoxSize) {
|
|
11042
11220
|
bounds = this.getBounds();
|
|
11043
|
-
this._label.setAutoMaxWidth(this.
|
|
11221
|
+
this._label.setAutoMaxWidth(this.getLabelWrapWidth(bounds));
|
|
11044
11222
|
}
|
|
11045
11223
|
}
|
|
11046
11224
|
const { iconBounds, labelBounds } = this.calculateContentLayout(this.getBounds(), iconBoxSize, labelSize);
|
|
@@ -11117,10 +11295,10 @@ var Node = class extends Element {
|
|
|
11117
11295
|
return this.calculateContentMinSize(labelSize, iconBoxSize, bounds);
|
|
11118
11296
|
}
|
|
11119
11297
|
/**
|
|
11120
|
-
* Render resize handles when
|
|
11298
|
+
* Render the selection frame when selected, and resize handles when they are enabled.
|
|
11121
11299
|
*/
|
|
11122
11300
|
renderResizeHandles(ctx) {
|
|
11123
|
-
if (this._state !== "selected"
|
|
11301
|
+
if (this._state !== "selected") return;
|
|
11124
11302
|
const bounds = this.getBounds();
|
|
11125
11303
|
const offset = 6;
|
|
11126
11304
|
const size = 8;
|
|
@@ -11130,6 +11308,10 @@ var Node = class extends Element {
|
|
|
11130
11308
|
ctx.lineWidth = 1;
|
|
11131
11309
|
ctx.setLineDash([4, 4]);
|
|
11132
11310
|
ctx.strokeRect(bounds.x - offset, bounds.y - offset, bounds.width + offset * 2, bounds.height + offset * 2);
|
|
11311
|
+
if (!this._resizeHandlesEnabled) {
|
|
11312
|
+
ctx.restore();
|
|
11313
|
+
return;
|
|
11314
|
+
}
|
|
11133
11315
|
ctx.fillStyle = "#ffffff";
|
|
11134
11316
|
ctx.strokeStyle = DEFAULT_SELECTION_COLOR;
|
|
11135
11317
|
ctx.lineWidth = 1.5;
|
|
@@ -11146,7 +11328,7 @@ var Node = class extends Element {
|
|
|
11146
11328
|
* Hit test a resize handle
|
|
11147
11329
|
*/
|
|
11148
11330
|
hitTestResizeHandle(point) {
|
|
11149
|
-
if (this._state !== "selected") return null;
|
|
11331
|
+
if (this._state !== "selected" || !this._resizeHandlesEnabled) return null;
|
|
11150
11332
|
const half = 8 / 2;
|
|
11151
11333
|
for (const handle of this.getResizeHandlePositions()) if (point.x >= handle.x - half && point.x <= handle.x + half && point.y >= handle.y - half && point.y <= handle.y + half) return handle.type;
|
|
11152
11334
|
return null;
|
|
@@ -11157,7 +11339,25 @@ var Node = class extends Element {
|
|
|
11157
11339
|
hitTest(point) {
|
|
11158
11340
|
const bounds = this.getBounds();
|
|
11159
11341
|
const padding = 10;
|
|
11160
|
-
return point.x >= bounds.x - padding && point.x <= bounds.x + bounds.width + padding && point.y >= bounds.y - padding && point.y <= bounds.y + bounds.height + padding;
|
|
11342
|
+
return point.x >= bounds.x - padding && point.x <= bounds.x + bounds.width + padding && point.y >= bounds.y - padding && point.y <= bounds.y + bounds.height + padding || this.hitTestExternalLabel(point);
|
|
11343
|
+
}
|
|
11344
|
+
hitTestExternalLabel(point) {
|
|
11345
|
+
if (!this.usesExternalLabel()) return false;
|
|
11346
|
+
return pointInBounds(point, this.getResolvedLabelBounds());
|
|
11347
|
+
}
|
|
11348
|
+
getLabelWrapWidth(bounds = this.getBounds()) {
|
|
11349
|
+
if (this.usesExternalLabel()) return externalLabelWrapWidth(bounds.width);
|
|
11350
|
+
return this.getLabelContainerBounds(bounds).width;
|
|
11351
|
+
}
|
|
11352
|
+
getResolvedLabelBounds(measured) {
|
|
11353
|
+
const shape = this.getBounds();
|
|
11354
|
+
if (!this.usesExternalLabel() || !this._label) return this.getLabelContainerBounds(shape);
|
|
11355
|
+
const size = measured ?? {
|
|
11356
|
+
width: this._label.measuredWidth || this.getLabelWrapWidth(shape),
|
|
11357
|
+
height: this._label.measuredHeight || (this._label.style.fontSize ?? 14) * 1.2
|
|
11358
|
+
};
|
|
11359
|
+
if (!isExternalLabelPlacement(this._labelPlacement)) return this.getLabelContainerBounds(shape);
|
|
11360
|
+
return resolveExternalLabelBounds(shape, this._labelPlacement, this._labelGap, size);
|
|
11161
11361
|
}
|
|
11162
11362
|
/**
|
|
11163
11363
|
* Get a connection point on the node boundary toward a target point,
|
|
@@ -11391,16 +11591,28 @@ var Node = class extends Element {
|
|
|
11391
11591
|
}
|
|
11392
11592
|
calculateContentMinSize(labelSize, iconBoxSize, bounds) {
|
|
11393
11593
|
const labelContainer = this.getLabelContainerBounds(bounds);
|
|
11394
|
-
const
|
|
11395
|
-
const
|
|
11396
|
-
const
|
|
11397
|
-
const
|
|
11594
|
+
const iconContainer = this.getIconContainerBounds(bounds);
|
|
11595
|
+
const labelWidthFactor = labelContainer.width > 0 ? bounds.width / labelContainer.width : 1;
|
|
11596
|
+
const labelHeightFactor = labelContainer.height > 0 ? bounds.height / labelContainer.height : 1;
|
|
11597
|
+
const iconWidthFactor = iconContainer.width > 0 ? bounds.width / iconContainer.width : 1;
|
|
11598
|
+
const iconHeightFactor = iconContainer.height > 0 ? bounds.height / iconContainer.height : 1;
|
|
11398
11599
|
return {
|
|
11399
|
-
width:
|
|
11400
|
-
height:
|
|
11600
|
+
width: Math.max((labelSize?.width ?? 0) * labelWidthFactor, (iconBoxSize?.width ?? 0) * iconWidthFactor),
|
|
11601
|
+
height: Math.max((labelSize?.height ?? 0) * labelHeightFactor, (iconBoxSize?.height ?? 0) * iconHeightFactor)
|
|
11401
11602
|
};
|
|
11402
11603
|
}
|
|
11604
|
+
/**
|
|
11605
|
+
* Content box for the node icon: AABB minus contentInset.
|
|
11606
|
+
* Unlike {@link getLabelContainerBounds}, this is not shrunk to the
|
|
11607
|
+
* inscribed rectangle of a circle/diamond, so a centered icon can fill the shape.
|
|
11608
|
+
*/
|
|
11609
|
+
getIconContainerBounds(bounds) {
|
|
11610
|
+
return this.applyContentInset(bounds);
|
|
11611
|
+
}
|
|
11403
11612
|
getLabelContainerBounds(bounds) {
|
|
11613
|
+
return this.applyContentInset(bounds);
|
|
11614
|
+
}
|
|
11615
|
+
applyContentInset(bounds) {
|
|
11404
11616
|
const { top, right, bottom, left } = resolveContentInset(this._contentInset, this._contentInsetScale, {
|
|
11405
11617
|
width: bounds.width,
|
|
11406
11618
|
height: bounds.height
|
|
@@ -11797,7 +12009,7 @@ var CircleNode = class extends Node {
|
|
|
11797
12009
|
const ry = this._height / 2 + padding;
|
|
11798
12010
|
const dx = point.x - center.x;
|
|
11799
12011
|
const dy = point.y - center.y;
|
|
11800
|
-
return dx * dx / (rx * rx) + dy * dy / (ry * ry) <= 1;
|
|
12012
|
+
return dx * dx / (rx * rx) + dy * dy / (ry * ry) <= 1 || this.hitTestExternalLabel(point);
|
|
11801
12013
|
}
|
|
11802
12014
|
getOutlineDirection(param) {
|
|
11803
12015
|
return this.outlineDirectionFromCenter(param);
|
|
@@ -11886,7 +12098,7 @@ var DiamondNode = class extends Node {
|
|
|
11886
12098
|
const hh = this._height / 2 + padding;
|
|
11887
12099
|
const dx = Math.abs(point.x - center.x);
|
|
11888
12100
|
const dy = Math.abs(point.y - center.y);
|
|
11889
|
-
return dx / hw + dy / hh <= 1;
|
|
12101
|
+
return dx / hw + dy / hh <= 1 || this.hitTestExternalLabel(point);
|
|
11890
12102
|
}
|
|
11891
12103
|
getOutlineDirection(param) {
|
|
11892
12104
|
return this.outlineDirectionFromCenter(param);
|
|
@@ -12287,7 +12499,7 @@ var CustomShapeNode = class extends Node {
|
|
|
12287
12499
|
const path = this.getPath();
|
|
12288
12500
|
if (ctx.isPointInPath(path, localX, localY)) return true;
|
|
12289
12501
|
ctx.lineWidth = 20;
|
|
12290
|
-
return ctx.isPointInStroke(path, localX, localY);
|
|
12502
|
+
return ctx.isPointInStroke(path, localX, localY) || this.hitTestExternalLabel(point);
|
|
12291
12503
|
}
|
|
12292
12504
|
render(ctx) {
|
|
12293
12505
|
const style = this.style;
|
|
@@ -13117,6 +13329,9 @@ function isCContainer(component) {
|
|
|
13117
13329
|
function isCShape(component) {
|
|
13118
13330
|
return component.type === "shape";
|
|
13119
13331
|
}
|
|
13332
|
+
function isCText(component) {
|
|
13333
|
+
return component.type === "text";
|
|
13334
|
+
}
|
|
13120
13335
|
/**
|
|
13121
13336
|
* Flex container component for CompositeNode.
|
|
13122
13337
|
* Lays out children using a flexbox-like algorithm.
|
|
@@ -13127,6 +13342,8 @@ var CContainer = class extends CompositeComponentBase {
|
|
|
13127
13342
|
this.type = "container";
|
|
13128
13343
|
this._cachedBounds = null;
|
|
13129
13344
|
this._cachedContainerBounds = null;
|
|
13345
|
+
this._layoutChildren = null;
|
|
13346
|
+
this._suppressBoundName = false;
|
|
13130
13347
|
this.id = options.id;
|
|
13131
13348
|
this._direction = options.direction ?? "column";
|
|
13132
13349
|
this._justifyContent = options.justifyContent ?? "start";
|
|
@@ -13155,6 +13372,27 @@ var CContainer = class extends CompositeComponentBase {
|
|
|
13155
13372
|
get children() {
|
|
13156
13373
|
return this._children;
|
|
13157
13374
|
}
|
|
13375
|
+
/**
|
|
13376
|
+
* When true, CText bound to `__name__` is omitted from measure/render/hit/SVG.
|
|
13377
|
+
* Used when CompositeNode draws the name as an external Node.label.
|
|
13378
|
+
*/
|
|
13379
|
+
setSuppressBoundName(value) {
|
|
13380
|
+
if (this._suppressBoundName === value) return;
|
|
13381
|
+
this._suppressBoundName = value;
|
|
13382
|
+
this._cachedBounds = null;
|
|
13383
|
+
this._layoutChildren = null;
|
|
13384
|
+
for (const child of this._children) {
|
|
13385
|
+
if (isCContainer(child)) child.setSuppressBoundName(value);
|
|
13386
|
+
if (isCShape(child) && child.content) child.content.setSuppressBoundName(value);
|
|
13387
|
+
}
|
|
13388
|
+
}
|
|
13389
|
+
isLayoutSkipped(child) {
|
|
13390
|
+
if (child.style.visible === false) return true;
|
|
13391
|
+
return this._suppressBoundName && isCText(child) && child.bindToProperty === "__name__";
|
|
13392
|
+
}
|
|
13393
|
+
getLayoutChildren() {
|
|
13394
|
+
return this._children.filter((child) => !this.isLayoutSkipped(child));
|
|
13395
|
+
}
|
|
13158
13396
|
addChild(child) {
|
|
13159
13397
|
child.setOnChange(() => this.handleChildChange());
|
|
13160
13398
|
this._children.push(child);
|
|
@@ -13186,8 +13424,8 @@ var CContainer = class extends CompositeComponentBase {
|
|
|
13186
13424
|
padding: this._padding
|
|
13187
13425
|
};
|
|
13188
13426
|
}
|
|
13189
|
-
buildFlexChildren(ctx) {
|
|
13190
|
-
return
|
|
13427
|
+
buildFlexChildren(ctx, children = this.getLayoutChildren()) {
|
|
13428
|
+
return children.map((child) => {
|
|
13191
13429
|
const s = child.style;
|
|
13192
13430
|
return {
|
|
13193
13431
|
measure: child.measure(ctx),
|
|
@@ -13220,11 +13458,13 @@ var CContainer = class extends CompositeComponentBase {
|
|
|
13220
13458
|
}
|
|
13221
13459
|
render(ctx, bounds) {
|
|
13222
13460
|
if (this.style.visible === false) return;
|
|
13223
|
-
const
|
|
13461
|
+
const layoutChildren = this.getLayoutChildren();
|
|
13462
|
+
const flexChildren = this.buildFlexChildren(ctx, layoutChildren);
|
|
13224
13463
|
const result = flexLayout({
|
|
13225
13464
|
width: bounds.width,
|
|
13226
13465
|
height: bounds.height
|
|
13227
13466
|
}, this.getFlexConfig(), flexChildren);
|
|
13467
|
+
this._layoutChildren = layoutChildren;
|
|
13228
13468
|
this._cachedBounds = result.childBounds;
|
|
13229
13469
|
this._cachedContainerBounds = bounds;
|
|
13230
13470
|
const opacity = this.style.opacity ?? 1;
|
|
@@ -13232,9 +13472,8 @@ var CContainer = class extends CompositeComponentBase {
|
|
|
13232
13472
|
ctx.save();
|
|
13233
13473
|
ctx.globalAlpha *= opacity;
|
|
13234
13474
|
}
|
|
13235
|
-
for (let i = 0; i <
|
|
13236
|
-
const child =
|
|
13237
|
-
if (child.style.visible === false) continue;
|
|
13475
|
+
for (let i = 0; i < layoutChildren.length; i++) {
|
|
13476
|
+
const child = layoutChildren[i];
|
|
13238
13477
|
const cb = result.childBounds[i];
|
|
13239
13478
|
child.render(ctx, {
|
|
13240
13479
|
x: bounds.x + cb.x,
|
|
@@ -13247,10 +13486,9 @@ var CContainer = class extends CompositeComponentBase {
|
|
|
13247
13486
|
}
|
|
13248
13487
|
hitTest(point, bounds) {
|
|
13249
13488
|
if (this.style.visible === false) return null;
|
|
13250
|
-
if (!this._cachedBounds) return null;
|
|
13251
|
-
for (let i = this.
|
|
13252
|
-
const child = this.
|
|
13253
|
-
if (child.style.visible === false) continue;
|
|
13489
|
+
if (!this._cachedBounds || !this._layoutChildren) return null;
|
|
13490
|
+
for (let i = this._layoutChildren.length - 1; i >= 0; i--) {
|
|
13491
|
+
const child = this._layoutChildren[i];
|
|
13254
13492
|
const cb = this._cachedBounds[i];
|
|
13255
13493
|
const absBounds = {
|
|
13256
13494
|
x: bounds.x + cb.x,
|
|
@@ -13264,6 +13502,22 @@ var CContainer = class extends CompositeComponentBase {
|
|
|
13264
13502
|
return null;
|
|
13265
13503
|
}
|
|
13266
13504
|
/**
|
|
13505
|
+
* First CText bound to `__name__`, including suppressed name text.
|
|
13506
|
+
*/
|
|
13507
|
+
findBoundNameText() {
|
|
13508
|
+
for (const child of this._children) {
|
|
13509
|
+
if (isCText(child) && child.bindToProperty === "__name__") return child;
|
|
13510
|
+
if (isCContainer(child)) {
|
|
13511
|
+
const found = child.findBoundNameText();
|
|
13512
|
+
if (found) return found;
|
|
13513
|
+
}
|
|
13514
|
+
if (isCShape(child) && child.content) {
|
|
13515
|
+
const found = child.content.findBoundNameText();
|
|
13516
|
+
if (found) return found;
|
|
13517
|
+
}
|
|
13518
|
+
}
|
|
13519
|
+
}
|
|
13520
|
+
/**
|
|
13267
13521
|
* Find a component by id recursively.
|
|
13268
13522
|
*/
|
|
13269
13523
|
findById(id) {
|
|
@@ -13300,8 +13554,8 @@ var CContainer = class extends CompositeComponentBase {
|
|
|
13300
13554
|
if (this.style.visible === false) return "";
|
|
13301
13555
|
const cachedBounds = this._cachedBounds;
|
|
13302
13556
|
const cachedContainer = this._cachedContainerBounds;
|
|
13303
|
-
|
|
13304
|
-
|
|
13557
|
+
const layoutChildren = this._layoutChildren ?? this.getLayoutChildren();
|
|
13558
|
+
return `<g>${layoutChildren.map((child, i) => {
|
|
13305
13559
|
if (cachedBounds && cachedContainer) {
|
|
13306
13560
|
const cb = cachedBounds[i];
|
|
13307
13561
|
const dx = bounds.x - cachedContainer.x;
|
|
@@ -13313,7 +13567,7 @@ var CContainer = class extends CompositeComponentBase {
|
|
|
13313
13567
|
height: cb.height
|
|
13314
13568
|
});
|
|
13315
13569
|
}
|
|
13316
|
-
const h = bounds.height / Math.max(1,
|
|
13570
|
+
const h = bounds.height / Math.max(1, layoutChildren.length);
|
|
13317
13571
|
return child.toSVG({
|
|
13318
13572
|
x: bounds.x,
|
|
13319
13573
|
y: bounds.y + i * h,
|
|
@@ -13620,8 +13874,8 @@ var CompositeNode = class extends Node {
|
|
|
13620
13874
|
return this.getLabelContainerBounds(this.getBounds());
|
|
13621
13875
|
}
|
|
13622
13876
|
hitTest(point) {
|
|
13623
|
-
if (this._shapeType === "circle") return this.hitTestEllipse(point);
|
|
13624
|
-
if (this._shapeType === "diamond") return this.hitTestDiamond(point);
|
|
13877
|
+
if (this._shapeType === "circle") return this.hitTestEllipse(point) || this.hitTestExternalLabel(point);
|
|
13878
|
+
if (this._shapeType === "diamond") return this.hitTestDiamond(point) || this.hitTestExternalLabel(point);
|
|
13625
13879
|
return super.hitTest(point);
|
|
13626
13880
|
}
|
|
13627
13881
|
hitTestEllipse(point) {
|
|
@@ -13642,7 +13896,27 @@ var CompositeNode = class extends Node {
|
|
|
13642
13896
|
const dy = Math.abs(point.y - center.y);
|
|
13643
13897
|
return dx / hw + dy / hh <= 1;
|
|
13644
13898
|
}
|
|
13899
|
+
/**
|
|
13900
|
+
* Copy color/font from the suppressed `__name__` CText onto the external Node.label.
|
|
13901
|
+
*/
|
|
13902
|
+
syncExternalBoundNameLabelStyle() {
|
|
13903
|
+
if (!this.usesExternalLabel() || !this._label) return;
|
|
13904
|
+
const nameText = this._content.findBoundNameText();
|
|
13905
|
+
if (!nameText) return;
|
|
13906
|
+
const current = this._label.styleOverrides;
|
|
13907
|
+
if (current.color === nameText.color && current.fontFamily === nameText.fontFamily && current.fontWeight === nameText.fontWeight && current.fontStyle === nameText.fontStyle && current.fontSize === nameText.fontSize) return;
|
|
13908
|
+
this._label.style = {
|
|
13909
|
+
...current,
|
|
13910
|
+
color: nameText.color,
|
|
13911
|
+
fontFamily: nameText.fontFamily,
|
|
13912
|
+
fontWeight: nameText.fontWeight,
|
|
13913
|
+
fontStyle: nameText.fontStyle,
|
|
13914
|
+
fontSize: nameText.fontSize
|
|
13915
|
+
};
|
|
13916
|
+
}
|
|
13645
13917
|
render(ctx) {
|
|
13918
|
+
this._content.setSuppressBoundName(this.usesExternalLabel());
|
|
13919
|
+
this.syncExternalBoundNameLabelStyle();
|
|
13646
13920
|
if (this._autoSize) this.applyAutoSize(ctx);
|
|
13647
13921
|
const { x, y, width, height } = this.getBounds();
|
|
13648
13922
|
const style = this.style;
|
|
@@ -13715,6 +13989,11 @@ var CompositeNode = class extends Node {
|
|
|
13715
13989
|
this.renderBadges(ctx, bounds);
|
|
13716
13990
|
const contentBounds = this.getContentBounds();
|
|
13717
13991
|
this._content.render(ctx, contentBounds);
|
|
13992
|
+
if (this.usesExternalLabel() && this._label) {
|
|
13993
|
+
this._label.setAutoMaxWidth(this.getLabelWrapWidth(bounds));
|
|
13994
|
+
const labelSize = this._label.measure(ctx);
|
|
13995
|
+
this.renderLabel(ctx, this.getResolvedLabelBounds(labelSize));
|
|
13996
|
+
}
|
|
13718
13997
|
this.renderPorts(ctx);
|
|
13719
13998
|
}
|
|
13720
13999
|
applyAutoSize(ctx) {
|
|
@@ -14348,6 +14627,8 @@ var Serializer = class {
|
|
|
14348
14627
|
styleClass: node.styleClass,
|
|
14349
14628
|
label,
|
|
14350
14629
|
labelStyleClass: typeof label === "string" ? node.label?.styleClass : void 0,
|
|
14630
|
+
labelPlacement: node.labelPlacement !== "center" ? node.labelPlacement : void 0,
|
|
14631
|
+
labelGap: node.labelGap !== 4 ? node.labelGap : void 0,
|
|
14351
14632
|
icon,
|
|
14352
14633
|
contentInset: hasContentInset ? contentInset : void 0,
|
|
14353
14634
|
anchorPoints,
|
|
@@ -14654,9 +14935,16 @@ var SvgExporter = class {
|
|
|
14654
14935
|
break;
|
|
14655
14936
|
}
|
|
14656
14937
|
}
|
|
14938
|
+
cn.content.setSuppressBoundName(cn.usesExternalLabel());
|
|
14939
|
+
cn.syncExternalBoundNameLabelStyle();
|
|
14657
14940
|
const contentBounds = cn.getLabelContainerBounds(bounds);
|
|
14658
14941
|
const contentSvg = cn.content.toSVG(contentBounds);
|
|
14659
|
-
|
|
14942
|
+
const labelSvg = cn.usesExternalLabel() ? this.renderNodeLabel(cn, bounds) : "";
|
|
14943
|
+
return [
|
|
14944
|
+
`${shape} fill="${fill}" fill-opacity="${fillOpacity}" stroke="${stroke}" stroke-width="${strokeWidth}" stroke-opacity="${strokeOpacity}"${dash}${dashOffset}/>`,
|
|
14945
|
+
contentSvg,
|
|
14946
|
+
labelSvg
|
|
14947
|
+
].join("");
|
|
14660
14948
|
}
|
|
14661
14949
|
default: shape = `<rect x="${bounds.x}" y="${bounds.y}" width="${bounds.width}" height="${bounds.height}"`;
|
|
14662
14950
|
}
|
|
@@ -14729,15 +15017,13 @@ var SvgExporter = class {
|
|
|
14729
15017
|
}
|
|
14730
15018
|
measureTextLabel(text, style = {}, inset = 8) {
|
|
14731
15019
|
const fontSize = style.fontSize ?? 14;
|
|
14732
|
-
const fontFamily = style.fontFamily ?? "sans-serif";
|
|
14733
|
-
const fontWeight = style.fontWeight ?? "normal";
|
|
14734
15020
|
const lineHeight = fontSize * 1.2;
|
|
14735
15021
|
const lines = text.split("\n");
|
|
14736
15022
|
let maxWidth = 0;
|
|
14737
15023
|
if (typeof document !== "undefined") {
|
|
14738
15024
|
const ctx = document.createElement("canvas").getContext("2d");
|
|
14739
15025
|
if (ctx) {
|
|
14740
|
-
ctx.font =
|
|
15026
|
+
ctx.font = canvasFontFromTextStyle(style);
|
|
14741
15027
|
for (const line of lines) maxWidth = Math.max(maxWidth, ctx.measureText(line).width);
|
|
14742
15028
|
}
|
|
14743
15029
|
}
|
|
@@ -14848,16 +15134,18 @@ var SvgExporter = class {
|
|
|
14848
15134
|
const fontSize = style.fontSize ?? 14;
|
|
14849
15135
|
const fontFamily = style.fontFamily ?? "sans-serif";
|
|
14850
15136
|
const fontWeight = style.fontWeight ?? "normal";
|
|
15137
|
+
const fontStyle = style.fontStyle ?? "normal";
|
|
15138
|
+
const fontStyleAttr = fontStyle !== "normal" ? ` font-style="${fontStyle}"` : "";
|
|
14851
15139
|
const opacity = style.opacity ?? 1;
|
|
14852
15140
|
const anchor = style.align === "left" ? "start" : style.align === "right" ? "end" : "middle";
|
|
14853
15141
|
const baseline = style.baseline === "top" ? "text-before-edge" : style.baseline === "bottom" ? "text-after-edge" : "middle";
|
|
14854
15142
|
const transform = rotation !== 0 ? ` transform="rotate(${rotation * 180 / Math.PI} ${point.x} ${point.y})"` : "";
|
|
14855
15143
|
const lines = text.split("\n");
|
|
14856
|
-
if (lines.length <= 1) return `<text x="${point.x}" y="${point.y}" fill="${fill}" fill-opacity="${opacity}" font-size="${fontSize}" font-family="${fontFamily}" font-weight="${fontWeight}" text-anchor="${anchor}" dominant-baseline="${baseline}"${transform}>${this.escapeText(text)}</text>`;
|
|
15144
|
+
if (lines.length <= 1) return `<text x="${point.x}" y="${point.y}" fill="${fill}" fill-opacity="${opacity}" font-size="${fontSize}" font-family="${fontFamily}" font-weight="${fontWeight}"${fontStyleAttr} text-anchor="${anchor}" dominant-baseline="${baseline}"${transform}>${this.escapeText(text)}</text>`;
|
|
14857
15145
|
const lineHeight = fontSize * 1.2;
|
|
14858
15146
|
const startY = point.y - (lines.length - 1) * lineHeight / 2;
|
|
14859
15147
|
const tspans = lines.map((line, index) => `<tspan x="${point.x}" y="${startY + index * lineHeight}">${this.escapeText(line)}</tspan>`).join("");
|
|
14860
|
-
return `<text x="${point.x}" y="${point.y}" fill="${fill}" fill-opacity="${opacity}" font-size="${fontSize}" font-family="${fontFamily}" font-weight="${fontWeight}" text-anchor="${anchor}" dominant-baseline="${baseline}"${transform}>${tspans}</text>`;
|
|
15148
|
+
return `<text x="${point.x}" y="${point.y}" fill="${fill}" fill-opacity="${opacity}" font-size="${fontSize}" font-family="${fontFamily}" font-weight="${fontWeight}"${fontStyleAttr} text-anchor="${anchor}" dominant-baseline="${baseline}"${transform}>${tspans}</text>`;
|
|
14861
15149
|
}
|
|
14862
15150
|
buildEdgePath(edge, edgeLabelOffset) {
|
|
14863
15151
|
if (!edge.label || !edge.labelLineGap) return this.buildPath(edge);
|
|
@@ -15572,6 +15860,6 @@ function applyEdgeStyle(ctx, style) {
|
|
|
15572
15860
|
return baseOpacity * strokeOpacity;
|
|
15573
15861
|
}
|
|
15574
15862
|
//#endregion
|
|
15575
|
-
export { AddNodeCommand, AnimationManager, AutoLayout, AutoRouting, BezierPathStrategy, CContainer, CDivider, CIcon, CShape, CText, ChangeEdgePropertiesCommand, ChangeEditablePolylineControlPointsCommand, ChangeGroupPropertiesCommand, ChangeNodePropertiesCommand, CircleNode, ClipboardManager, CompositeCommand, CompositeNode, ConnectionManager, ContextMenuManager, CustomShapeNode, DARK_THEME, DEFAULT_THEME, DiagramRenderer, DiamondNode, DragManager, Edge, Element, EventEmitter, GridOverlay, Group, GuidesOverlay, HistoryManager, ImageExporter, InputHandler, InteractionManager, MiniMap, MoveNodesCommand, NavigationManager, Node, NodeImage, PolylinePathStrategy, Port, PropertyChangeBatcher, RectangleNode, RemoveNodeCommand, ResizeManager, ResizeNodesCommand, RulersOverlay, SearchManager, SelectionManager, Serializer, ShapeFactories, StraightPathStrategy, StyleManager, SvgExporter, TextLabel, alignNodes, angle, applyEdgeStyle, applyNodeStyle, bezierPoint, boundsCenter, calculateBezierControlPoints, clamp, clonePoints, container, deserializeCComponent, directionFromAngle, distance, distanceToSegment, distributeNodes, divider, drawRoundedRectPath, expandBounds, flexLayout, generateId, getClosestPointOnPath, getPathPointAt, icon, isCornerPlacement, isEdgeEdgeEndpoint, isNodeEdgeEndpoint, lerp, mergeBounds, normalizeSides, pointInEllipse, pointInRect, rectIntersection, rectUnion, rectsIntersect, renderFillAndStroke, resetIdCounter, resetPortIdCounter, resolveContentInset, rotatePoint, segmentRectIntersections, shape, snapPointToGrid, snapToGrid, text };
|
|
15863
|
+
export { AddNodeCommand, AnimationManager, AutoLayout, AutoRouting, BezierPathStrategy, CContainer, CDivider, CIcon, CShape, CText, ChangeEdgePropertiesCommand, ChangeEditablePolylineControlPointsCommand, ChangeGroupPropertiesCommand, ChangeNodePropertiesCommand, CircleNode, ClipboardManager, CompositeCommand, CompositeNode, ConnectionManager, ContextMenuManager, CustomShapeNode, DARK_THEME, DEFAULT_LABEL_GAP, DEFAULT_THEME, DiagramRenderer, DiamondNode, DragManager, Edge, Element, EventEmitter, GridOverlay, Group, GuidesOverlay, HistoryManager, ImageExporter, InputHandler, InteractionManager, MiniMap, MoveNodesCommand, NavigationManager, Node, NodeImage, PolylinePathStrategy, Port, PropertyChangeBatcher, RectangleNode, RemoveNodeCommand, ResizeManager, ResizeNodesCommand, RulersOverlay, SearchManager, SelectionManager, Serializer, ShapeFactories, StraightPathStrategy, StyleManager, SvgExporter, TextLabel, alignNodes, angle, applyEdgeStyle, applyNodeStyle, bezierPoint, boundsCenter, calculateBezierControlPoints, clamp, clonePoints, container, deserializeCComponent, directionFromAngle, distance, distanceToSegment, distributeNodes, divider, drawRoundedRectPath, expandBounds, flexLayout, generateId, getClosestPointOnPath, getPathPointAt, icon, isCornerPlacement, isEdgeEdgeEndpoint, isExternalLabelPlacement, isNodeEdgeEndpoint, lerp, mergeBounds, normalizeSides, pointInEllipse, pointInRect, rectIntersection, rectUnion, rectsIntersect, renderFillAndStroke, resetIdCounter, resetPortIdCounter, resolveContentInset, rotatePoint, segmentRectIntersections, shape, snapPointToGrid, snapToGrid, text };
|
|
15576
15864
|
|
|
15577
15865
|
//# sourceMappingURL=papirus.js.map
|