@ngroznykh/papirus 0.6.5 → 0.7.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/dist/core/ClipboardManager.d.ts.map +1 -1
- package/dist/core/ConnectionManager.d.ts +6 -0
- package/dist/core/ConnectionManager.d.ts.map +1 -1
- package/dist/core/DiagramRenderer.d.ts +9 -1
- package/dist/core/DiagramRenderer.d.ts.map +1 -1
- package/dist/core/DragManager.d.ts.map +1 -1
- package/dist/core/EdgeEndpointUpdater.d.ts +8 -1
- package/dist/core/EdgeEndpointUpdater.d.ts.map +1 -1
- package/dist/core/InteractionManager.d.ts.map +1 -1
- package/dist/core/SearchManager.d.ts.map +1 -1
- package/dist/elements/Edge.d.ts +21 -0
- package/dist/elements/Edge.d.ts.map +1 -1
- package/dist/elements/NodeImage.d.ts.map +1 -1
- package/dist/elements/composite/CIcon.d.ts.map +1 -1
- package/dist/elements/edge/EdgeLabelRenderer.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/papirus.js +422 -163
- package/dist/papirus.js.map +1 -1
- package/dist/types.d.ts +15 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/AutoRouting.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/edgePath.d.ts +21 -0
- package/dist/utils/edgePath.d.ts.map +1 -0
- package/dist/utils/svgAssetLoader.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/papirus.js
CHANGED
|
@@ -1047,19 +1047,21 @@ var DragManager = class extends EventEmitter {
|
|
|
1047
1047
|
const nodeBindings = /* @__PURE__ */ new Map();
|
|
1048
1048
|
for (const edge of this.renderer.edges.values()) {
|
|
1049
1049
|
if (!edge.hasEditableControlPoints()) continue;
|
|
1050
|
-
const
|
|
1051
|
-
const
|
|
1050
|
+
const fromId = edge.from.nodeId;
|
|
1051
|
+
const toId = edge.to.nodeId;
|
|
1052
|
+
const isFromDragged = !!fromId && draggedIds.has(fromId);
|
|
1053
|
+
const isToDragged = !!toId && draggedIds.has(toId);
|
|
1052
1054
|
if (isFromDragged && isToDragged) {
|
|
1053
1055
|
this.editablePolylineFullyConnectedEdges.add(edge.id);
|
|
1054
1056
|
this.initialControlPointsForFullyConnected.set(edge.id, clonePoints(edge.controlPoints));
|
|
1055
1057
|
continue;
|
|
1056
1058
|
}
|
|
1057
1059
|
for (const node of this.draggedNodes) {
|
|
1058
|
-
const isFrom =
|
|
1059
|
-
const isTo =
|
|
1060
|
+
const isFrom = fromId === node.id;
|
|
1061
|
+
const isTo = toId === node.id;
|
|
1060
1062
|
if (!isFrom && !isTo) continue;
|
|
1061
|
-
const oppositeNodeId = isFrom ?
|
|
1062
|
-
if (draggedIds.has(oppositeNodeId)) continue;
|
|
1063
|
+
const oppositeNodeId = isFrom ? toId : fromId;
|
|
1064
|
+
if (!oppositeNodeId || draggedIds.has(oppositeNodeId)) continue;
|
|
1063
1065
|
const controlPointIndex = isFrom ? 0 : edge.controlPoints.length - 1;
|
|
1064
1066
|
const nearestPoint = edge.controlPoints[controlPointIndex];
|
|
1065
1067
|
const endpoint = isFrom ? edge.startPoint : edge.endPoint;
|
|
@@ -1734,6 +1736,8 @@ var ConnectionManager = class extends EventEmitter {
|
|
|
1734
1736
|
this.sourceOutlineParam = null;
|
|
1735
1737
|
this.previewTargetOutlineParam = null;
|
|
1736
1738
|
this.previewTargetNodeId = null;
|
|
1739
|
+
this.previewTargetEdgeId = null;
|
|
1740
|
+
this.previewTargetPathParam = null;
|
|
1737
1741
|
this.isReconnecting = false;
|
|
1738
1742
|
this.reconnectingEdge = null;
|
|
1739
1743
|
this.reconnectingEndpoint = null;
|
|
@@ -1912,8 +1916,8 @@ var ConnectionManager = class extends EventEmitter {
|
|
|
1912
1916
|
updateReconnectingEdge(point) {
|
|
1913
1917
|
if (!this.reconnectingEdge) return;
|
|
1914
1918
|
const edge = this.reconnectingEdge;
|
|
1915
|
-
const fromNode = this.renderer.getNode(edge.from.nodeId);
|
|
1916
|
-
const toNode = this.renderer.getNode(edge.to.nodeId);
|
|
1919
|
+
const fromNode = edge.from.nodeId ? this.renderer.getNode(edge.from.nodeId) : void 0;
|
|
1920
|
+
const toNode = edge.to.nodeId ? this.renderer.getNode(edge.to.nodeId) : void 0;
|
|
1917
1921
|
let snappedPoint = point;
|
|
1918
1922
|
let snappedDir;
|
|
1919
1923
|
let targetNode = this.getNodeAtPoint(point, true);
|
|
@@ -2056,6 +2060,8 @@ var ConnectionManager = class extends EventEmitter {
|
|
|
2056
2060
|
this.previewTargetAnchorId = null;
|
|
2057
2061
|
this.previewTargetOutlineParam = null;
|
|
2058
2062
|
this.previewTargetNodeId = null;
|
|
2063
|
+
this.previewTargetEdgeId = null;
|
|
2064
|
+
this.previewTargetPathParam = null;
|
|
2059
2065
|
let targetNode = this.getNodeAtPoint(cursorPoint, false);
|
|
2060
2066
|
if (this.attachToOutline) {
|
|
2061
2067
|
const tolerance = 40 / Math.max(this.renderer.zoom, 1e-4);
|
|
@@ -2099,6 +2105,24 @@ var ConnectionManager = class extends EventEmitter {
|
|
|
2099
2105
|
}
|
|
2100
2106
|
}
|
|
2101
2107
|
}
|
|
2108
|
+
const edgeDrop = this.findEdgeDropTarget(cursorPoint);
|
|
2109
|
+
if (edgeDrop) {
|
|
2110
|
+
let nodeDistance = Infinity;
|
|
2111
|
+
if (targetNode) {
|
|
2112
|
+
const dx = cursorPoint.x - snappedPoint.x;
|
|
2113
|
+
const dy = cursorPoint.y - snappedPoint.y;
|
|
2114
|
+
nodeDistance = Math.sqrt(dx * dx + dy * dy);
|
|
2115
|
+
}
|
|
2116
|
+
if (!targetNode || edgeDrop.distance < nodeDistance) {
|
|
2117
|
+
targetNode = null;
|
|
2118
|
+
this.previewTargetNodeId = null;
|
|
2119
|
+
this.previewTargetOutlineParam = null;
|
|
2120
|
+
this.previewTargetAnchorId = null;
|
|
2121
|
+
snappedPoint = edgeDrop.point;
|
|
2122
|
+
this.previewTargetEdgeId = edgeDrop.edge.id;
|
|
2123
|
+
this.previewTargetPathParam = edgeDrop.pathParam;
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2102
2126
|
if (targetNode) {
|
|
2103
2127
|
const forbidden = this._connectionValidator && this.sourceNode && !this._connectionValidator(this.sourceNode.id, targetNode.id);
|
|
2104
2128
|
this.setCursor(forbidden ? "not-allowed" : "crosshair");
|
|
@@ -2199,9 +2223,29 @@ var ConnectionManager = class extends EventEmitter {
|
|
|
2199
2223
|
y: event.worldY
|
|
2200
2224
|
};
|
|
2201
2225
|
let createdEdge = null;
|
|
2226
|
+
const edgeDrop = this.previewTargetEdgeId != null && this.previewTargetPathParam != null ? {
|
|
2227
|
+
edgeId: this.previewTargetEdgeId,
|
|
2228
|
+
pathParam: this.previewTargetPathParam,
|
|
2229
|
+
point
|
|
2230
|
+
} : this.findEdgeDropTarget(point);
|
|
2202
2231
|
let targetNode = this.getNodeAtPoint(point, false);
|
|
2203
2232
|
if (!targetNode && this.attachToOutline && this.previewTargetNodeId) targetNode = this.renderer.getNode(this.previewTargetNodeId) ?? null;
|
|
2204
|
-
if (targetNode) {
|
|
2233
|
+
if (edgeDrop && this.sourceNode && (!targetNode || this.previewTargetEdgeId != null)) {
|
|
2234
|
+
const from = this.attachToOutline ? {
|
|
2235
|
+
nodeId: this.sourceNode.id,
|
|
2236
|
+
outlineParam: this.sourceOutlineParam ?? void 0
|
|
2237
|
+
} : {
|
|
2238
|
+
nodeId: this.sourceNode.id,
|
|
2239
|
+
portId: this.sourceAnchorId ? `${ANCHOR_PORT_PREFIX}${this.sourceAnchorId}` : void 0
|
|
2240
|
+
};
|
|
2241
|
+
const to = {
|
|
2242
|
+
edgeId: edgeDrop.edgeId,
|
|
2243
|
+
pathParam: edgeDrop.pathParam
|
|
2244
|
+
};
|
|
2245
|
+
createdEdge = this.createEdge(from, to);
|
|
2246
|
+
this.addEdge(createdEdge);
|
|
2247
|
+
this.emit("connect", createdEdge);
|
|
2248
|
+
} else if (targetNode) {
|
|
2205
2249
|
if (!this._connectionValidator || this._connectionValidator(this.sourceNode.id, targetNode.id)) {
|
|
2206
2250
|
let from;
|
|
2207
2251
|
let to;
|
|
@@ -2338,8 +2382,36 @@ var ConnectionManager = class extends EventEmitter {
|
|
|
2338
2382
|
this.sourceOutlineParam = null;
|
|
2339
2383
|
this.previewTargetOutlineParam = null;
|
|
2340
2384
|
this.previewTargetNodeId = null;
|
|
2385
|
+
this.previewTargetEdgeId = null;
|
|
2386
|
+
this.previewTargetPathParam = null;
|
|
2341
2387
|
this.setCursor("");
|
|
2342
2388
|
}
|
|
2389
|
+
/**
|
|
2390
|
+
* Find the nearest edge under the cursor for junction / note→relation drops.
|
|
2391
|
+
*/
|
|
2392
|
+
findEdgeDropTarget(point) {
|
|
2393
|
+
const tolerance = Math.max(14, 20 / Math.max(this.renderer.zoom, 1e-4));
|
|
2394
|
+
let best = null;
|
|
2395
|
+
for (const edge of this.renderer.edges.values()) {
|
|
2396
|
+
if (!edge.visible || edge.path.length < 2) continue;
|
|
2397
|
+
const closest = edge.getClosestPointOnPath(point);
|
|
2398
|
+
if (!closest || closest.distance > tolerance) continue;
|
|
2399
|
+
if (!best || closest.distance < best.distance) best = {
|
|
2400
|
+
edge,
|
|
2401
|
+
pathParam: closest.pathParam,
|
|
2402
|
+
point: closest.point,
|
|
2403
|
+
distance: closest.distance
|
|
2404
|
+
};
|
|
2405
|
+
}
|
|
2406
|
+
if (!best) return null;
|
|
2407
|
+
return {
|
|
2408
|
+
edge: best.edge,
|
|
2409
|
+
edgeId: best.edge.id,
|
|
2410
|
+
pathParam: best.pathParam,
|
|
2411
|
+
point: best.point,
|
|
2412
|
+
distance: best.distance
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2343
2415
|
resetReconnection() {
|
|
2344
2416
|
this.isReconnecting = false;
|
|
2345
2417
|
if (this.reconnectingEdge) this.reconnectingEdge.autoUpdateEndpoints = true;
|
|
@@ -3588,6 +3660,109 @@ var Element = class {
|
|
|
3588
3660
|
}
|
|
3589
3661
|
};
|
|
3590
3662
|
//#endregion
|
|
3663
|
+
//#region src/utils/edgePath.ts
|
|
3664
|
+
/**
|
|
3665
|
+
* Point and tangent angle at a normalized position along an edge path (0..1 by length).
|
|
3666
|
+
*/
|
|
3667
|
+
function getPathPointAt(path, type, position) {
|
|
3668
|
+
if (type !== "bezier" || path.length < 4) return getPointAlongPolyline(path, position);
|
|
3669
|
+
const samples = [];
|
|
3670
|
+
const steps = 20;
|
|
3671
|
+
for (let i = 1; i + 2 < path.length; i += 3) {
|
|
3672
|
+
const start = path[i - 1];
|
|
3673
|
+
const control1 = path[i];
|
|
3674
|
+
const control2 = path[i + 1];
|
|
3675
|
+
const end = path[i + 2];
|
|
3676
|
+
for (let sample = 0; sample <= steps; sample++) {
|
|
3677
|
+
const t = sample / steps;
|
|
3678
|
+
if (samples.length > 0 && t === 0) continue;
|
|
3679
|
+
samples.push(bezierPoint(start, control1, control2, end, t));
|
|
3680
|
+
}
|
|
3681
|
+
}
|
|
3682
|
+
return getPointAlongPolyline(samples, position);
|
|
3683
|
+
}
|
|
3684
|
+
function getPointAlongPolyline(path, position) {
|
|
3685
|
+
if (path.length === 0) return {
|
|
3686
|
+
point: {
|
|
3687
|
+
x: 0,
|
|
3688
|
+
y: 0
|
|
3689
|
+
},
|
|
3690
|
+
angle: 0
|
|
3691
|
+
};
|
|
3692
|
+
if (path.length === 1) return {
|
|
3693
|
+
point: path[0],
|
|
3694
|
+
angle: 0
|
|
3695
|
+
};
|
|
3696
|
+
let totalLength = 0;
|
|
3697
|
+
const segments = [];
|
|
3698
|
+
for (let i = 1; i < path.length; i++) {
|
|
3699
|
+
const start = path[i - 1];
|
|
3700
|
+
const end = path[i];
|
|
3701
|
+
const length = Math.hypot(end.x - start.x, end.y - start.y);
|
|
3702
|
+
segments.push({
|
|
3703
|
+
start,
|
|
3704
|
+
end,
|
|
3705
|
+
length
|
|
3706
|
+
});
|
|
3707
|
+
totalLength += length;
|
|
3708
|
+
}
|
|
3709
|
+
const targetLength = totalLength * Math.max(0, Math.min(1, position));
|
|
3710
|
+
let accumulated = 0;
|
|
3711
|
+
for (const segment of segments) {
|
|
3712
|
+
if (accumulated + segment.length >= targetLength) {
|
|
3713
|
+
const segmentPosition = segment.length > 0 ? (targetLength - accumulated) / segment.length : 0;
|
|
3714
|
+
return {
|
|
3715
|
+
point: {
|
|
3716
|
+
x: segment.start.x + segmentPosition * (segment.end.x - segment.start.x),
|
|
3717
|
+
y: segment.start.y + segmentPosition * (segment.end.y - segment.start.y)
|
|
3718
|
+
},
|
|
3719
|
+
angle: Math.atan2(segment.end.y - segment.start.y, segment.end.x - segment.start.x)
|
|
3720
|
+
};
|
|
3721
|
+
}
|
|
3722
|
+
accumulated += segment.length;
|
|
3723
|
+
}
|
|
3724
|
+
const lastSegment = segments[segments.length - 1];
|
|
3725
|
+
return {
|
|
3726
|
+
point: lastSegment.end,
|
|
3727
|
+
angle: Math.atan2(lastSegment.end.y - lastSegment.start.y, lastSegment.end.x - lastSegment.start.x)
|
|
3728
|
+
};
|
|
3729
|
+
}
|
|
3730
|
+
/** Map tangent angle to a cardinal direction used by polyline routing. */
|
|
3731
|
+
function directionFromAngle(angle) {
|
|
3732
|
+
const twoPi = Math.PI * 2;
|
|
3733
|
+
const a = (angle % twoPi + twoPi) % twoPi;
|
|
3734
|
+
if (a >= Math.PI * .25 && a < Math.PI * .75) return "bottom";
|
|
3735
|
+
if (a >= Math.PI * .75 && a < Math.PI * 1.25) return "left";
|
|
3736
|
+
if (a >= Math.PI * 1.25 && a < Math.PI * 1.75) return "top";
|
|
3737
|
+
return "right";
|
|
3738
|
+
}
|
|
3739
|
+
/**
|
|
3740
|
+
* Nearest point on an edge path to `point`, by sampling along normalized length.
|
|
3741
|
+
*/
|
|
3742
|
+
function getClosestPointOnPath(path, type, point, samples = 48) {
|
|
3743
|
+
if (path.length < 2) return null;
|
|
3744
|
+
let bestPoint = getPathPointAt(path, type, 0).point;
|
|
3745
|
+
let bestParam = 0;
|
|
3746
|
+
let bestDistSq = Infinity;
|
|
3747
|
+
for (let i = 0; i <= samples; i++) {
|
|
3748
|
+
const pathParam = i / samples;
|
|
3749
|
+
const at = getPathPointAt(path, type, pathParam).point;
|
|
3750
|
+
const dx = at.x - point.x;
|
|
3751
|
+
const dy = at.y - point.y;
|
|
3752
|
+
const distSq = dx * dx + dy * dy;
|
|
3753
|
+
if (distSq < bestDistSq) {
|
|
3754
|
+
bestDistSq = distSq;
|
|
3755
|
+
bestPoint = at;
|
|
3756
|
+
bestParam = pathParam;
|
|
3757
|
+
}
|
|
3758
|
+
}
|
|
3759
|
+
return {
|
|
3760
|
+
point: bestPoint,
|
|
3761
|
+
pathParam: bestParam,
|
|
3762
|
+
distance: Math.sqrt(bestDistSq)
|
|
3763
|
+
};
|
|
3764
|
+
}
|
|
3765
|
+
//#endregion
|
|
3591
3766
|
//#region src/elements/paths/StraightPathStrategy.ts
|
|
3592
3767
|
/**
|
|
3593
3768
|
* Straight line path strategy
|
|
@@ -4696,69 +4871,6 @@ function getEdgeLabelRotation(options) {
|
|
|
4696
4871
|
if (angle < -Math.PI / 2) angle += Math.PI;
|
|
4697
4872
|
return angle;
|
|
4698
4873
|
}
|
|
4699
|
-
function getPathPointAt(path, type, position) {
|
|
4700
|
-
if (type !== "bezier" || path.length < 4) return getPointAlongPolyline(path, position);
|
|
4701
|
-
const samples = [];
|
|
4702
|
-
const steps = 20;
|
|
4703
|
-
for (let i = 1; i + 2 < path.length; i += 3) {
|
|
4704
|
-
const start = path[i - 1];
|
|
4705
|
-
const control1 = path[i];
|
|
4706
|
-
const control2 = path[i + 1];
|
|
4707
|
-
const end = path[i + 2];
|
|
4708
|
-
for (let sample = 0; sample <= steps; sample++) {
|
|
4709
|
-
const t = sample / steps;
|
|
4710
|
-
if (samples.length > 0 && t === 0) continue;
|
|
4711
|
-
samples.push(bezierPoint(start, control1, control2, end, t));
|
|
4712
|
-
}
|
|
4713
|
-
}
|
|
4714
|
-
return getPointAlongPolyline(samples, position);
|
|
4715
|
-
}
|
|
4716
|
-
function getPointAlongPolyline(path, position) {
|
|
4717
|
-
if (path.length === 0) return {
|
|
4718
|
-
point: {
|
|
4719
|
-
x: 0,
|
|
4720
|
-
y: 0
|
|
4721
|
-
},
|
|
4722
|
-
angle: 0
|
|
4723
|
-
};
|
|
4724
|
-
if (path.length === 1) return {
|
|
4725
|
-
point: path[0],
|
|
4726
|
-
angle: 0
|
|
4727
|
-
};
|
|
4728
|
-
let totalLength = 0;
|
|
4729
|
-
const segments = [];
|
|
4730
|
-
for (let i = 1; i < path.length; i++) {
|
|
4731
|
-
const start = path[i - 1];
|
|
4732
|
-
const end = path[i];
|
|
4733
|
-
const length = Math.hypot(end.x - start.x, end.y - start.y);
|
|
4734
|
-
segments.push({
|
|
4735
|
-
start,
|
|
4736
|
-
end,
|
|
4737
|
-
length
|
|
4738
|
-
});
|
|
4739
|
-
totalLength += length;
|
|
4740
|
-
}
|
|
4741
|
-
const targetLength = totalLength * Math.max(0, Math.min(1, position));
|
|
4742
|
-
let accumulated = 0;
|
|
4743
|
-
for (const segment of segments) {
|
|
4744
|
-
if (accumulated + segment.length >= targetLength) {
|
|
4745
|
-
const segmentPosition = segment.length > 0 ? (targetLength - accumulated) / segment.length : 0;
|
|
4746
|
-
return {
|
|
4747
|
-
point: {
|
|
4748
|
-
x: segment.start.x + segmentPosition * (segment.end.x - segment.start.x),
|
|
4749
|
-
y: segment.start.y + segmentPosition * (segment.end.y - segment.start.y)
|
|
4750
|
-
},
|
|
4751
|
-
angle: Math.atan2(segment.end.y - segment.start.y, segment.end.x - segment.start.x)
|
|
4752
|
-
};
|
|
4753
|
-
}
|
|
4754
|
-
accumulated += segment.length;
|
|
4755
|
-
}
|
|
4756
|
-
const lastSegment = segments[segments.length - 1];
|
|
4757
|
-
return {
|
|
4758
|
-
point: lastSegment.end,
|
|
4759
|
-
angle: Math.atan2(lastSegment.end.y - lastSegment.start.y, lastSegment.end.x - lastSegment.start.x)
|
|
4760
|
-
};
|
|
4761
|
-
}
|
|
4762
4874
|
//#endregion
|
|
4763
4875
|
//#region src/elements/Edge.ts
|
|
4764
4876
|
var DEFAULT_EDGE_STYLE = {
|
|
@@ -4825,6 +4937,7 @@ var Edge = class extends Element {
|
|
|
4825
4937
|
}
|
|
4826
4938
|
set from(value) {
|
|
4827
4939
|
this._from = value;
|
|
4940
|
+
this._bindingListener?.();
|
|
4828
4941
|
this.markDirty();
|
|
4829
4942
|
}
|
|
4830
4943
|
/**
|
|
@@ -4835,9 +4948,17 @@ var Edge = class extends Element {
|
|
|
4835
4948
|
}
|
|
4836
4949
|
set to(value) {
|
|
4837
4950
|
this._to = value;
|
|
4951
|
+
this._bindingListener?.();
|
|
4838
4952
|
this.markDirty();
|
|
4839
4953
|
}
|
|
4840
4954
|
/**
|
|
4955
|
+
* Notified when from/to binding changes (ports / outline params / node ids).
|
|
4956
|
+
* Used by DiagramRenderer to resync endpoints without doing so on every pan frame.
|
|
4957
|
+
*/
|
|
4958
|
+
setBindingListener(listener) {
|
|
4959
|
+
this._bindingListener = listener;
|
|
4960
|
+
}
|
|
4961
|
+
/**
|
|
4841
4962
|
* Edge type
|
|
4842
4963
|
*/
|
|
4843
4964
|
get type() {
|
|
@@ -5102,6 +5223,10 @@ var Edge = class extends Element {
|
|
|
5102
5223
|
* @param toDir Direction at end point (top, right, bottom, left)
|
|
5103
5224
|
*/
|
|
5104
5225
|
updateEndpoints(fromPoint, toPoint, fromDir, toDir, options) {
|
|
5226
|
+
if (this._path.length > 0 && this._fromPoint.x === fromPoint.x && this._fromPoint.y === fromPoint.y && this._toPoint.x === toPoint.x && this._toPoint.y === toPoint.y && this._fromDir === fromDir && this._toDir === toDir) {
|
|
5227
|
+
this._pathOptions = options;
|
|
5228
|
+
return;
|
|
5229
|
+
}
|
|
5105
5230
|
this._fromPoint = fromPoint;
|
|
5106
5231
|
this._toPoint = toPoint;
|
|
5107
5232
|
this._fromDir = fromDir;
|
|
@@ -5398,6 +5523,19 @@ var Edge = class extends Element {
|
|
|
5398
5523
|
});
|
|
5399
5524
|
}
|
|
5400
5525
|
/**
|
|
5526
|
+
* Point and tangent on the current path at a normalized length position (0..1).
|
|
5527
|
+
*/
|
|
5528
|
+
getPointAt(pathParam = .5) {
|
|
5529
|
+
if (this._path.length < 2) return null;
|
|
5530
|
+
return getPathPointAt(this._path, this._type, pathParam);
|
|
5531
|
+
}
|
|
5532
|
+
/**
|
|
5533
|
+
* Nearest point on this edge's path to `point` (world coordinates).
|
|
5534
|
+
*/
|
|
5535
|
+
getClosestPointOnPath(point) {
|
|
5536
|
+
return getClosestPointOnPath(this._path, this._type, point);
|
|
5537
|
+
}
|
|
5538
|
+
/**
|
|
5401
5539
|
* Get world position of label center along path.
|
|
5402
5540
|
*/
|
|
5403
5541
|
getLabelPosition() {
|
|
@@ -5592,22 +5730,27 @@ var ClipboardManager = class {
|
|
|
5592
5730
|
nodeIds.add(node.id);
|
|
5593
5731
|
}
|
|
5594
5732
|
}
|
|
5595
|
-
for (const edge of this.renderer.edges.values())
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5733
|
+
for (const edge of this.renderer.edges.values()) {
|
|
5734
|
+
const fromId = edge.from.nodeId;
|
|
5735
|
+
const toId = edge.to.nodeId;
|
|
5736
|
+
if (!fromId || !toId || !nodeIds.has(fromId) || !nodeIds.has(toId)) continue;
|
|
5737
|
+
edges.push({
|
|
5738
|
+
id: edge.id,
|
|
5739
|
+
from: edge.from,
|
|
5740
|
+
to: edge.to,
|
|
5741
|
+
type: edge.type,
|
|
5742
|
+
controlPoints: edge.controlPoints,
|
|
5743
|
+
arrowType: edge.arrowType,
|
|
5744
|
+
style: edge.style,
|
|
5745
|
+
styleClass: edge.styleClass,
|
|
5746
|
+
label: edge.label?.text,
|
|
5747
|
+
labelStyleClass: edge.label?.styleClass,
|
|
5748
|
+
labelOffset: edge.labelOffset !== 0 ? edge.labelOffset : void 0,
|
|
5749
|
+
labelBackground: edge.labelBackground,
|
|
5750
|
+
labelLineGap: edge.labelLineGap ? true : void 0,
|
|
5751
|
+
data: Object.keys(edge.data).length > 0 ? edge.data : void 0
|
|
5752
|
+
});
|
|
5753
|
+
}
|
|
5611
5754
|
this.clipboard = {
|
|
5612
5755
|
nodes,
|
|
5613
5756
|
edges
|
|
@@ -5634,8 +5777,11 @@ var ClipboardManager = class {
|
|
|
5634
5777
|
newNodes.push(this.nodeFactory(copyData));
|
|
5635
5778
|
}
|
|
5636
5779
|
for (const edgeData of this.clipboard.edges) {
|
|
5637
|
-
const
|
|
5638
|
-
const
|
|
5780
|
+
const fromKey = edgeData.from.nodeId;
|
|
5781
|
+
const toKey = edgeData.to.nodeId;
|
|
5782
|
+
if (!fromKey || !toKey) continue;
|
|
5783
|
+
const fromNodeId = idMap.get(fromKey);
|
|
5784
|
+
const toNodeId = idMap.get(toKey);
|
|
5639
5785
|
if (!fromNodeId || !toNodeId) continue;
|
|
5640
5786
|
const newId = `${edgeData.id}_copy_${Date.now()}`;
|
|
5641
5787
|
const copyEdge = {
|
|
@@ -5837,7 +5983,9 @@ var InteractionManager = class {
|
|
|
5837
5983
|
const idSet = new Set(nodeIds);
|
|
5838
5984
|
for (const edge of this.renderer.edges.values()) {
|
|
5839
5985
|
if (!edge.hasEditableControlPoints()) continue;
|
|
5840
|
-
|
|
5986
|
+
const fromId = edge.from.nodeId;
|
|
5987
|
+
const toId = edge.to.nodeId;
|
|
5988
|
+
if ((!fromId || !idSet.has(fromId)) && (!toId || !idSet.has(toId))) continue;
|
|
5841
5989
|
if (this.dragStartEditablePolylinePoints.has(edge.id)) continue;
|
|
5842
5990
|
this.dragStartEditablePolylinePoints.set(edge.id, clonePoints(edge.controlPoints));
|
|
5843
5991
|
}
|
|
@@ -6385,10 +6533,14 @@ var InteractionManager = class {
|
|
|
6385
6533
|
nodeGroupIds.set(child.id, list);
|
|
6386
6534
|
}
|
|
6387
6535
|
}
|
|
6388
|
-
for (const edge of this.renderer.edges.values())
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6536
|
+
for (const edge of this.renderer.edges.values()) {
|
|
6537
|
+
const fromId = edge.from.nodeId;
|
|
6538
|
+
const toId = edge.to.nodeId;
|
|
6539
|
+
if (fromId && nodeIds.has(fromId) || toId && nodeIds.has(toId)) {
|
|
6540
|
+
if (!edgeIds.has(edge.id)) {
|
|
6541
|
+
edgeIds.add(edge.id);
|
|
6542
|
+
edges.push(edge);
|
|
6543
|
+
}
|
|
6392
6544
|
}
|
|
6393
6545
|
}
|
|
6394
6546
|
return new CompositeCommand([{
|
|
@@ -6411,8 +6563,15 @@ var InteractionManager = class {
|
|
|
6411
6563
|
}]);
|
|
6412
6564
|
}
|
|
6413
6565
|
endpointsEqual(a, b) {
|
|
6414
|
-
if (a.
|
|
6566
|
+
if ((a.edgeId ?? null) !== (b.edgeId ?? null)) return false;
|
|
6567
|
+
if ((a.nodeId ?? null) !== (b.nodeId ?? null)) return false;
|
|
6415
6568
|
if ((a.portId ?? null) !== (b.portId ?? null)) return false;
|
|
6569
|
+
const aPath = a.pathParam;
|
|
6570
|
+
const bPath = b.pathParam;
|
|
6571
|
+
if (aPath !== void 0 || bPath !== void 0) {
|
|
6572
|
+
if (aPath === void 0 || bPath === void 0) return false;
|
|
6573
|
+
if (Math.abs(aPath - bPath) >= 1e-9) return false;
|
|
6574
|
+
}
|
|
6416
6575
|
const aParam = a.outlineParam;
|
|
6417
6576
|
const bParam = b.outlineParam;
|
|
6418
6577
|
if (aParam === void 0 && bParam === void 0) return true;
|
|
@@ -6812,7 +6971,16 @@ var ContextMenuManager = class extends EventEmitter {
|
|
|
6812
6971
|
}
|
|
6813
6972
|
};
|
|
6814
6973
|
//#endregion
|
|
6974
|
+
//#region src/types.ts
|
|
6975
|
+
function isNodeEdgeEndpoint(endpoint) {
|
|
6976
|
+
return typeof endpoint.nodeId === "string" && endpoint.nodeId.length > 0;
|
|
6977
|
+
}
|
|
6978
|
+
function isEdgeEdgeEndpoint(endpoint) {
|
|
6979
|
+
return typeof endpoint.edgeId === "string" && endpoint.edgeId.length > 0;
|
|
6980
|
+
}
|
|
6981
|
+
//#endregion
|
|
6815
6982
|
//#region src/core/EdgeEndpointUpdater.ts
|
|
6983
|
+
var EDGE_ATTACH_MAX_PASSES = 3;
|
|
6816
6984
|
var EdgeEndpointUpdater = class {
|
|
6817
6985
|
constructor(host) {
|
|
6818
6986
|
this.host = host;
|
|
@@ -6826,26 +6994,73 @@ var EdgeEndpointUpdater = class {
|
|
|
6826
6994
|
update(includeRoutingObstacles) {
|
|
6827
6995
|
const obstacles = includeRoutingObstacles ? this.host.getNodeObstacles() : void 0;
|
|
6828
6996
|
const nodes = this.host.getNodes();
|
|
6829
|
-
|
|
6997
|
+
const edges = this.host.getEdges();
|
|
6998
|
+
const nodeNode = [];
|
|
6999
|
+
const edgeAttached = [];
|
|
7000
|
+
for (const edge of edges.values()) {
|
|
6830
7001
|
if (!edge.autoUpdateEndpoints) continue;
|
|
6831
|
-
|
|
6832
|
-
|
|
6833
|
-
|
|
6834
|
-
|
|
6835
|
-
|
|
6836
|
-
|
|
6837
|
-
edge.
|
|
7002
|
+
if (isEdgeEdgeEndpoint(edge.from) || isEdgeEdgeEndpoint(edge.to)) edgeAttached.push(edge);
|
|
7003
|
+
else nodeNode.push(edge);
|
|
7004
|
+
}
|
|
7005
|
+
for (const edge of nodeNode) this.updateNodeNodeEdge(edge, nodes, includeRoutingObstacles, obstacles);
|
|
7006
|
+
for (let pass = 0; pass < EDGE_ATTACH_MAX_PASSES; pass++) {
|
|
7007
|
+
let changed = false;
|
|
7008
|
+
for (const edge of edgeAttached) if (this.updateEdgeAttachedEdge(edge, nodes, edges, obstacles)) changed = true;
|
|
7009
|
+
if (!changed) break;
|
|
7010
|
+
}
|
|
7011
|
+
}
|
|
7012
|
+
updateNodeNodeEdge(edge, nodes, includeRoutingObstacles, obstacles) {
|
|
7013
|
+
if (!isNodeEdgeEndpoint(edge.from) || !isNodeEdgeEndpoint(edge.to)) return;
|
|
7014
|
+
const fromNode = nodes.get(edge.from.nodeId);
|
|
7015
|
+
const toNode = nodes.get(edge.to.nodeId);
|
|
7016
|
+
if (!fromNode || !toNode) return;
|
|
7017
|
+
if (includeRoutingObstacles) this.lockAnchors(edge, fromNode, toNode);
|
|
7018
|
+
const from = this.resolveNodeEndpoint(fromNode, edge.from);
|
|
7019
|
+
const to = this.resolveNodeEndpoint(toNode, edge.to);
|
|
7020
|
+
edge.updateEndpoints(from.point, to.point, from.direction, to.direction, obstacles ? { obstacles } : void 0);
|
|
7021
|
+
}
|
|
7022
|
+
/**
|
|
7023
|
+
* @returns true when the resolved geometry changed enough to warrant another pass
|
|
7024
|
+
*/
|
|
7025
|
+
updateEdgeAttachedEdge(edge, nodes, edges, obstacles) {
|
|
7026
|
+
const from = this.resolveAnyEndpoint(edge.from, nodes, edges);
|
|
7027
|
+
const to = this.resolveAnyEndpoint(edge.to, nodes, edges);
|
|
7028
|
+
if (!from || !to) return false;
|
|
7029
|
+
const before = edge.path;
|
|
7030
|
+
const beforeKey = before.length >= 2 ? `${before[0].x},${before[0].y}:${before[before.length - 1].x},${before[before.length - 1].y}` : "";
|
|
7031
|
+
edge.updateEndpoints(from.point, to.point, from.direction, to.direction, obstacles ? { obstacles } : void 0);
|
|
7032
|
+
const after = edge.path;
|
|
7033
|
+
return beforeKey !== (after.length >= 2 ? `${after[0].x},${after[0].y}:${after[after.length - 1].x},${after[after.length - 1].y}` : "");
|
|
7034
|
+
}
|
|
7035
|
+
resolveAnyEndpoint(endpoint, nodes, edges) {
|
|
7036
|
+
if (isEdgeEdgeEndpoint(endpoint)) return this.resolveEdgeEndpoint(endpoint, edges);
|
|
7037
|
+
if (isNodeEdgeEndpoint(endpoint)) {
|
|
7038
|
+
const node = nodes.get(endpoint.nodeId);
|
|
7039
|
+
if (!node) return null;
|
|
7040
|
+
return this.resolveNodeEndpoint(node, endpoint);
|
|
6838
7041
|
}
|
|
7042
|
+
return null;
|
|
7043
|
+
}
|
|
7044
|
+
resolveEdgeEndpoint(endpoint, edges) {
|
|
7045
|
+
const host = edges.get(endpoint.edgeId);
|
|
7046
|
+
if (!host) return null;
|
|
7047
|
+
const pathParam = endpoint.pathParam ?? .5;
|
|
7048
|
+
const at = host.getPointAt(pathParam);
|
|
7049
|
+
if (!at) return null;
|
|
7050
|
+
return {
|
|
7051
|
+
point: at.point,
|
|
7052
|
+
direction: directionFromAngle(at.angle)
|
|
7053
|
+
};
|
|
6839
7054
|
}
|
|
6840
7055
|
lockAnchors(edge, fromNode, toNode) {
|
|
6841
|
-
if (edge.lockAnchors && edge.from.outlineParam === void 0 && !edge.from.portId) {
|
|
7056
|
+
if (isNodeEdgeEndpoint(edge.from) && edge.lockAnchors && edge.from.outlineParam === void 0 && !edge.from.portId) {
|
|
6842
7057
|
const anchor = fromNode.getNearestAnchor(toNode.getCenter());
|
|
6843
7058
|
if (anchor) edge.from = {
|
|
6844
7059
|
...edge.from,
|
|
6845
7060
|
portId: `${ANCHOR_PORT_PREFIX}${anchor.id}`
|
|
6846
7061
|
};
|
|
6847
7062
|
}
|
|
6848
|
-
if (edge.lockAnchors && edge.to.outlineParam === void 0 && !edge.to.portId) {
|
|
7063
|
+
if (isNodeEdgeEndpoint(edge.to) && edge.lockAnchors && edge.to.outlineParam === void 0 && !edge.to.portId) {
|
|
6849
7064
|
const anchor = toNode.getNearestAnchor(fromNode.getCenter());
|
|
6850
7065
|
if (anchor) edge.to = {
|
|
6851
7066
|
...edge.to,
|
|
@@ -6853,18 +7068,18 @@ var EdgeEndpointUpdater = class {
|
|
|
6853
7068
|
};
|
|
6854
7069
|
}
|
|
6855
7070
|
}
|
|
6856
|
-
|
|
6857
|
-
if (portId) {
|
|
6858
|
-
const anchorId = portId.slice(ANCHOR_PORT_PREFIX.length);
|
|
7071
|
+
resolveNodeEndpoint(node, endpoint) {
|
|
7072
|
+
if (endpoint.portId) {
|
|
7073
|
+
const anchorId = endpoint.portId.slice(ANCHOR_PORT_PREFIX.length);
|
|
6859
7074
|
const point = node.getAnchorPointById(anchorId);
|
|
6860
7075
|
return point ? {
|
|
6861
7076
|
point,
|
|
6862
7077
|
direction: anchorId.split(":")[0]
|
|
6863
7078
|
} : { point: node.getCenter() };
|
|
6864
7079
|
}
|
|
6865
|
-
if (outlineParam !== void 0) return {
|
|
6866
|
-
point: node.getConnectionPointAtOutlineParam(outlineParam),
|
|
6867
|
-
direction: getDirectionFromOutlineParam(outlineParam)
|
|
7080
|
+
if (endpoint.outlineParam !== void 0) return {
|
|
7081
|
+
point: node.getConnectionPointAtOutlineParam(endpoint.outlineParam),
|
|
7082
|
+
direction: getDirectionFromOutlineParam(endpoint.outlineParam)
|
|
6868
7083
|
};
|
|
6869
7084
|
return { point: node.getCenter() };
|
|
6870
7085
|
}
|
|
@@ -7232,6 +7447,7 @@ var DiagramRenderer = class extends EventEmitter {
|
|
|
7232
7447
|
this._offsetY = 0;
|
|
7233
7448
|
this._dirty = true;
|
|
7234
7449
|
this._styleDirty = true;
|
|
7450
|
+
this._contentDirty = true;
|
|
7235
7451
|
this._destroyed = false;
|
|
7236
7452
|
this._canvasRect = null;
|
|
7237
7453
|
this._nodeObstaclesCache = null;
|
|
@@ -7536,12 +7752,11 @@ var DiagramRenderer = class extends EventEmitter {
|
|
|
7536
7752
|
* Add a node to the diagram
|
|
7537
7753
|
*/
|
|
7538
7754
|
addNode(node) {
|
|
7539
|
-
node.setDirtyListener(() => this.
|
|
7755
|
+
node.setDirtyListener(() => this.markContentDirty());
|
|
7540
7756
|
node.setAttachToOutlineGetter(() => this._attachToOutline);
|
|
7541
7757
|
this._nodes.set(node.id, node);
|
|
7542
|
-
this._nodeObstaclesCache = null;
|
|
7543
7758
|
this.animationManager.registerEnter(node.id);
|
|
7544
|
-
this.
|
|
7759
|
+
this.markContentDirty();
|
|
7545
7760
|
this.emit("nodeAdd", node);
|
|
7546
7761
|
}
|
|
7547
7762
|
/**
|
|
@@ -7572,11 +7787,12 @@ var DiagramRenderer = class extends EventEmitter {
|
|
|
7572
7787
|
*/
|
|
7573
7788
|
addEdge(edge) {
|
|
7574
7789
|
edge.setDirtyListener(() => this.markDirty());
|
|
7790
|
+
edge.setBindingListener(() => this.markContentDirty());
|
|
7575
7791
|
this._edges.set(edge.id, edge);
|
|
7576
|
-
this._updateEdgeIndex(edge.id, edge.from.nodeId, true);
|
|
7577
|
-
this._updateEdgeIndex(edge.id, edge.to.nodeId, true);
|
|
7792
|
+
if (edge.from.nodeId) this._updateEdgeIndex(edge.id, edge.from.nodeId, true);
|
|
7793
|
+
if (edge.to.nodeId) this._updateEdgeIndex(edge.id, edge.to.nodeId, true);
|
|
7578
7794
|
this.animationManager.registerEnter(edge.id);
|
|
7579
|
-
this.
|
|
7795
|
+
this.markContentDirty();
|
|
7580
7796
|
this.emit("edgeAdd", edge);
|
|
7581
7797
|
}
|
|
7582
7798
|
/**
|
|
@@ -7615,21 +7831,27 @@ var DiagramRenderer = class extends EventEmitter {
|
|
|
7615
7831
|
if (edgesToRemove) for (const edgeId of Array.from(edgesToRemove)) this.removeEdgeImmediate(edgeId);
|
|
7616
7832
|
for (const group of this._groups.values()) group.removeChild(nodeId);
|
|
7617
7833
|
this._nodes.delete(nodeId);
|
|
7618
|
-
this._nodeObstaclesCache = null;
|
|
7619
7834
|
node.setDirtyListener(void 0);
|
|
7620
7835
|
node.setAttachToOutlineGetter(void 0);
|
|
7621
|
-
this.
|
|
7836
|
+
this.markContentDirty();
|
|
7622
7837
|
this.emit("nodeRemove", node);
|
|
7623
7838
|
return true;
|
|
7624
7839
|
}
|
|
7625
7840
|
removeEdgeImmediate(edgeId) {
|
|
7626
7841
|
const edge = this._edges.get(edgeId);
|
|
7627
7842
|
if (edge === void 0) return false;
|
|
7628
|
-
|
|
7629
|
-
|
|
7843
|
+
const dependents = [];
|
|
7844
|
+
for (const other of this._edges.values()) {
|
|
7845
|
+
if (other.id === edgeId) continue;
|
|
7846
|
+
if (other.from.edgeId === edgeId || other.to.edgeId === edgeId) dependents.push(other.id);
|
|
7847
|
+
}
|
|
7848
|
+
for (const dependentId of dependents) this.removeEdgeImmediate(dependentId);
|
|
7849
|
+
if (edge.from.nodeId) this._updateEdgeIndex(edgeId, edge.from.nodeId, false);
|
|
7850
|
+
if (edge.to.nodeId) this._updateEdgeIndex(edgeId, edge.to.nodeId, false);
|
|
7630
7851
|
this._edges.delete(edgeId);
|
|
7631
7852
|
edge.setDirtyListener(void 0);
|
|
7632
|
-
|
|
7853
|
+
edge.setBindingListener(void 0);
|
|
7854
|
+
this.markContentDirty();
|
|
7633
7855
|
this.emit("edgeRemove", edge);
|
|
7634
7856
|
return true;
|
|
7635
7857
|
}
|
|
@@ -7709,12 +7931,22 @@ var DiagramRenderer = class extends EventEmitter {
|
|
|
7709
7931
|
for (const n of this._nodes.values()) n.setBadgeHover(n.id === hoveredNodeId ? hoveredIndex : -1);
|
|
7710
7932
|
}
|
|
7711
7933
|
/**
|
|
7712
|
-
* Mark the diagram as needing re-render
|
|
7934
|
+
* Mark the diagram as needing re-render (viewport / cosmetics).
|
|
7935
|
+
* Does not resync edge endpoints — use {@link markContentDirty} after layout changes.
|
|
7713
7936
|
*/
|
|
7714
7937
|
markDirty() {
|
|
7715
7938
|
this._dirty = true;
|
|
7716
7939
|
}
|
|
7717
7940
|
/**
|
|
7941
|
+
* Mark that node/edge layout changed and edge endpoints must be recalculated.
|
|
7942
|
+
* Pan/zoom should call {@link markDirty} only so path routing is not redone every frame.
|
|
7943
|
+
*/
|
|
7944
|
+
markContentDirty() {
|
|
7945
|
+
this._contentDirty = true;
|
|
7946
|
+
this._nodeObstaclesCache = null;
|
|
7947
|
+
this._dirty = true;
|
|
7948
|
+
}
|
|
7949
|
+
/**
|
|
7718
7950
|
* Force an immediate render
|
|
7719
7951
|
*/
|
|
7720
7952
|
render() {
|
|
@@ -7792,7 +8024,6 @@ var DiagramRenderer = class extends EventEmitter {
|
|
|
7792
8024
|
}
|
|
7793
8025
|
renderFrame(now) {
|
|
7794
8026
|
this.frameTime = now;
|
|
7795
|
-
this._nodeObstaclesCache = null;
|
|
7796
8027
|
const ctx = this.ctx;
|
|
7797
8028
|
const { width, height, backgroundColor } = this.options;
|
|
7798
8029
|
ctx.setTransform(this.devicePixelRatio, 0, 0, this.devicePixelRatio, 0, 0);
|
|
@@ -7810,7 +8041,10 @@ var DiagramRenderer = class extends EventEmitter {
|
|
|
7810
8041
|
this.renderElementWithAnimation(ctx, group, () => group.render(ctx));
|
|
7811
8042
|
group.clearDirty();
|
|
7812
8043
|
}
|
|
7813
|
-
this.
|
|
8044
|
+
if (this._contentDirty) {
|
|
8045
|
+
this.edgeEndpointUpdater.updateAll();
|
|
8046
|
+
this._contentDirty = false;
|
|
8047
|
+
}
|
|
7814
8048
|
for (const node of this._nodes.values()) if (node.visible) {
|
|
7815
8049
|
this.renderElementWithAnimation(ctx, node, () => node.render(ctx));
|
|
7816
8050
|
node.clearDirty();
|
|
@@ -8054,8 +8288,10 @@ var SearchManager = class {
|
|
|
8054
8288
|
for (const edge of this.renderer.edges.values()) {
|
|
8055
8289
|
if (scope === "nodes") continue;
|
|
8056
8290
|
const matchesEdge = this.matchesEdge(edge, options);
|
|
8057
|
-
const
|
|
8058
|
-
const
|
|
8291
|
+
const fromId = edge.from.nodeId;
|
|
8292
|
+
const toId = edge.to.nodeId;
|
|
8293
|
+
const fromVisible = fromId ? this.renderer.getNode(fromId)?.visible ?? true : true;
|
|
8294
|
+
const toVisible = toId ? this.renderer.getNode(toId)?.visible ?? true : true;
|
|
8059
8295
|
edge.visible = matchesEdge && fromVisible && toVisible;
|
|
8060
8296
|
}
|
|
8061
8297
|
this.renderer.markDirty();
|
|
@@ -8414,7 +8650,7 @@ var svgTextCache = new LRUCache(100);
|
|
|
8414
8650
|
function fetchSvgText(url) {
|
|
8415
8651
|
let promise = svgTextCache.get(url);
|
|
8416
8652
|
if (!promise) {
|
|
8417
|
-
promise = fetch(url).then((r) => r.ok ? r.text() : "");
|
|
8653
|
+
promise = fetch(url).then((r) => r.ok ? r.text() : "").catch(() => "");
|
|
8418
8654
|
svgTextCache.set(url, promise);
|
|
8419
8655
|
}
|
|
8420
8656
|
return promise;
|
|
@@ -8432,6 +8668,32 @@ function readSvgFromUrlSync(url) {
|
|
|
8432
8668
|
} catch {}
|
|
8433
8669
|
return null;
|
|
8434
8670
|
}
|
|
8671
|
+
/**
|
|
8672
|
+
* Resolve a source string to an href suitable for Image/SVG (async, with tint).
|
|
8673
|
+
*/
|
|
8674
|
+
async function resolveTintedSvgDataUrl(source, options = {}) {
|
|
8675
|
+
const { strokeColor, fillColor } = options;
|
|
8676
|
+
if (isSvgMarkup(source)) return svgToDataUrl(tintSvg(source, strokeColor, fillColor));
|
|
8677
|
+
if (isSvgUrl(source) || source.toLowerCase().endsWith(".svg")) {
|
|
8678
|
+
const svgText = await fetchSvgText(source);
|
|
8679
|
+
if (!svgText) return null;
|
|
8680
|
+
return svgToDataUrl(tintSvg(svgText, strokeColor, fillColor));
|
|
8681
|
+
}
|
|
8682
|
+
return null;
|
|
8683
|
+
}
|
|
8684
|
+
/**
|
|
8685
|
+
* Resolve a source to a tinted data URL synchronously (SvgExporter path).
|
|
8686
|
+
*/
|
|
8687
|
+
function resolveTintedSvgDataUrlSync(source, options = {}) {
|
|
8688
|
+
const { strokeColor, fillColor } = options;
|
|
8689
|
+
if (isSvgMarkup(source)) return svgToDataUrl(tintSvg(source, strokeColor, fillColor));
|
|
8690
|
+
if (source.toLowerCase().endsWith(".svg")) {
|
|
8691
|
+
const svgText = readSvgFromUrlSync(source);
|
|
8692
|
+
if (!svgText) return null;
|
|
8693
|
+
return svgToDataUrl(tintSvg(svgText, strokeColor, fillColor));
|
|
8694
|
+
}
|
|
8695
|
+
return null;
|
|
8696
|
+
}
|
|
8435
8697
|
//#endregion
|
|
8436
8698
|
//#region src/utils/iconLayout.ts
|
|
8437
8699
|
/**
|
|
@@ -8616,22 +8878,20 @@ var NodeImage = class {
|
|
|
8616
8878
|
if (source instanceof HTMLImageElement) return;
|
|
8617
8879
|
const version = ++this._sourceVersion;
|
|
8618
8880
|
if (isSvgMarkup(source)) {
|
|
8619
|
-
const
|
|
8881
|
+
const resolvedSource = resolveTintedSvgDataUrlSync(source, {
|
|
8882
|
+
strokeColor: this._options.strokeColor,
|
|
8883
|
+
fillColor: this._options.fillColor
|
|
8884
|
+
});
|
|
8620
8885
|
if (version !== this._sourceVersion) return;
|
|
8621
|
-
this._image.src =
|
|
8886
|
+
this._image.src = resolvedSource ?? source;
|
|
8622
8887
|
return;
|
|
8623
8888
|
}
|
|
8624
|
-
|
|
8625
|
-
|
|
8626
|
-
|
|
8627
|
-
|
|
8628
|
-
const tinted = tintSvg(svgText, this._options.strokeColor, this._options.fillColor);
|
|
8629
|
-
this._image.src = svgToDataUrl(tinted);
|
|
8630
|
-
return;
|
|
8631
|
-
}
|
|
8632
|
-
}
|
|
8889
|
+
const resolvedSource = await resolveTintedSvgDataUrl(source, {
|
|
8890
|
+
strokeColor: this._options.strokeColor,
|
|
8891
|
+
fillColor: this._options.fillColor
|
|
8892
|
+
});
|
|
8633
8893
|
if (version !== this._sourceVersion) return;
|
|
8634
|
-
this._image.src = source;
|
|
8894
|
+
this._image.src = resolvedSource ?? source;
|
|
8635
8895
|
}
|
|
8636
8896
|
attachHandlers() {
|
|
8637
8897
|
if (this._image.complete && this._image.naturalWidth > 0) {
|
|
@@ -10703,16 +10963,8 @@ var CIcon = class extends CompositeComponentBase {
|
|
|
10703
10963
|
}
|
|
10704
10964
|
}
|
|
10705
10965
|
async loadSource(source) {
|
|
10706
|
-
|
|
10707
|
-
|
|
10708
|
-
this._image.src = svgToDataUrl(tinted);
|
|
10709
|
-
} else if (isSvgUrl(source) && this._fillColor) try {
|
|
10710
|
-
const tinted = tintSvg(await fetchSvgText(source), void 0, this._fillColor);
|
|
10711
|
-
this._image.src = svgToDataUrl(tinted);
|
|
10712
|
-
} catch {
|
|
10713
|
-
this._image.src = source;
|
|
10714
|
-
}
|
|
10715
|
-
else this._image.src = source;
|
|
10966
|
+
const resolvedSource = await resolveTintedSvgDataUrl(source, { fillColor: this._fillColor });
|
|
10967
|
+
this._image.src = resolvedSource ?? source;
|
|
10716
10968
|
}
|
|
10717
10969
|
measure(_ctx) {
|
|
10718
10970
|
return {
|
|
@@ -10761,7 +11013,8 @@ var CIcon = class extends CompositeComponentBase {
|
|
|
10761
11013
|
const drawHeight = Math.min(this._height, bounds.height);
|
|
10762
11014
|
const dx = bounds.x + (bounds.width - drawWidth) / 2;
|
|
10763
11015
|
const dy = bounds.y + (bounds.height - drawHeight) / 2;
|
|
10764
|
-
|
|
11016
|
+
const source = resolveTintedSvgDataUrlSync(this._source, { fillColor: this._fillColor }) ?? this._source;
|
|
11017
|
+
svg += `<image href="${escapeXmlAttr(source)}" x="${dx}" y="${dy}" width="${drawWidth}" height="${drawHeight}" />`;
|
|
10765
11018
|
return svg;
|
|
10766
11019
|
}
|
|
10767
11020
|
};
|
|
@@ -12180,9 +12433,14 @@ var Serializer = class {
|
|
|
12180
12433
|
if (!Array.isArray(data.nodes)) throw new SerializerValidationError("Invalid or missing nodes array");
|
|
12181
12434
|
if (!Array.isArray(data.edges)) throw new SerializerValidationError("Invalid or missing edges array");
|
|
12182
12435
|
const nodeIds = new Set(data.nodes.map((n) => n.id));
|
|
12436
|
+
const edgeIds = new Set(data.edges.map((e) => e.id));
|
|
12183
12437
|
for (const edge of data.edges) {
|
|
12184
|
-
if (
|
|
12185
|
-
|
|
12438
|
+
if (edge.from.edgeId) {
|
|
12439
|
+
if (!edgeIds.has(edge.from.edgeId)) throw new SerializerValidationError(`Edge "${edge.id}" references non-existent host edge "${edge.from.edgeId}"`);
|
|
12440
|
+
} else if (!edge.from.nodeId || !nodeIds.has(edge.from.nodeId)) throw new SerializerValidationError(`Edge "${edge.id}" references non-existent source node "${edge.from.nodeId}"`);
|
|
12441
|
+
if (edge.to.edgeId) {
|
|
12442
|
+
if (!edgeIds.has(edge.to.edgeId)) throw new SerializerValidationError(`Edge "${edge.id}" references non-existent host edge "${edge.to.edgeId}"`);
|
|
12443
|
+
} else if (!edge.to.nodeId || !nodeIds.has(edge.to.nodeId)) throw new SerializerValidationError(`Edge "${edge.id}" references non-existent target node "${edge.to.nodeId}"`);
|
|
12186
12444
|
}
|
|
12187
12445
|
}
|
|
12188
12446
|
/**
|
|
@@ -12965,12 +13223,10 @@ var SvgExporter = class {
|
|
|
12965
13223
|
const source = opts.source;
|
|
12966
13224
|
if (source instanceof HTMLImageElement) return source.src;
|
|
12967
13225
|
if (!source) return "";
|
|
12968
|
-
|
|
12969
|
-
|
|
12970
|
-
|
|
12971
|
-
|
|
12972
|
-
}
|
|
12973
|
-
return source;
|
|
13226
|
+
return resolveTintedSvgDataUrlSync(source, {
|
|
13227
|
+
strokeColor: opts.strokeColor,
|
|
13228
|
+
fillColor: opts.fillColor
|
|
13229
|
+
}) ?? source;
|
|
12974
13230
|
}
|
|
12975
13231
|
escapeAttribute(value) {
|
|
12976
13232
|
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -13027,8 +13283,11 @@ var AutoRouting = class {
|
|
|
13027
13283
|
const type = options.type ?? "polyline";
|
|
13028
13284
|
for (const edge of renderer.edges.values()) {
|
|
13029
13285
|
edge.type = type;
|
|
13030
|
-
const
|
|
13031
|
-
const
|
|
13286
|
+
const fromId = edge.from.nodeId;
|
|
13287
|
+
const toId = edge.to.nodeId;
|
|
13288
|
+
if (!fromId || !toId) continue;
|
|
13289
|
+
const fromNode = renderer.getNode(fromId);
|
|
13290
|
+
const toNode = renderer.getNode(toId);
|
|
13032
13291
|
if (!fromNode || !toNode) continue;
|
|
13033
13292
|
const fromPoint = edge.from.portId !== void 0 ? fromNode.getPortPosition(edge.from.portId) ?? fromNode.getCenter() : fromNode.getCenter();
|
|
13034
13293
|
const toPoint = edge.to.portId !== void 0 ? toNode.getPortPosition(edge.to.portId) ?? toNode.getCenter() : toNode.getCenter();
|
|
@@ -13503,6 +13762,6 @@ function applyEdgeStyle(ctx, style) {
|
|
|
13503
13762
|
return baseOpacity * strokeOpacity;
|
|
13504
13763
|
}
|
|
13505
13764
|
//#endregion
|
|
13506
|
-
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, RulersOverlay, SearchManager, SelectionManager, Serializer, ShapeFactories, StraightPathStrategy, StyleManager, SvgExporter, TextLabel, alignNodes, angle, applyEdgeStyle, applyNodeStyle, bezierPoint, boundsCenter, calculateBezierControlPoints, clamp, clonePoints, container, deserializeCComponent, distance, distanceToSegment, distributeNodes, divider, drawRoundedRectPath, expandBounds, flexLayout, generateId, icon, isCornerPlacement, lerp, mergeBounds, normalizeSides, pointInEllipse, pointInRect, rectIntersection, rectUnion, rectsIntersect, renderFillAndStroke, resetIdCounter, resetPortIdCounter, rotatePoint, segmentRectIntersections, shape, snapPointToGrid, snapToGrid, text };
|
|
13765
|
+
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, 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, rotatePoint, segmentRectIntersections, shape, snapPointToGrid, snapToGrid, text };
|
|
13507
13766
|
|
|
13508
13767
|
//# sourceMappingURL=papirus.js.map
|