@ngroznykh/papirus 0.6.5 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +15 -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 +439 -167
- 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() {
|
|
@@ -4848,7 +4969,7 @@ var Edge = class extends Element {
|
|
|
4848
4969
|
this._type = value;
|
|
4849
4970
|
this._pathStrategy = this.getPathStrategy(value);
|
|
4850
4971
|
if (value !== "editable-polyline" && this._controlPoints?.length) this.controlPoints = void 0;
|
|
4851
|
-
this.
|
|
4972
|
+
else this.recalculatePath();
|
|
4852
4973
|
}
|
|
4853
4974
|
}
|
|
4854
4975
|
/**
|
|
@@ -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,71 @@ 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.
|
|
6838
|
-
|
|
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, toNode.getCenter(), edge.lockAnchors);
|
|
7019
|
+
const to = this.resolveNodeEndpoint(toNode, edge.to, fromNode.getCenter(), edge.lockAnchors);
|
|
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 fromEdge = isEdgeEdgeEndpoint(edge.from) ? this.resolveEdgeEndpoint(edge.from, edges) : null;
|
|
7027
|
+
const toEdge = isEdgeEdgeEndpoint(edge.to) ? this.resolveEdgeEndpoint(edge.to, edges) : null;
|
|
7028
|
+
const from = fromEdge ?? (isNodeEdgeEndpoint(edge.from) ? this.resolveNodeEnd(edge.from, nodes, toEdge?.point, edge.lockAnchors) : null);
|
|
7029
|
+
const to = toEdge ?? (isNodeEdgeEndpoint(edge.to) ? this.resolveNodeEnd(edge.to, nodes, from?.point, edge.lockAnchors) : null);
|
|
7030
|
+
if (!from || !to) return false;
|
|
7031
|
+
const before = edge.path;
|
|
7032
|
+
const beforeKey = before.length >= 2 ? `${before[0].x},${before[0].y}:${before[before.length - 1].x},${before[before.length - 1].y}` : "";
|
|
7033
|
+
edge.updateEndpoints(from.point, to.point, from.direction, to.direction, obstacles ? { obstacles } : void 0);
|
|
7034
|
+
const after = edge.path;
|
|
7035
|
+
return beforeKey !== (after.length >= 2 ? `${after[0].x},${after[0].y}:${after[after.length - 1].x},${after[after.length - 1].y}` : "");
|
|
7036
|
+
}
|
|
7037
|
+
resolveNodeEnd(endpoint, nodes, toward, lockAnchors) {
|
|
7038
|
+
const node = nodes.get(endpoint.nodeId);
|
|
7039
|
+
if (!node) return null;
|
|
7040
|
+
return this.resolveNodeEndpoint(node, endpoint, toward, lockAnchors);
|
|
7041
|
+
}
|
|
7042
|
+
resolveEdgeEndpoint(endpoint, edges) {
|
|
7043
|
+
const host = edges.get(endpoint.edgeId);
|
|
7044
|
+
if (!host) return null;
|
|
7045
|
+
const pathParam = endpoint.pathParam ?? .5;
|
|
7046
|
+
const at = host.getPointAt(pathParam);
|
|
7047
|
+
if (!at) return null;
|
|
7048
|
+
return {
|
|
7049
|
+
point: at.point,
|
|
7050
|
+
direction: directionFromAngle(at.angle)
|
|
7051
|
+
};
|
|
6839
7052
|
}
|
|
6840
7053
|
lockAnchors(edge, fromNode, toNode) {
|
|
6841
|
-
if (edge.lockAnchors && edge.from.outlineParam === void 0 && !edge.from.portId) {
|
|
7054
|
+
if (isNodeEdgeEndpoint(edge.from) && edge.lockAnchors && edge.from.outlineParam === void 0 && !edge.from.portId) {
|
|
6842
7055
|
const anchor = fromNode.getNearestAnchor(toNode.getCenter());
|
|
6843
7056
|
if (anchor) edge.from = {
|
|
6844
7057
|
...edge.from,
|
|
6845
7058
|
portId: `${ANCHOR_PORT_PREFIX}${anchor.id}`
|
|
6846
7059
|
};
|
|
6847
7060
|
}
|
|
6848
|
-
if (edge.lockAnchors && edge.to.outlineParam === void 0 && !edge.to.portId) {
|
|
7061
|
+
if (isNodeEdgeEndpoint(edge.to) && edge.lockAnchors && edge.to.outlineParam === void 0 && !edge.to.portId) {
|
|
6849
7062
|
const anchor = toNode.getNearestAnchor(fromNode.getCenter());
|
|
6850
7063
|
if (anchor) edge.to = {
|
|
6851
7064
|
...edge.to,
|
|
@@ -6853,21 +7066,36 @@ var EdgeEndpointUpdater = class {
|
|
|
6853
7066
|
};
|
|
6854
7067
|
}
|
|
6855
7068
|
}
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
7069
|
+
/**
|
|
7070
|
+
* Resolve a node endpoint:
|
|
7071
|
+
* - lockAnchors + portId → fixed side port (wins over leftover outlineParam)
|
|
7072
|
+
* - outlineParam → fixed point on outline (attach-to-outline)
|
|
7073
|
+
* - otherwise → floating nearest port/outline toward the other end
|
|
7074
|
+
*/
|
|
7075
|
+
resolveNodeEndpoint(node, endpoint, toward, lockAnchors) {
|
|
7076
|
+
if (lockAnchors && isNodeEdgeEndpoint(endpoint) && endpoint.portId) {
|
|
7077
|
+
const anchorId = endpoint.portId.slice(ANCHOR_PORT_PREFIX.length);
|
|
6859
7078
|
const point = node.getAnchorPointById(anchorId);
|
|
6860
|
-
|
|
7079
|
+
if (point) return {
|
|
6861
7080
|
point,
|
|
6862
7081
|
direction: anchorId.split(":")[0]
|
|
6863
|
-
}
|
|
7082
|
+
};
|
|
6864
7083
|
}
|
|
6865
|
-
if (outlineParam !== void 0) return {
|
|
6866
|
-
point: node.getConnectionPointAtOutlineParam(outlineParam),
|
|
6867
|
-
direction: getDirectionFromOutlineParam(outlineParam)
|
|
7084
|
+
if (isNodeEdgeEndpoint(endpoint) && endpoint.outlineParam !== void 0) return {
|
|
7085
|
+
point: node.getConnectionPointAtOutlineParam(endpoint.outlineParam),
|
|
7086
|
+
direction: getDirectionFromOutlineParam(endpoint.outlineParam)
|
|
6868
7087
|
};
|
|
7088
|
+
if (toward) return this.resolveFloatingEndpoint(node, toward);
|
|
6869
7089
|
return { point: node.getCenter() };
|
|
6870
7090
|
}
|
|
7091
|
+
resolveFloatingEndpoint(node, toward) {
|
|
7092
|
+
const anchor = node.getNearestAnchor(toward);
|
|
7093
|
+
if (anchor) return {
|
|
7094
|
+
point: anchor.point,
|
|
7095
|
+
direction: anchor.id.split(":")[0]
|
|
7096
|
+
};
|
|
7097
|
+
return { point: node.getConnectionPoint(toward) };
|
|
7098
|
+
}
|
|
6871
7099
|
};
|
|
6872
7100
|
//#endregion
|
|
6873
7101
|
//#region src/core/ScrollbarController.ts
|
|
@@ -7232,6 +7460,7 @@ var DiagramRenderer = class extends EventEmitter {
|
|
|
7232
7460
|
this._offsetY = 0;
|
|
7233
7461
|
this._dirty = true;
|
|
7234
7462
|
this._styleDirty = true;
|
|
7463
|
+
this._contentDirty = true;
|
|
7235
7464
|
this._destroyed = false;
|
|
7236
7465
|
this._canvasRect = null;
|
|
7237
7466
|
this._nodeObstaclesCache = null;
|
|
@@ -7536,12 +7765,11 @@ var DiagramRenderer = class extends EventEmitter {
|
|
|
7536
7765
|
* Add a node to the diagram
|
|
7537
7766
|
*/
|
|
7538
7767
|
addNode(node) {
|
|
7539
|
-
node.setDirtyListener(() => this.
|
|
7768
|
+
node.setDirtyListener(() => this.markContentDirty());
|
|
7540
7769
|
node.setAttachToOutlineGetter(() => this._attachToOutline);
|
|
7541
7770
|
this._nodes.set(node.id, node);
|
|
7542
|
-
this._nodeObstaclesCache = null;
|
|
7543
7771
|
this.animationManager.registerEnter(node.id);
|
|
7544
|
-
this.
|
|
7772
|
+
this.markContentDirty();
|
|
7545
7773
|
this.emit("nodeAdd", node);
|
|
7546
7774
|
}
|
|
7547
7775
|
/**
|
|
@@ -7572,11 +7800,12 @@ var DiagramRenderer = class extends EventEmitter {
|
|
|
7572
7800
|
*/
|
|
7573
7801
|
addEdge(edge) {
|
|
7574
7802
|
edge.setDirtyListener(() => this.markDirty());
|
|
7803
|
+
edge.setBindingListener(() => this.markContentDirty());
|
|
7575
7804
|
this._edges.set(edge.id, edge);
|
|
7576
|
-
this._updateEdgeIndex(edge.id, edge.from.nodeId, true);
|
|
7577
|
-
this._updateEdgeIndex(edge.id, edge.to.nodeId, true);
|
|
7805
|
+
if (edge.from.nodeId) this._updateEdgeIndex(edge.id, edge.from.nodeId, true);
|
|
7806
|
+
if (edge.to.nodeId) this._updateEdgeIndex(edge.id, edge.to.nodeId, true);
|
|
7578
7807
|
this.animationManager.registerEnter(edge.id);
|
|
7579
|
-
this.
|
|
7808
|
+
this.markContentDirty();
|
|
7580
7809
|
this.emit("edgeAdd", edge);
|
|
7581
7810
|
}
|
|
7582
7811
|
/**
|
|
@@ -7615,21 +7844,27 @@ var DiagramRenderer = class extends EventEmitter {
|
|
|
7615
7844
|
if (edgesToRemove) for (const edgeId of Array.from(edgesToRemove)) this.removeEdgeImmediate(edgeId);
|
|
7616
7845
|
for (const group of this._groups.values()) group.removeChild(nodeId);
|
|
7617
7846
|
this._nodes.delete(nodeId);
|
|
7618
|
-
this._nodeObstaclesCache = null;
|
|
7619
7847
|
node.setDirtyListener(void 0);
|
|
7620
7848
|
node.setAttachToOutlineGetter(void 0);
|
|
7621
|
-
this.
|
|
7849
|
+
this.markContentDirty();
|
|
7622
7850
|
this.emit("nodeRemove", node);
|
|
7623
7851
|
return true;
|
|
7624
7852
|
}
|
|
7625
7853
|
removeEdgeImmediate(edgeId) {
|
|
7626
7854
|
const edge = this._edges.get(edgeId);
|
|
7627
7855
|
if (edge === void 0) return false;
|
|
7628
|
-
|
|
7629
|
-
|
|
7856
|
+
const dependents = [];
|
|
7857
|
+
for (const other of this._edges.values()) {
|
|
7858
|
+
if (other.id === edgeId) continue;
|
|
7859
|
+
if (other.from.edgeId === edgeId || other.to.edgeId === edgeId) dependents.push(other.id);
|
|
7860
|
+
}
|
|
7861
|
+
for (const dependentId of dependents) this.removeEdgeImmediate(dependentId);
|
|
7862
|
+
if (edge.from.nodeId) this._updateEdgeIndex(edgeId, edge.from.nodeId, false);
|
|
7863
|
+
if (edge.to.nodeId) this._updateEdgeIndex(edgeId, edge.to.nodeId, false);
|
|
7630
7864
|
this._edges.delete(edgeId);
|
|
7631
7865
|
edge.setDirtyListener(void 0);
|
|
7632
|
-
|
|
7866
|
+
edge.setBindingListener(void 0);
|
|
7867
|
+
this.markContentDirty();
|
|
7633
7868
|
this.emit("edgeRemove", edge);
|
|
7634
7869
|
return true;
|
|
7635
7870
|
}
|
|
@@ -7709,12 +7944,22 @@ var DiagramRenderer = class extends EventEmitter {
|
|
|
7709
7944
|
for (const n of this._nodes.values()) n.setBadgeHover(n.id === hoveredNodeId ? hoveredIndex : -1);
|
|
7710
7945
|
}
|
|
7711
7946
|
/**
|
|
7712
|
-
* Mark the diagram as needing re-render
|
|
7947
|
+
* Mark the diagram as needing re-render (viewport / cosmetics).
|
|
7948
|
+
* Does not resync edge endpoints — use {@link markContentDirty} after layout changes.
|
|
7713
7949
|
*/
|
|
7714
7950
|
markDirty() {
|
|
7715
7951
|
this._dirty = true;
|
|
7716
7952
|
}
|
|
7717
7953
|
/**
|
|
7954
|
+
* Mark that node/edge layout changed and edge endpoints must be recalculated.
|
|
7955
|
+
* Pan/zoom should call {@link markDirty} only so path routing is not redone every frame.
|
|
7956
|
+
*/
|
|
7957
|
+
markContentDirty() {
|
|
7958
|
+
this._contentDirty = true;
|
|
7959
|
+
this._nodeObstaclesCache = null;
|
|
7960
|
+
this._dirty = true;
|
|
7961
|
+
}
|
|
7962
|
+
/**
|
|
7718
7963
|
* Force an immediate render
|
|
7719
7964
|
*/
|
|
7720
7965
|
render() {
|
|
@@ -7792,7 +8037,6 @@ var DiagramRenderer = class extends EventEmitter {
|
|
|
7792
8037
|
}
|
|
7793
8038
|
renderFrame(now) {
|
|
7794
8039
|
this.frameTime = now;
|
|
7795
|
-
this._nodeObstaclesCache = null;
|
|
7796
8040
|
const ctx = this.ctx;
|
|
7797
8041
|
const { width, height, backgroundColor } = this.options;
|
|
7798
8042
|
ctx.setTransform(this.devicePixelRatio, 0, 0, this.devicePixelRatio, 0, 0);
|
|
@@ -7810,7 +8054,10 @@ var DiagramRenderer = class extends EventEmitter {
|
|
|
7810
8054
|
this.renderElementWithAnimation(ctx, group, () => group.render(ctx));
|
|
7811
8055
|
group.clearDirty();
|
|
7812
8056
|
}
|
|
7813
|
-
this.
|
|
8057
|
+
if (this._contentDirty) {
|
|
8058
|
+
this.edgeEndpointUpdater.updateAll();
|
|
8059
|
+
this._contentDirty = false;
|
|
8060
|
+
}
|
|
7814
8061
|
for (const node of this._nodes.values()) if (node.visible) {
|
|
7815
8062
|
this.renderElementWithAnimation(ctx, node, () => node.render(ctx));
|
|
7816
8063
|
node.clearDirty();
|
|
@@ -8054,8 +8301,10 @@ var SearchManager = class {
|
|
|
8054
8301
|
for (const edge of this.renderer.edges.values()) {
|
|
8055
8302
|
if (scope === "nodes") continue;
|
|
8056
8303
|
const matchesEdge = this.matchesEdge(edge, options);
|
|
8057
|
-
const
|
|
8058
|
-
const
|
|
8304
|
+
const fromId = edge.from.nodeId;
|
|
8305
|
+
const toId = edge.to.nodeId;
|
|
8306
|
+
const fromVisible = fromId ? this.renderer.getNode(fromId)?.visible ?? true : true;
|
|
8307
|
+
const toVisible = toId ? this.renderer.getNode(toId)?.visible ?? true : true;
|
|
8059
8308
|
edge.visible = matchesEdge && fromVisible && toVisible;
|
|
8060
8309
|
}
|
|
8061
8310
|
this.renderer.markDirty();
|
|
@@ -8414,7 +8663,7 @@ var svgTextCache = new LRUCache(100);
|
|
|
8414
8663
|
function fetchSvgText(url) {
|
|
8415
8664
|
let promise = svgTextCache.get(url);
|
|
8416
8665
|
if (!promise) {
|
|
8417
|
-
promise = fetch(url).then((r) => r.ok ? r.text() : "");
|
|
8666
|
+
promise = fetch(url).then((r) => r.ok ? r.text() : "").catch(() => "");
|
|
8418
8667
|
svgTextCache.set(url, promise);
|
|
8419
8668
|
}
|
|
8420
8669
|
return promise;
|
|
@@ -8432,6 +8681,32 @@ function readSvgFromUrlSync(url) {
|
|
|
8432
8681
|
} catch {}
|
|
8433
8682
|
return null;
|
|
8434
8683
|
}
|
|
8684
|
+
/**
|
|
8685
|
+
* Resolve a source string to an href suitable for Image/SVG (async, with tint).
|
|
8686
|
+
*/
|
|
8687
|
+
async function resolveTintedSvgDataUrl(source, options = {}) {
|
|
8688
|
+
const { strokeColor, fillColor } = options;
|
|
8689
|
+
if (isSvgMarkup(source)) return svgToDataUrl(tintSvg(source, strokeColor, fillColor));
|
|
8690
|
+
if (isSvgUrl(source) || source.toLowerCase().endsWith(".svg")) {
|
|
8691
|
+
const svgText = await fetchSvgText(source);
|
|
8692
|
+
if (!svgText) return null;
|
|
8693
|
+
return svgToDataUrl(tintSvg(svgText, strokeColor, fillColor));
|
|
8694
|
+
}
|
|
8695
|
+
return null;
|
|
8696
|
+
}
|
|
8697
|
+
/**
|
|
8698
|
+
* Resolve a source to a tinted data URL synchronously (SvgExporter path).
|
|
8699
|
+
*/
|
|
8700
|
+
function resolveTintedSvgDataUrlSync(source, options = {}) {
|
|
8701
|
+
const { strokeColor, fillColor } = options;
|
|
8702
|
+
if (isSvgMarkup(source)) return svgToDataUrl(tintSvg(source, strokeColor, fillColor));
|
|
8703
|
+
if (source.toLowerCase().endsWith(".svg")) {
|
|
8704
|
+
const svgText = readSvgFromUrlSync(source);
|
|
8705
|
+
if (!svgText) return null;
|
|
8706
|
+
return svgToDataUrl(tintSvg(svgText, strokeColor, fillColor));
|
|
8707
|
+
}
|
|
8708
|
+
return null;
|
|
8709
|
+
}
|
|
8435
8710
|
//#endregion
|
|
8436
8711
|
//#region src/utils/iconLayout.ts
|
|
8437
8712
|
/**
|
|
@@ -8616,22 +8891,20 @@ var NodeImage = class {
|
|
|
8616
8891
|
if (source instanceof HTMLImageElement) return;
|
|
8617
8892
|
const version = ++this._sourceVersion;
|
|
8618
8893
|
if (isSvgMarkup(source)) {
|
|
8619
|
-
const
|
|
8894
|
+
const resolvedSource = resolveTintedSvgDataUrlSync(source, {
|
|
8895
|
+
strokeColor: this._options.strokeColor,
|
|
8896
|
+
fillColor: this._options.fillColor
|
|
8897
|
+
});
|
|
8620
8898
|
if (version !== this._sourceVersion) return;
|
|
8621
|
-
this._image.src =
|
|
8899
|
+
this._image.src = resolvedSource ?? source;
|
|
8622
8900
|
return;
|
|
8623
8901
|
}
|
|
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
|
-
}
|
|
8902
|
+
const resolvedSource = await resolveTintedSvgDataUrl(source, {
|
|
8903
|
+
strokeColor: this._options.strokeColor,
|
|
8904
|
+
fillColor: this._options.fillColor
|
|
8905
|
+
});
|
|
8633
8906
|
if (version !== this._sourceVersion) return;
|
|
8634
|
-
this._image.src = source;
|
|
8907
|
+
this._image.src = resolvedSource ?? source;
|
|
8635
8908
|
}
|
|
8636
8909
|
attachHandlers() {
|
|
8637
8910
|
if (this._image.complete && this._image.naturalWidth > 0) {
|
|
@@ -10703,16 +10976,8 @@ var CIcon = class extends CompositeComponentBase {
|
|
|
10703
10976
|
}
|
|
10704
10977
|
}
|
|
10705
10978
|
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;
|
|
10979
|
+
const resolvedSource = await resolveTintedSvgDataUrl(source, { fillColor: this._fillColor });
|
|
10980
|
+
this._image.src = resolvedSource ?? source;
|
|
10716
10981
|
}
|
|
10717
10982
|
measure(_ctx) {
|
|
10718
10983
|
return {
|
|
@@ -10761,7 +11026,8 @@ var CIcon = class extends CompositeComponentBase {
|
|
|
10761
11026
|
const drawHeight = Math.min(this._height, bounds.height);
|
|
10762
11027
|
const dx = bounds.x + (bounds.width - drawWidth) / 2;
|
|
10763
11028
|
const dy = bounds.y + (bounds.height - drawHeight) / 2;
|
|
10764
|
-
|
|
11029
|
+
const source = resolveTintedSvgDataUrlSync(this._source, { fillColor: this._fillColor }) ?? this._source;
|
|
11030
|
+
svg += `<image href="${escapeXmlAttr(source)}" x="${dx}" y="${dy}" width="${drawWidth}" height="${drawHeight}" />`;
|
|
10765
11031
|
return svg;
|
|
10766
11032
|
}
|
|
10767
11033
|
};
|
|
@@ -12180,9 +12446,14 @@ var Serializer = class {
|
|
|
12180
12446
|
if (!Array.isArray(data.nodes)) throw new SerializerValidationError("Invalid or missing nodes array");
|
|
12181
12447
|
if (!Array.isArray(data.edges)) throw new SerializerValidationError("Invalid or missing edges array");
|
|
12182
12448
|
const nodeIds = new Set(data.nodes.map((n) => n.id));
|
|
12449
|
+
const edgeIds = new Set(data.edges.map((e) => e.id));
|
|
12183
12450
|
for (const edge of data.edges) {
|
|
12184
|
-
if (
|
|
12185
|
-
|
|
12451
|
+
if (edge.from.edgeId) {
|
|
12452
|
+
if (!edgeIds.has(edge.from.edgeId)) throw new SerializerValidationError(`Edge "${edge.id}" references non-existent host edge "${edge.from.edgeId}"`);
|
|
12453
|
+
} else if (!edge.from.nodeId || !nodeIds.has(edge.from.nodeId)) throw new SerializerValidationError(`Edge "${edge.id}" references non-existent source node "${edge.from.nodeId}"`);
|
|
12454
|
+
if (edge.to.edgeId) {
|
|
12455
|
+
if (!edgeIds.has(edge.to.edgeId)) throw new SerializerValidationError(`Edge "${edge.id}" references non-existent host edge "${edge.to.edgeId}"`);
|
|
12456
|
+
} 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
12457
|
}
|
|
12187
12458
|
}
|
|
12188
12459
|
/**
|
|
@@ -12965,12 +13236,10 @@ var SvgExporter = class {
|
|
|
12965
13236
|
const source = opts.source;
|
|
12966
13237
|
if (source instanceof HTMLImageElement) return source.src;
|
|
12967
13238
|
if (!source) return "";
|
|
12968
|
-
|
|
12969
|
-
|
|
12970
|
-
|
|
12971
|
-
|
|
12972
|
-
}
|
|
12973
|
-
return source;
|
|
13239
|
+
return resolveTintedSvgDataUrlSync(source, {
|
|
13240
|
+
strokeColor: opts.strokeColor,
|
|
13241
|
+
fillColor: opts.fillColor
|
|
13242
|
+
}) ?? source;
|
|
12974
13243
|
}
|
|
12975
13244
|
escapeAttribute(value) {
|
|
12976
13245
|
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -13027,8 +13296,11 @@ var AutoRouting = class {
|
|
|
13027
13296
|
const type = options.type ?? "polyline";
|
|
13028
13297
|
for (const edge of renderer.edges.values()) {
|
|
13029
13298
|
edge.type = type;
|
|
13030
|
-
const
|
|
13031
|
-
const
|
|
13299
|
+
const fromId = edge.from.nodeId;
|
|
13300
|
+
const toId = edge.to.nodeId;
|
|
13301
|
+
if (!fromId || !toId) continue;
|
|
13302
|
+
const fromNode = renderer.getNode(fromId);
|
|
13303
|
+
const toNode = renderer.getNode(toId);
|
|
13032
13304
|
if (!fromNode || !toNode) continue;
|
|
13033
13305
|
const fromPoint = edge.from.portId !== void 0 ? fromNode.getPortPosition(edge.from.portId) ?? fromNode.getCenter() : fromNode.getCenter();
|
|
13034
13306
|
const toPoint = edge.to.portId !== void 0 ? toNode.getPortPosition(edge.to.portId) ?? toNode.getCenter() : toNode.getCenter();
|
|
@@ -13503,6 +13775,6 @@ function applyEdgeStyle(ctx, style) {
|
|
|
13503
13775
|
return baseOpacity * strokeOpacity;
|
|
13504
13776
|
}
|
|
13505
13777
|
//#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 };
|
|
13778
|
+
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
13779
|
|
|
13508
13780
|
//# sourceMappingURL=papirus.js.map
|