@ngroznykh/papirus 0.3.3 → 0.3.5
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/README.md +7 -1
- package/README.ru.md +7 -1
- package/dist/core/ConnectionManager.d.ts +0 -4
- package/dist/core/ConnectionManager.d.ts.map +1 -1
- package/dist/core/DiagramRenderer.d.ts.map +1 -1
- package/dist/core/InteractionManager.d.ts.map +1 -1
- package/dist/core/history/commands.d.ts +1 -0
- package/dist/core/history/commands.d.ts.map +1 -1
- package/dist/elements/Edge.d.ts +3 -2
- package/dist/elements/Edge.d.ts.map +1 -1
- package/dist/elements/TextLabel.d.ts +8 -0
- package/dist/elements/TextLabel.d.ts.map +1 -1
- package/dist/elements/paths/BezierPathStrategy.d.ts.map +1 -1
- package/dist/elements/paths/PathStrategy.d.ts +9 -0
- package/dist/elements/paths/PathStrategy.d.ts.map +1 -1
- package/dist/elements/paths/PolylinePathStrategy.d.ts.map +1 -1
- package/dist/elements/paths/index.d.ts +1 -1
- package/dist/elements/paths/index.d.ts.map +1 -1
- package/dist/papirus.js +516 -27
- package/dist/papirus.js.map +1 -1
- package/package.json +1 -1
package/dist/papirus.js
CHANGED
|
@@ -1524,13 +1524,6 @@ class ConnectionManager extends EventEmitter {
|
|
|
1524
1524
|
this.renderer.markDirty();
|
|
1525
1525
|
return true;
|
|
1526
1526
|
}
|
|
1527
|
-
/**
|
|
1528
|
-
* Get the node ID at the other end of the reconnecting edge
|
|
1529
|
-
*/
|
|
1530
|
-
getOtherNodeId() {
|
|
1531
|
-
if (!this.reconnectingEdge) return null;
|
|
1532
|
-
return this.reconnectingEndpoint === "start" ? this.reconnectingEdge.to.nodeId : this.reconnectingEdge.from.nodeId;
|
|
1533
|
-
}
|
|
1534
1527
|
/**
|
|
1535
1528
|
* Handle mouse up to complete or cancel connection
|
|
1536
1529
|
*/
|
|
@@ -1576,14 +1569,15 @@ class ConnectionManager extends EventEmitter {
|
|
|
1576
1569
|
if (targetNode) {
|
|
1577
1570
|
const allowed = !this._connectionValidator || this._connectionValidator(this.sourceNode.id, targetNode.id);
|
|
1578
1571
|
if (allowed) {
|
|
1579
|
-
const
|
|
1572
|
+
const nearestTargetAnchor = targetNode.getNearestAnchor(point);
|
|
1573
|
+
const sourceAnchorId = this.sourceAnchorId;
|
|
1580
1574
|
const from = {
|
|
1581
1575
|
nodeId: this.sourceNode.id,
|
|
1582
|
-
portId:
|
|
1576
|
+
portId: sourceAnchorId ? `${ANCHOR_PORT_PREFIX}${sourceAnchorId}` : void 0
|
|
1583
1577
|
};
|
|
1584
1578
|
const to = {
|
|
1585
1579
|
nodeId: targetNode.id,
|
|
1586
|
-
portId:
|
|
1580
|
+
portId: nearestTargetAnchor ? `${ANCHOR_PORT_PREFIX}${nearestTargetAnchor.id}` : void 0
|
|
1587
1581
|
};
|
|
1588
1582
|
createdEdge = this.createEdge(from, to);
|
|
1589
1583
|
this.addEdge(createdEdge);
|
|
@@ -1691,8 +1685,8 @@ class ConnectionManager extends EventEmitter {
|
|
|
1691
1685
|
}
|
|
1692
1686
|
ctx.stroke();
|
|
1693
1687
|
}
|
|
1694
|
-
isCompatibleTarget(
|
|
1695
|
-
return
|
|
1688
|
+
isCompatibleTarget(_node) {
|
|
1689
|
+
return true;
|
|
1696
1690
|
}
|
|
1697
1691
|
setCursor(cursor) {
|
|
1698
1692
|
const canvas = this.renderer.getCanvas();
|
|
@@ -1814,10 +1808,6 @@ class ConnectionManager extends EventEmitter {
|
|
|
1814
1808
|
continue;
|
|
1815
1809
|
}
|
|
1816
1810
|
if (reconnecting) {
|
|
1817
|
-
const otherId = this.getOtherNodeId();
|
|
1818
|
-
if (otherId && node.id === otherId) {
|
|
1819
|
-
continue;
|
|
1820
|
-
}
|
|
1821
1811
|
return node;
|
|
1822
1812
|
}
|
|
1823
1813
|
if (this.isCompatibleTarget(node)) {
|
|
@@ -1912,6 +1902,7 @@ class TextLabel {
|
|
|
1912
1902
|
this._measuredHeight = 0;
|
|
1913
1903
|
this._measureDirty = true;
|
|
1914
1904
|
this._text = options.text;
|
|
1905
|
+
this._editableText = options.editableText;
|
|
1915
1906
|
this._localStyle = { ...options.style };
|
|
1916
1907
|
this._style = { ...DEFAULT_STYLE, ...options.style };
|
|
1917
1908
|
this._maxWidth = options.maxWidth;
|
|
@@ -1934,6 +1925,19 @@ class TextLabel {
|
|
|
1934
1925
|
this._onChange?.();
|
|
1935
1926
|
}
|
|
1936
1927
|
}
|
|
1928
|
+
/**
|
|
1929
|
+
* Editable text shown in inline editor instead of display text.
|
|
1930
|
+
* When set, double-click editing will use this value.
|
|
1931
|
+
*/
|
|
1932
|
+
get editableText() {
|
|
1933
|
+
return this._editableText;
|
|
1934
|
+
}
|
|
1935
|
+
set editableText(value) {
|
|
1936
|
+
if (this._editableText !== value) {
|
|
1937
|
+
this._editableText = value;
|
|
1938
|
+
this._onChange?.();
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1937
1941
|
/**
|
|
1938
1942
|
* Text style
|
|
1939
1943
|
*/
|
|
@@ -2171,6 +2175,7 @@ const snapshotLabel = (label) => {
|
|
|
2171
2175
|
}
|
|
2172
2176
|
return {
|
|
2173
2177
|
text: label.text,
|
|
2178
|
+
editableText: label.editableText,
|
|
2174
2179
|
style: cloneValue(label.style),
|
|
2175
2180
|
styleClass: label.styleClass,
|
|
2176
2181
|
maxWidth: label.maxWidth
|
|
@@ -2182,6 +2187,7 @@ const applyLabelSnapshot = (current, snapshot) => {
|
|
|
2182
2187
|
}
|
|
2183
2188
|
if (current) {
|
|
2184
2189
|
current.text = snapshot.text;
|
|
2190
|
+
current.editableText = snapshot.editableText;
|
|
2185
2191
|
current.style = snapshot.style ?? {};
|
|
2186
2192
|
current.styleClass = snapshot.styleClass;
|
|
2187
2193
|
current.maxWidth = snapshot.maxWidth;
|
|
@@ -2189,6 +2195,7 @@ const applyLabelSnapshot = (current, snapshot) => {
|
|
|
2189
2195
|
}
|
|
2190
2196
|
return new TextLabel({
|
|
2191
2197
|
text: snapshot.text,
|
|
2198
|
+
editableText: snapshot.editableText,
|
|
2192
2199
|
style: snapshot.style,
|
|
2193
2200
|
styleClass: snapshot.styleClass,
|
|
2194
2201
|
maxWidth: snapshot.maxWidth
|
|
@@ -2739,8 +2746,339 @@ class StraightPathStrategy {
|
|
|
2739
2746
|
}
|
|
2740
2747
|
}
|
|
2741
2748
|
const MIN_SEGMENT_LENGTH = 20;
|
|
2749
|
+
const SELF_LOOP_MIN_DISTANCE$1 = 1;
|
|
2750
|
+
const SELF_LOOP_OFFSET$1 = 42;
|
|
2751
|
+
const SELF_LOOP_SPREAD$1 = 20;
|
|
2752
|
+
const CORNER_BYPASS_DISTANCE$1 = 220;
|
|
2753
|
+
const CORNER_BYPASS_CLEARANCE$1 = 34;
|
|
2754
|
+
const OPPOSITE_BYPASS_DISTANCE$1 = 280;
|
|
2755
|
+
const OPPOSITE_BYPASS_CLEARANCE$1 = 36;
|
|
2756
|
+
const OPPOSITE_BYPASS_ARC$1 = 48;
|
|
2757
|
+
const OBSTACLE_ROUTING_DISTANCE = 1200;
|
|
2758
|
+
const OBSTACLE_MARGIN = 12;
|
|
2759
|
+
const ROUTE_EXIT_DISTANCE = 32;
|
|
2760
|
+
const TURN_PENALTY = 70;
|
|
2761
|
+
const FIRST_VERTICAL_PENALTY = 28;
|
|
2762
|
+
function isHorizontal$1(dir) {
|
|
2763
|
+
return dir === "left" || dir === "right";
|
|
2764
|
+
}
|
|
2765
|
+
function isVertical$1(dir) {
|
|
2766
|
+
return dir === "top" || dir === "bottom";
|
|
2767
|
+
}
|
|
2768
|
+
function isOppositeDirections$1(fromDir, toDir) {
|
|
2769
|
+
return fromDir === "left" && toDir === "right" || fromDir === "right" && toDir === "left" || fromDir === "top" && toDir === "bottom" || fromDir === "bottom" && toDir === "top";
|
|
2770
|
+
}
|
|
2771
|
+
function expandObstacles(obstacles, margin) {
|
|
2772
|
+
return obstacles.map((obstacle) => ({
|
|
2773
|
+
...obstacle,
|
|
2774
|
+
x: obstacle.x - margin,
|
|
2775
|
+
y: obstacle.y - margin,
|
|
2776
|
+
width: obstacle.width + margin * 2,
|
|
2777
|
+
height: obstacle.height + margin * 2
|
|
2778
|
+
}));
|
|
2779
|
+
}
|
|
2780
|
+
function pointInsideObstacle(point, obstacle) {
|
|
2781
|
+
return point.x >= obstacle.x && point.x <= obstacle.x + obstacle.width && point.y >= obstacle.y && point.y <= obstacle.y + obstacle.height;
|
|
2782
|
+
}
|
|
2783
|
+
function segmentIntersectsExpandedObstacle(a, b, obstacle) {
|
|
2784
|
+
const minX = obstacle.x;
|
|
2785
|
+
const maxX = obstacle.x + obstacle.width;
|
|
2786
|
+
const minY = obstacle.y;
|
|
2787
|
+
const maxY = obstacle.y + obstacle.height;
|
|
2788
|
+
if (Math.abs(a.x - b.x) < 1e-3) {
|
|
2789
|
+
const x = a.x;
|
|
2790
|
+
const y1 = Math.min(a.y, b.y);
|
|
2791
|
+
const y2 = Math.max(a.y, b.y);
|
|
2792
|
+
return x >= minX && x <= maxX && y2 >= minY && y1 <= maxY;
|
|
2793
|
+
}
|
|
2794
|
+
if (Math.abs(a.y - b.y) < 1e-3) {
|
|
2795
|
+
const y = a.y;
|
|
2796
|
+
const x1 = Math.min(a.x, b.x);
|
|
2797
|
+
const x2 = Math.max(a.x, b.x);
|
|
2798
|
+
return y >= minY && y <= maxY && x2 >= minX && x1 <= maxX;
|
|
2799
|
+
}
|
|
2800
|
+
return false;
|
|
2801
|
+
}
|
|
2802
|
+
function simplifyPath(path) {
|
|
2803
|
+
const out = [];
|
|
2804
|
+
for (const point of path) {
|
|
2805
|
+
const prev = out[out.length - 1];
|
|
2806
|
+
if (!prev || Math.abs(prev.x - point.x) > 1e-3 || Math.abs(prev.y - point.y) > 1e-3) {
|
|
2807
|
+
out.push(point);
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
return out;
|
|
2811
|
+
}
|
|
2812
|
+
function manhattan(a, b) {
|
|
2813
|
+
return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
|
|
2814
|
+
}
|
|
2815
|
+
function moveByDir(point, dir, distance2) {
|
|
2816
|
+
switch (dir) {
|
|
2817
|
+
case "top":
|
|
2818
|
+
return { x: point.x, y: point.y - distance2 };
|
|
2819
|
+
case "bottom":
|
|
2820
|
+
return { x: point.x, y: point.y + distance2 };
|
|
2821
|
+
case "left":
|
|
2822
|
+
return { x: point.x - distance2, y: point.y };
|
|
2823
|
+
case "right":
|
|
2824
|
+
return { x: point.x + distance2, y: point.y };
|
|
2825
|
+
default:
|
|
2826
|
+
return point;
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2829
|
+
function inferDir(from, to) {
|
|
2830
|
+
const dx = to.x - from.x;
|
|
2831
|
+
const dy = to.y - from.y;
|
|
2832
|
+
if (Math.abs(dx) >= Math.abs(dy)) {
|
|
2833
|
+
return dx >= 0 ? "right" : "left";
|
|
2834
|
+
}
|
|
2835
|
+
return dy >= 0 ? "bottom" : "top";
|
|
2836
|
+
}
|
|
2837
|
+
function oppositeDir(dir) {
|
|
2838
|
+
switch (dir) {
|
|
2839
|
+
case "left":
|
|
2840
|
+
return "right";
|
|
2841
|
+
case "right":
|
|
2842
|
+
return "left";
|
|
2843
|
+
case "top":
|
|
2844
|
+
return "bottom";
|
|
2845
|
+
case "bottom":
|
|
2846
|
+
return "top";
|
|
2847
|
+
default:
|
|
2848
|
+
return dir;
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
function buildRoutedPolyline(from, to, fromDir, toDir, obstacles) {
|
|
2852
|
+
if (obstacles.length === 0) {
|
|
2853
|
+
return null;
|
|
2854
|
+
}
|
|
2855
|
+
const effectiveFromDir = fromDir ?? inferDir(from, to);
|
|
2856
|
+
const effectiveToDir = toDir ?? oppositeDir(effectiveFromDir);
|
|
2857
|
+
const startExit = moveByDir(from, effectiveFromDir, ROUTE_EXIT_DISTANCE);
|
|
2858
|
+
const endEntry = moveByDir(to, effectiveToDir, ROUTE_EXIT_DISTANCE);
|
|
2859
|
+
const expanded = expandObstacles(obstacles, OBSTACLE_MARGIN);
|
|
2860
|
+
const xs = /* @__PURE__ */ new Set([startExit.x, endEntry.x]);
|
|
2861
|
+
const ys = /* @__PURE__ */ new Set([startExit.y, endEntry.y]);
|
|
2862
|
+
for (const obstacle of expanded) {
|
|
2863
|
+
xs.add(obstacle.x - 1);
|
|
2864
|
+
xs.add(obstacle.x + obstacle.width + 1);
|
|
2865
|
+
ys.add(obstacle.y - 1);
|
|
2866
|
+
ys.add(obstacle.y + obstacle.height + 1);
|
|
2867
|
+
}
|
|
2868
|
+
const nodes = [];
|
|
2869
|
+
const nodeIndex = /* @__PURE__ */ new Map();
|
|
2870
|
+
const nodeKey = (x, y) => `${x}|${y}`;
|
|
2871
|
+
const addNode = (x, y) => {
|
|
2872
|
+
const key = nodeKey(x, y);
|
|
2873
|
+
if (nodeIndex.has(key)) return;
|
|
2874
|
+
const point = { x, y };
|
|
2875
|
+
if (expanded.some((obstacle) => pointInsideObstacle(point, obstacle))) {
|
|
2876
|
+
return;
|
|
2877
|
+
}
|
|
2878
|
+
nodeIndex.set(key, nodes.length);
|
|
2879
|
+
nodes.push(point);
|
|
2880
|
+
};
|
|
2881
|
+
for (const x of xs) {
|
|
2882
|
+
for (const y of ys) {
|
|
2883
|
+
addNode(x, y);
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
addNode(startExit.x, startExit.y);
|
|
2887
|
+
addNode(endEntry.x, endEntry.y);
|
|
2888
|
+
const startIdx = nodeIndex.get(nodeKey(startExit.x, startExit.y));
|
|
2889
|
+
const goalIdx = nodeIndex.get(nodeKey(endEntry.x, endEntry.y));
|
|
2890
|
+
if (startIdx == null || goalIdx == null) {
|
|
2891
|
+
return null;
|
|
2892
|
+
}
|
|
2893
|
+
const edges = /* @__PURE__ */ new Map();
|
|
2894
|
+
const pushEdge = (a, b, dir, length) => {
|
|
2895
|
+
const list = edges.get(a) ?? [];
|
|
2896
|
+
list.push({ to: b, dir, length });
|
|
2897
|
+
edges.set(a, list);
|
|
2898
|
+
};
|
|
2899
|
+
const byX = /* @__PURE__ */ new Map();
|
|
2900
|
+
const byY = /* @__PURE__ */ new Map();
|
|
2901
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
2902
|
+
const node = nodes[i];
|
|
2903
|
+
const xsList = byX.get(node.x) ?? [];
|
|
2904
|
+
xsList.push(i);
|
|
2905
|
+
byX.set(node.x, xsList);
|
|
2906
|
+
const ysList = byY.get(node.y) ?? [];
|
|
2907
|
+
ysList.push(i);
|
|
2908
|
+
byY.set(node.y, ysList);
|
|
2909
|
+
}
|
|
2910
|
+
for (const list of byX.values()) {
|
|
2911
|
+
list.sort((a, b) => nodes[a].y - nodes[b].y);
|
|
2912
|
+
for (let i = 1; i < list.length; i++) {
|
|
2913
|
+
const a = nodes[list[i - 1]];
|
|
2914
|
+
const b = nodes[list[i]];
|
|
2915
|
+
if (!expanded.some((obstacle) => segmentIntersectsExpandedObstacle(a, b, obstacle))) {
|
|
2916
|
+
const length = manhattan(a, b);
|
|
2917
|
+
pushEdge(list[i - 1], list[i], "v", length);
|
|
2918
|
+
pushEdge(list[i], list[i - 1], "v", length);
|
|
2919
|
+
}
|
|
2920
|
+
}
|
|
2921
|
+
}
|
|
2922
|
+
for (const list of byY.values()) {
|
|
2923
|
+
list.sort((a, b) => nodes[a].x - nodes[b].x);
|
|
2924
|
+
for (let i = 1; i < list.length; i++) {
|
|
2925
|
+
const a = nodes[list[i - 1]];
|
|
2926
|
+
const b = nodes[list[i]];
|
|
2927
|
+
if (!expanded.some((obstacle) => segmentIntersectsExpandedObstacle(a, b, obstacle))) {
|
|
2928
|
+
const length = manhattan(a, b);
|
|
2929
|
+
pushEdge(list[i - 1], list[i], "h", length);
|
|
2930
|
+
pushEdge(list[i], list[i - 1], "h", length);
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
const makeStateKey = (node, dir) => `${node}:${dir}`;
|
|
2935
|
+
const open = [];
|
|
2936
|
+
const best = /* @__PURE__ */ new Map();
|
|
2937
|
+
const prev = /* @__PURE__ */ new Map();
|
|
2938
|
+
const startKey = makeStateKey(startIdx, "s");
|
|
2939
|
+
best.set(startKey, 0);
|
|
2940
|
+
open.push({
|
|
2941
|
+
node: startIdx,
|
|
2942
|
+
dir: "s",
|
|
2943
|
+
g: 0,
|
|
2944
|
+
f: manhattan(nodes[startIdx], nodes[goalIdx]),
|
|
2945
|
+
key: startKey
|
|
2946
|
+
});
|
|
2947
|
+
let goalKey = null;
|
|
2948
|
+
while (open.length > 0) {
|
|
2949
|
+
let bestIdx = 0;
|
|
2950
|
+
for (let i = 1; i < open.length; i++) {
|
|
2951
|
+
if (open[i].f < open[bestIdx].f) bestIdx = i;
|
|
2952
|
+
}
|
|
2953
|
+
const current = open.splice(bestIdx, 1)[0];
|
|
2954
|
+
if (current.node === goalIdx) {
|
|
2955
|
+
goalKey = current.key;
|
|
2956
|
+
break;
|
|
2957
|
+
}
|
|
2958
|
+
for (const edge of edges.get(current.node) ?? []) {
|
|
2959
|
+
const nextDir = edge.dir;
|
|
2960
|
+
let stepCost = edge.length;
|
|
2961
|
+
if (current.dir !== "s" && current.dir !== nextDir) {
|
|
2962
|
+
stepCost += TURN_PENALTY;
|
|
2963
|
+
}
|
|
2964
|
+
if (current.dir === "s" && nextDir === "v") {
|
|
2965
|
+
stepCost += FIRST_VERTICAL_PENALTY;
|
|
2966
|
+
}
|
|
2967
|
+
const nextG = current.g + stepCost;
|
|
2968
|
+
const nextKey = makeStateKey(edge.to, nextDir);
|
|
2969
|
+
if (nextG >= (best.get(nextKey) ?? Infinity)) {
|
|
2970
|
+
continue;
|
|
2971
|
+
}
|
|
2972
|
+
best.set(nextKey, nextG);
|
|
2973
|
+
prev.set(nextKey, current.key);
|
|
2974
|
+
const h = manhattan(nodes[edge.to], nodes[goalIdx]);
|
|
2975
|
+
open.push({ node: edge.to, dir: nextDir, g: nextG, f: nextG + h, key: nextKey });
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
if (!goalKey) {
|
|
2979
|
+
return null;
|
|
2980
|
+
}
|
|
2981
|
+
const routed = [];
|
|
2982
|
+
let cursor = goalKey;
|
|
2983
|
+
while (cursor) {
|
|
2984
|
+
const [nodePart] = cursor.split(":");
|
|
2985
|
+
const node = nodes[Number(nodePart)];
|
|
2986
|
+
routed.push({ x: node.x, y: node.y });
|
|
2987
|
+
cursor = prev.get(cursor);
|
|
2988
|
+
}
|
|
2989
|
+
routed.reverse();
|
|
2990
|
+
const internal = simplifyPath(routed).filter((_, idx, arr) => idx !== 0 && idx !== arr.length - 1);
|
|
2991
|
+
return simplifyPath([from, startExit, ...internal, endEntry, to]);
|
|
2992
|
+
}
|
|
2742
2993
|
class PolylinePathStrategy {
|
|
2743
2994
|
calculatePath(from, to, fromDir, toDir, _options) {
|
|
2995
|
+
const dx = to.x - from.x;
|
|
2996
|
+
const dy = to.y - from.y;
|
|
2997
|
+
const distance2 = Math.sqrt(dx * dx + dy * dy);
|
|
2998
|
+
const selfLoop = _options?.selfLoop ?? false;
|
|
2999
|
+
const obstacles = _options?.obstacles ?? [];
|
|
3000
|
+
if (!selfLoop && distance2 < OBSTACLE_ROUTING_DISTANCE) {
|
|
3001
|
+
const routed = buildRoutedPolyline(from, to, fromDir, toDir, obstacles);
|
|
3002
|
+
if (routed) {
|
|
3003
|
+
return routed;
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
3006
|
+
if (selfLoop && distance2 < SELF_LOOP_MIN_DISTANCE$1 && (fromDir || toDir)) {
|
|
3007
|
+
const dir = fromDir ?? toDir;
|
|
3008
|
+
switch (dir) {
|
|
3009
|
+
case "bottom":
|
|
3010
|
+
return [
|
|
3011
|
+
from,
|
|
3012
|
+
{ x: from.x - SELF_LOOP_SPREAD$1, y: from.y + SELF_LOOP_OFFSET$1 },
|
|
3013
|
+
{ x: from.x + SELF_LOOP_SPREAD$1, y: from.y + SELF_LOOP_OFFSET$1 },
|
|
3014
|
+
to
|
|
3015
|
+
];
|
|
3016
|
+
case "left":
|
|
3017
|
+
return [
|
|
3018
|
+
from,
|
|
3019
|
+
{ x: from.x - SELF_LOOP_OFFSET$1, y: from.y + SELF_LOOP_SPREAD$1 },
|
|
3020
|
+
{ x: from.x - SELF_LOOP_OFFSET$1, y: from.y - SELF_LOOP_SPREAD$1 },
|
|
3021
|
+
to
|
|
3022
|
+
];
|
|
3023
|
+
case "right":
|
|
3024
|
+
return [
|
|
3025
|
+
from,
|
|
3026
|
+
{ x: from.x + SELF_LOOP_OFFSET$1, y: from.y - SELF_LOOP_SPREAD$1 },
|
|
3027
|
+
{ x: from.x + SELF_LOOP_OFFSET$1, y: from.y + SELF_LOOP_SPREAD$1 },
|
|
3028
|
+
to
|
|
3029
|
+
];
|
|
3030
|
+
case "top":
|
|
3031
|
+
default:
|
|
3032
|
+
return [
|
|
3033
|
+
from,
|
|
3034
|
+
{ x: from.x + SELF_LOOP_SPREAD$1, y: from.y - SELF_LOOP_OFFSET$1 },
|
|
3035
|
+
{ x: from.x - SELF_LOOP_SPREAD$1, y: from.y - SELF_LOOP_OFFSET$1 },
|
|
3036
|
+
to
|
|
3037
|
+
];
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
if (selfLoop && distance2 < OPPOSITE_BYPASS_DISTANCE$1 && fromDir && toDir && isOppositeDirections$1(fromDir, toDir)) {
|
|
3041
|
+
if (isHorizontal$1(fromDir) && isHorizontal$1(toDir)) {
|
|
3042
|
+
const fromStepX = fromDir === "left" ? -OPPOSITE_BYPASS_CLEARANCE$1 : OPPOSITE_BYPASS_CLEARANCE$1;
|
|
3043
|
+
const toStepX = toDir === "left" ? -OPPOSITE_BYPASS_CLEARANCE$1 : OPPOSITE_BYPASS_CLEARANCE$1;
|
|
3044
|
+
const sideY = from.x <= to.x ? -1 : 1;
|
|
3045
|
+
const outerY = from.y + sideY * OPPOSITE_BYPASS_ARC$1;
|
|
3046
|
+
return [
|
|
3047
|
+
from,
|
|
3048
|
+
{ x: from.x + fromStepX, y: from.y },
|
|
3049
|
+
{ x: from.x + fromStepX, y: outerY },
|
|
3050
|
+
{ x: to.x + toStepX, y: outerY },
|
|
3051
|
+
{ x: to.x + toStepX, y: to.y },
|
|
3052
|
+
to
|
|
3053
|
+
];
|
|
3054
|
+
}
|
|
3055
|
+
if (isVertical$1(fromDir) && isVertical$1(toDir)) {
|
|
3056
|
+
const fromStepY = fromDir === "top" ? -OPPOSITE_BYPASS_CLEARANCE$1 : OPPOSITE_BYPASS_CLEARANCE$1;
|
|
3057
|
+
const toStepY = toDir === "top" ? -OPPOSITE_BYPASS_CLEARANCE$1 : OPPOSITE_BYPASS_CLEARANCE$1;
|
|
3058
|
+
const sideX = from.y <= to.y ? 1 : -1;
|
|
3059
|
+
const outerX = from.x + sideX * OPPOSITE_BYPASS_ARC$1;
|
|
3060
|
+
return [
|
|
3061
|
+
from,
|
|
3062
|
+
{ x: from.x, y: from.y + fromStepY },
|
|
3063
|
+
{ x: outerX, y: from.y + fromStepY },
|
|
3064
|
+
{ x: outerX, y: to.y + toStepY },
|
|
3065
|
+
{ x: to.x, y: to.y + toStepY },
|
|
3066
|
+
to
|
|
3067
|
+
];
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
if (selfLoop && distance2 < CORNER_BYPASS_DISTANCE$1 && fromDir && toDir && (isHorizontal$1(fromDir) && isVertical$1(toDir) || isVertical$1(fromDir) && isHorizontal$1(toDir))) {
|
|
3071
|
+
const fromOuter = {
|
|
3072
|
+
x: from.x + (fromDir === "left" ? -CORNER_BYPASS_CLEARANCE$1 : fromDir === "right" ? CORNER_BYPASS_CLEARANCE$1 : 0),
|
|
3073
|
+
y: from.y + (fromDir === "top" ? -CORNER_BYPASS_CLEARANCE$1 : fromDir === "bottom" ? CORNER_BYPASS_CLEARANCE$1 : 0)
|
|
3074
|
+
};
|
|
3075
|
+
const toOuter = {
|
|
3076
|
+
x: to.x + (toDir === "left" ? -CORNER_BYPASS_CLEARANCE$1 : toDir === "right" ? CORNER_BYPASS_CLEARANCE$1 : 0),
|
|
3077
|
+
y: to.y + (toDir === "top" ? -CORNER_BYPASS_CLEARANCE$1 : toDir === "bottom" ? CORNER_BYPASS_CLEARANCE$1 : 0)
|
|
3078
|
+
};
|
|
3079
|
+
const corner = isHorizontal$1(fromDir) ? { x: fromOuter.x, y: toOuter.y } : { x: toOuter.x, y: fromOuter.y };
|
|
3080
|
+
return [from, fromOuter, corner, toOuter, to];
|
|
3081
|
+
}
|
|
2744
3082
|
if (fromDir || toDir) {
|
|
2745
3083
|
return this.calculateDirectedPath(from, to, fromDir, toDir);
|
|
2746
3084
|
}
|
|
@@ -2800,6 +3138,14 @@ class PolylinePathStrategy {
|
|
|
2800
3138
|
return false;
|
|
2801
3139
|
}
|
|
2802
3140
|
}
|
|
3141
|
+
const SELF_LOOP_MIN_DISTANCE = 1;
|
|
3142
|
+
const SELF_LOOP_OFFSET = 90;
|
|
3143
|
+
const SELF_LOOP_SPREAD = 50;
|
|
3144
|
+
const CORNER_BYPASS_DISTANCE = 220;
|
|
3145
|
+
const CORNER_BYPASS_CLEARANCE = 80;
|
|
3146
|
+
const OPPOSITE_BYPASS_DISTANCE = 280;
|
|
3147
|
+
const OPPOSITE_BYPASS_CLEARANCE = 90;
|
|
3148
|
+
const OPPOSITE_BYPASS_ARC = 120;
|
|
2803
3149
|
function getDirectionOffset(dir, distance2) {
|
|
2804
3150
|
const offset = Math.min(distance2 * 0.5, BEZIER_MAX_OFFSET);
|
|
2805
3151
|
switch (dir) {
|
|
@@ -2815,11 +3161,138 @@ function getDirectionOffset(dir, distance2) {
|
|
|
2815
3161
|
return { x: 0, y: 0 };
|
|
2816
3162
|
}
|
|
2817
3163
|
}
|
|
3164
|
+
function createSelfLoopPath(point, dir) {
|
|
3165
|
+
switch (dir) {
|
|
3166
|
+
case "bottom":
|
|
3167
|
+
return [
|
|
3168
|
+
point,
|
|
3169
|
+
{ x: point.x - SELF_LOOP_SPREAD, y: point.y + SELF_LOOP_OFFSET },
|
|
3170
|
+
{ x: point.x + SELF_LOOP_SPREAD, y: point.y + SELF_LOOP_OFFSET },
|
|
3171
|
+
point
|
|
3172
|
+
];
|
|
3173
|
+
case "left":
|
|
3174
|
+
return [
|
|
3175
|
+
point,
|
|
3176
|
+
{ x: point.x - SELF_LOOP_OFFSET, y: point.y + SELF_LOOP_SPREAD },
|
|
3177
|
+
{ x: point.x - SELF_LOOP_OFFSET, y: point.y - SELF_LOOP_SPREAD },
|
|
3178
|
+
point
|
|
3179
|
+
];
|
|
3180
|
+
case "right":
|
|
3181
|
+
return [
|
|
3182
|
+
point,
|
|
3183
|
+
{ x: point.x + SELF_LOOP_OFFSET, y: point.y - SELF_LOOP_SPREAD },
|
|
3184
|
+
{ x: point.x + SELF_LOOP_OFFSET, y: point.y + SELF_LOOP_SPREAD },
|
|
3185
|
+
point
|
|
3186
|
+
];
|
|
3187
|
+
case "top":
|
|
3188
|
+
default:
|
|
3189
|
+
return [
|
|
3190
|
+
point,
|
|
3191
|
+
{ x: point.x + SELF_LOOP_SPREAD, y: point.y - SELF_LOOP_OFFSET },
|
|
3192
|
+
{ x: point.x - SELF_LOOP_SPREAD, y: point.y - SELF_LOOP_OFFSET },
|
|
3193
|
+
point
|
|
3194
|
+
];
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
function isHorizontal(dir) {
|
|
3198
|
+
return dir === "left" || dir === "right";
|
|
3199
|
+
}
|
|
3200
|
+
function isVertical(dir) {
|
|
3201
|
+
return dir === "top" || dir === "bottom";
|
|
3202
|
+
}
|
|
3203
|
+
function isOppositeDirections(fromDir, toDir) {
|
|
3204
|
+
return fromDir === "left" && toDir === "right" || fromDir === "right" && toDir === "left" || fromDir === "top" && toDir === "bottom" || fromDir === "bottom" && toDir === "top";
|
|
3205
|
+
}
|
|
3206
|
+
function dirToVector(dir) {
|
|
3207
|
+
switch (dir) {
|
|
3208
|
+
case "top":
|
|
3209
|
+
return { x: 0, y: -1 };
|
|
3210
|
+
case "bottom":
|
|
3211
|
+
return { x: 0, y: 1 };
|
|
3212
|
+
case "left":
|
|
3213
|
+
return { x: -1, y: 0 };
|
|
3214
|
+
case "right":
|
|
3215
|
+
return { x: 1, y: 0 };
|
|
3216
|
+
default:
|
|
3217
|
+
return { x: 0, y: 0 };
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3220
|
+
function createOppositeBypassPath(from, to, fromDir, toDir) {
|
|
3221
|
+
if (!fromDir || !toDir || !isOppositeDirections(fromDir, toDir)) {
|
|
3222
|
+
return null;
|
|
3223
|
+
}
|
|
3224
|
+
const fromOut = dirToVector(fromDir);
|
|
3225
|
+
const toOut = dirToVector(toDir);
|
|
3226
|
+
if (isHorizontal(fromDir) && isHorizontal(toDir)) {
|
|
3227
|
+
const sideY = from.x <= to.x ? -1 : 1;
|
|
3228
|
+
return [
|
|
3229
|
+
from,
|
|
3230
|
+
{
|
|
3231
|
+
x: from.x + fromOut.x * OPPOSITE_BYPASS_CLEARANCE,
|
|
3232
|
+
y: from.y + sideY * OPPOSITE_BYPASS_ARC
|
|
3233
|
+
},
|
|
3234
|
+
{
|
|
3235
|
+
x: to.x + toOut.x * OPPOSITE_BYPASS_CLEARANCE,
|
|
3236
|
+
y: to.y + sideY * OPPOSITE_BYPASS_ARC
|
|
3237
|
+
},
|
|
3238
|
+
to
|
|
3239
|
+
];
|
|
3240
|
+
}
|
|
3241
|
+
if (isVertical(fromDir) && isVertical(toDir)) {
|
|
3242
|
+
const sideX = from.y <= to.y ? 1 : -1;
|
|
3243
|
+
return [
|
|
3244
|
+
from,
|
|
3245
|
+
{
|
|
3246
|
+
x: from.x + sideX * OPPOSITE_BYPASS_ARC,
|
|
3247
|
+
y: from.y + fromOut.y * OPPOSITE_BYPASS_CLEARANCE
|
|
3248
|
+
},
|
|
3249
|
+
{
|
|
3250
|
+
x: to.x + sideX * OPPOSITE_BYPASS_ARC,
|
|
3251
|
+
y: to.y + toOut.y * OPPOSITE_BYPASS_CLEARANCE
|
|
3252
|
+
},
|
|
3253
|
+
to
|
|
3254
|
+
];
|
|
3255
|
+
}
|
|
3256
|
+
return null;
|
|
3257
|
+
}
|
|
3258
|
+
function createCornerBypassPath(from, to, fromDir, toDir) {
|
|
3259
|
+
if (!fromDir || !toDir) return null;
|
|
3260
|
+
const orthogonal = isHorizontal(fromDir) && isVertical(toDir) || isVertical(fromDir) && isHorizontal(toDir);
|
|
3261
|
+
if (!orthogonal) return null;
|
|
3262
|
+
const fromOut = dirToVector(fromDir);
|
|
3263
|
+
const toOut = dirToVector(toDir);
|
|
3264
|
+
const fromOuter = {
|
|
3265
|
+
x: from.x + fromOut.x * CORNER_BYPASS_CLEARANCE,
|
|
3266
|
+
y: from.y + fromOut.y * CORNER_BYPASS_CLEARANCE
|
|
3267
|
+
};
|
|
3268
|
+
const toOuter = {
|
|
3269
|
+
x: to.x + toOut.x * CORNER_BYPASS_CLEARANCE,
|
|
3270
|
+
y: to.y + toOut.y * CORNER_BYPASS_CLEARANCE
|
|
3271
|
+
};
|
|
3272
|
+
return [from, fromOuter, toOuter, to];
|
|
3273
|
+
}
|
|
2818
3274
|
class BezierPathStrategy {
|
|
2819
3275
|
calculatePath(from, to, fromDir, toDir, options) {
|
|
2820
3276
|
const dx = to.x - from.x;
|
|
2821
3277
|
const dy = to.y - from.y;
|
|
2822
3278
|
const distance2 = Math.sqrt(dx * dx + dy * dy);
|
|
3279
|
+
const loopDir = fromDir ?? toDir;
|
|
3280
|
+
const selfLoop = options?.selfLoop ?? false;
|
|
3281
|
+
if (selfLoop && distance2 < SELF_LOOP_MIN_DISTANCE && loopDir) {
|
|
3282
|
+
return createSelfLoopPath(from, loopDir);
|
|
3283
|
+
}
|
|
3284
|
+
if (selfLoop && distance2 < OPPOSITE_BYPASS_DISTANCE) {
|
|
3285
|
+
const oppositeBypass = createOppositeBypassPath(from, to, fromDir, toDir);
|
|
3286
|
+
if (oppositeBypass) {
|
|
3287
|
+
return oppositeBypass;
|
|
3288
|
+
}
|
|
3289
|
+
}
|
|
3290
|
+
if (selfLoop && distance2 < CORNER_BYPASS_DISTANCE) {
|
|
3291
|
+
const cornerBypass = createCornerBypassPath(from, to, fromDir, toDir);
|
|
3292
|
+
if (cornerBypass) {
|
|
3293
|
+
return cornerBypass;
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
2823
3296
|
const controlPoints = options?.controlPoints;
|
|
2824
3297
|
if (controlPoints && controlPoints.length > 0) {
|
|
2825
3298
|
if (controlPoints.length >= 3 && controlPoints.length % 3 === 0) {
|
|
@@ -3119,11 +3592,12 @@ class Edge extends Element {
|
|
|
3119
3592
|
* @param fromDir Direction at start point (top, right, bottom, left)
|
|
3120
3593
|
* @param toDir Direction at end point (top, right, bottom, left)
|
|
3121
3594
|
*/
|
|
3122
|
-
updateEndpoints(fromPoint, toPoint, fromDir, toDir) {
|
|
3595
|
+
updateEndpoints(fromPoint, toPoint, fromDir, toDir, options) {
|
|
3123
3596
|
this._fromPoint = fromPoint;
|
|
3124
3597
|
this._toPoint = toPoint;
|
|
3125
3598
|
this._fromDir = fromDir;
|
|
3126
3599
|
this._toDir = toDir;
|
|
3600
|
+
this._pathOptions = options;
|
|
3127
3601
|
this.recalculatePath();
|
|
3128
3602
|
}
|
|
3129
3603
|
/**
|
|
@@ -3136,7 +3610,9 @@ class Edge extends Element {
|
|
|
3136
3610
|
this._fromDir,
|
|
3137
3611
|
this._toDir,
|
|
3138
3612
|
{
|
|
3139
|
-
|
|
3613
|
+
...this._pathOptions,
|
|
3614
|
+
controlPoints: this._controlPoints,
|
|
3615
|
+
selfLoop: this._from.nodeId === this._to.nodeId
|
|
3140
3616
|
}
|
|
3141
3617
|
);
|
|
3142
3618
|
this.updateBounds();
|
|
@@ -3927,7 +4403,8 @@ class InteractionManager {
|
|
|
3927
4403
|
return;
|
|
3928
4404
|
}
|
|
3929
4405
|
if ("typeName" in hitElement) {
|
|
3930
|
-
|
|
4406
|
+
const editText = hitElement.label?.editableText ?? hitElement.label?.text ?? "";
|
|
4407
|
+
this.startInlineLabelEdit("node", hitElement.id, editText, hitElement.getLabelPosition());
|
|
3931
4408
|
return;
|
|
3932
4409
|
}
|
|
3933
4410
|
if ("from" in hitElement && "to" in hitElement) {
|
|
@@ -4084,7 +4561,12 @@ class InteractionManager {
|
|
|
4084
4561
|
node.label = value;
|
|
4085
4562
|
return;
|
|
4086
4563
|
}
|
|
4087
|
-
node.label.
|
|
4564
|
+
if (node.label.editableText !== void 0) {
|
|
4565
|
+
node.label.editableText = value;
|
|
4566
|
+
node.label.text = value;
|
|
4567
|
+
} else {
|
|
4568
|
+
node.label.text = value;
|
|
4569
|
+
}
|
|
4088
4570
|
});
|
|
4089
4571
|
return;
|
|
4090
4572
|
}
|
|
@@ -5332,18 +5814,18 @@ class DiagramRenderer extends EventEmitter {
|
|
|
5332
5814
|
}
|
|
5333
5815
|
}
|
|
5334
5816
|
this.updateEdgeEndpoints();
|
|
5335
|
-
for (const edge of this._edges.values()) {
|
|
5336
|
-
if (edge.visible) {
|
|
5337
|
-
this.renderElementWithAnimation(ctx, edge, () => edge.render(ctx));
|
|
5338
|
-
edge.clearDirty();
|
|
5339
|
-
}
|
|
5340
|
-
}
|
|
5341
5817
|
for (const node of this._nodes.values()) {
|
|
5342
5818
|
if (node.visible) {
|
|
5343
5819
|
this.renderElementWithAnimation(ctx, node, () => node.render(ctx));
|
|
5344
5820
|
node.clearDirty();
|
|
5345
5821
|
}
|
|
5346
5822
|
}
|
|
5823
|
+
for (const edge of this._edges.values()) {
|
|
5824
|
+
if (edge.visible) {
|
|
5825
|
+
this.renderElementWithAnimation(ctx, edge, () => edge.render(ctx));
|
|
5826
|
+
edge.clearDirty();
|
|
5827
|
+
}
|
|
5828
|
+
}
|
|
5347
5829
|
for (const edge of this._edges.values()) {
|
|
5348
5830
|
if (edge.visible) {
|
|
5349
5831
|
edge.renderHandles(ctx);
|
|
@@ -5425,7 +5907,14 @@ class DiagramRenderer extends EventEmitter {
|
|
|
5425
5907
|
toDir = anchorId.split(":")[0];
|
|
5426
5908
|
}
|
|
5427
5909
|
}
|
|
5428
|
-
|
|
5910
|
+
const obstacles = Array.from(this._nodes.values()).map((node) => ({
|
|
5911
|
+
x: node.x - 8,
|
|
5912
|
+
y: node.y - 8,
|
|
5913
|
+
width: node.width + 16,
|
|
5914
|
+
height: node.height + 16,
|
|
5915
|
+
role: node.id === edge.from.nodeId ? "source" : node.id === edge.to.nodeId ? "target" : "other"
|
|
5916
|
+
}));
|
|
5917
|
+
edge.updateEndpoints(fromPoint, toPoint, fromDir, toDir, { obstacles });
|
|
5429
5918
|
}
|
|
5430
5919
|
}
|
|
5431
5920
|
renderScrollbars(ctx) {
|