@ngroznykh/papirus 0.8.1 → 0.8.3
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/EdgeEndpointUpdater.d.ts +5 -7
- package/dist/core/EdgeEndpointUpdater.d.ts.map +1 -1
- package/dist/elements/paths/PolylinePathStrategy.d.ts.map +1 -1
- package/dist/elements/paths/routeOrthogonalAround.d.ts +12 -1
- package/dist/elements/paths/routeOrthogonalAround.d.ts.map +1 -1
- package/dist/papirus.js +339 -45
- package/dist/papirus.js.map +1 -1
- package/package.json +1 -1
|
@@ -21,20 +21,18 @@ export declare class EdgeEndpointUpdater {
|
|
|
21
21
|
private resolveEdgeEndpoint;
|
|
22
22
|
private lockAnchors;
|
|
23
23
|
/**
|
|
24
|
-
* When nodes are clearly stacked,
|
|
25
|
-
*
|
|
24
|
+
* When nodes are clearly stacked/side-by-side, assign facing sides only to
|
|
25
|
+
* unset endpoints. Intentional ports (including lateral reconnect with
|
|
26
|
+
* attachToOutline off) and outlineParams are kept — orthogonal routing clears
|
|
27
|
+
* same-side wraps around the stack.
|
|
26
28
|
*
|
|
27
29
|
* Uses bounding-box gaps (not center dx/dy): a large horizontal offset must not
|
|
28
30
|
* flip bottom→left while the source is still below the target — that reintroduces
|
|
29
31
|
* the left-edge contour jog when the user drags the node leftward.
|
|
30
|
-
*
|
|
31
|
-
* - Always set facing ports when unset (so lockAnchors does not pick left/right).
|
|
32
|
-
* - Rewrite target left/right → top/bottom when there is a clear vertical gap.
|
|
33
|
-
* - Do not rewrite an intentional source side exit (e.g. left exit from a nested
|
|
34
|
-
* component around its parent).
|
|
35
32
|
*/
|
|
36
33
|
private preferFacingSidePorts;
|
|
37
34
|
private portSide;
|
|
35
|
+
private endpointSide;
|
|
38
36
|
private nearestAnchorOnSide;
|
|
39
37
|
private setEndpointSidePort;
|
|
40
38
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EdgeEndpointUpdater.d.ts","sourceRoot":"","sources":["../../src/core/EdgeEndpointUpdater.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAOrD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,IAAI,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACtC,QAAQ,IAAI,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACtC,gBAAgB,IAAI,YAAY,EAAE,CAAC;CACpC;AAOD,qBAAa,mBAAmB;IAClB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,uBAAuB;IAE1D,aAAa,IAAI,IAAI;IAMrB,SAAS,IAAI,IAAI;IAIjB,OAAO,CAAC,MAAM;IA+Bd,OAAO,CAAC,kBAAkB;IA2D1B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IA8C9B,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,mBAAmB;IAe3B,OAAO,CAAC,WAAW;IAqBnB
|
|
1
|
+
{"version":3,"file":"EdgeEndpointUpdater.d.ts","sourceRoot":"","sources":["../../src/core/EdgeEndpointUpdater.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAOrD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,IAAI,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACtC,QAAQ,IAAI,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACtC,gBAAgB,IAAI,YAAY,EAAE,CAAC;CACpC;AAOD,qBAAa,mBAAmB;IAClB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,uBAAuB;IAE1D,aAAa,IAAI,IAAI;IAMrB,SAAS,IAAI,IAAI;IAIjB,OAAO,CAAC,MAAM;IA+Bd,OAAO,CAAC,kBAAkB;IA2D1B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IA8C9B,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,mBAAmB;IAe3B,OAAO,CAAC,WAAW;IAqBnB;;;;;;;;;OASG;IACH,OAAO,CAAC,qBAAqB;IA2C7B,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,mBAAmB;IAmB3B,OAAO,CAAC,mBAAmB;IAqB3B;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IA6B3B,OAAO,CAAC,uBAAuB;CAOhC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PolylinePathStrategy.d.ts","sourceRoot":"","sources":["../../../src/elements/paths/PolylinePathStrategy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAgB,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"PolylinePathStrategy.d.ts","sourceRoot":"","sources":["../../../src/elements/paths/PolylinePathStrategy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAgB,MAAM,gBAAgB,CAAC;AAgctF;;GAEG;AACH,qBAAa,oBAAqB,YAAW,YAAY;IACvD,aAAa,CACX,IAAI,EAAE,KAAK,EACX,EAAE,EAAE,KAAK,EACT,OAAO,CAAC,EAAE,MAAM,EAChB,KAAK,CAAC,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,mBAAmB,GAC7B,KAAK,EAAE;IAkNV,OAAO,CAAC,qBAAqB;IAgF7B,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO;CAQjE"}
|
|
@@ -17,6 +17,11 @@ export type RouteOrthogonalAroundInput = {
|
|
|
17
17
|
/** Source node owning `from` — avoid re-entering after exit. */
|
|
18
18
|
source?: RouteRect;
|
|
19
19
|
target?: RouteRect;
|
|
20
|
+
/**
|
|
21
|
+
* Sibling / other nodes to avoid (e.g. stack mates inside a container).
|
|
22
|
+
* Not used as exit blockers — only as mid-path obstacles.
|
|
23
|
+
*/
|
|
24
|
+
others?: RouteRect[];
|
|
20
25
|
margin?: number;
|
|
21
26
|
exitDistance?: number;
|
|
22
27
|
};
|
|
@@ -55,6 +60,12 @@ export declare function clampFacingExitEntry(from: Point, to: Point, fromDir: Si
|
|
|
55
60
|
endEntry: Point;
|
|
56
61
|
};
|
|
57
62
|
export declare function routeOrthogonalAround(input: RouteOrthogonalAroundInput): Point[];
|
|
63
|
+
export interface EnsureOrthogonalOptions {
|
|
64
|
+
/** Prefer leaving the first point along this side (avoids licking source face). */
|
|
65
|
+
exitDir?: Side;
|
|
66
|
+
/** Prefer arriving at the last point along this side. */
|
|
67
|
+
enterDir?: Side;
|
|
68
|
+
}
|
|
58
69
|
/** Insert elbows so no consecutive points form a diagonal (safety net). */
|
|
59
|
-
export declare function ensureOrthogonalPath(path: Point[]): Point[];
|
|
70
|
+
export declare function ensureOrthogonalPath(path: Point[], options?: EnsureOrthogonalOptions): Point[];
|
|
60
71
|
//# sourceMappingURL=routeOrthogonalAround.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routeOrthogonalAround.d.ts","sourceRoot":"","sources":["../../../src/elements/paths/routeOrthogonalAround.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC,MAAM,MAAM,IAAI,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEvD,MAAM,MAAM,SAAS,GAAG;IACtB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,IAAI,EAAE,KAAK,CAAC;IACZ,EAAE,EAAE,KAAK,CAAC;IACV,OAAO,EAAE,IAAI,CAAC;IACd,KAAK,EAAE,IAAI,CAAC;IACZ,gEAAgE;IAChE,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,gEAAgE;IAChE,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAaF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE,SAAS,EACf,IAAI,EAAE,SAAS,EACf,WAAW,SAAI,GACd,CAAC,SAAS,EAAE,SAAS,CAAC,CAiDxB;AAUD,+DAA+D;AAC/D,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,GAAG,KAAK,GAAG,IAAI,CA0B3E;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,CAAC,EAAE,SAAS,EACZ,CAAC,EAAE,SAAS,GACX;IAAE,CAAC,EAAE,SAAS,CAAC;IAAC,CAAC,EAAE,SAAS,CAAA;CAAE,GAAG,IAAI,CAQvC;AAED,2FAA2F;AAC3F,wBAAgB,aAAa,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,GAAG,MAAM,CAMhE;AAmBD,kFAAkF;AAClF,wBAAgB,6BAA6B,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,SAAS,GAAG,OAAO,CAcvF;AAUD;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,MAAM,CA6B1F;
|
|
1
|
+
{"version":3,"file":"routeOrthogonalAround.d.ts","sourceRoot":"","sources":["../../../src/elements/paths/routeOrthogonalAround.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC,MAAM,MAAM,IAAI,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEvD,MAAM,MAAM,SAAS,GAAG;IACtB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,IAAI,EAAE,KAAK,CAAC;IACZ,EAAE,EAAE,KAAK,CAAC;IACV,OAAO,EAAE,IAAI,CAAC;IACd,KAAK,EAAE,IAAI,CAAC;IACZ,gEAAgE;IAChE,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,gEAAgE;IAChE,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAaF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE,SAAS,EACf,IAAI,EAAE,SAAS,EACf,WAAW,SAAI,GACd,CAAC,SAAS,EAAE,SAAS,CAAC,CAiDxB;AAUD,+DAA+D;AAC/D,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,GAAG,KAAK,GAAG,IAAI,CA0B3E;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,CAAC,EAAE,SAAS,EACZ,CAAC,EAAE,SAAS,GACX;IAAE,CAAC,EAAE,SAAS,CAAC;IAAC,CAAC,EAAE,SAAS,CAAA;CAAE,GAAG,IAAI,CAQvC;AAED,2FAA2F;AAC3F,wBAAgB,aAAa,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,GAAG,MAAM,CAMhE;AAmBD,kFAAkF;AAClF,wBAAgB,6BAA6B,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,SAAS,GAAG,OAAO,CAcvF;AAUD;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,MAAM,CA6B1F;AA2ND;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,KAAK,EACX,EAAE,EAAE,KAAK,EACT,OAAO,EAAE,IAAI,EACb,KAAK,EAAE,IAAI,EACX,SAAS,EAAE,KAAK,EAChB,QAAQ,EAAE,KAAK,GACd;IAAE,SAAS,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,KAAK,CAAA;CAAE,CA8BvC;AAiWD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,0BAA0B,GAAG,KAAK,EAAE,CA6LhF;AA0BD,MAAM,WAAW,uBAAuB;IACtC,mFAAmF;IACnF,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,yDAAyD;IACzD,QAAQ,CAAC,EAAE,IAAI,CAAC;CACjB;AAED,2EAA2E;AAC3E,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,KAAK,EAAE,EACb,OAAO,CAAC,EAAE,uBAAuB,GAChC,KAAK,EAAE,CAwBT"}
|
package/dist/papirus.js
CHANGED
|
@@ -4038,11 +4038,11 @@ function contourProximityPenalty(a, b, obstacles) {
|
|
|
4038
4038
|
const proximity = 1 - minDist / CONTOUR_BAND;
|
|
4039
4039
|
return segLen * (CONTOUR_PENALTY / 10) * proximity;
|
|
4040
4040
|
}
|
|
4041
|
-
/** Push along dir until outside expanded
|
|
4042
|
-
function computeStartExit(from, fromDir,
|
|
4041
|
+
/** Push along dir until outside expanded blocker (and at least exitDistance). */
|
|
4042
|
+
function computeStartExit(from, fromDir, blocker, margin, exitDistance) {
|
|
4043
4043
|
let p = moveByDir$1(from, fromDir, exitDistance);
|
|
4044
|
-
if (!
|
|
4045
|
-
const expanded = expandRect(
|
|
4044
|
+
if (!blocker) return p;
|
|
4045
|
+
const expanded = expandRect(blocker, margin);
|
|
4046
4046
|
let guard = 0;
|
|
4047
4047
|
while (pointInOpenInterior(p, expanded) && guard++ < 64) p = moveByDir$1(p, fromDir, exitDistance);
|
|
4048
4048
|
if (pointInOpenInterior(p, expanded)) {
|
|
@@ -4076,6 +4076,158 @@ function computeStartExit(from, fromDir, parent, margin, exitDistance) {
|
|
|
4076
4076
|
}
|
|
4077
4077
|
return p;
|
|
4078
4078
|
}
|
|
4079
|
+
function pointInRectClosed(p, r) {
|
|
4080
|
+
return p.x >= r.x && p.x <= r.x + r.width && p.y >= r.y && p.y <= r.y + r.height;
|
|
4081
|
+
}
|
|
4082
|
+
/**
|
|
4083
|
+
* Push a same-side exit/entry far enough that the orthogonal span between
|
|
4084
|
+
* `spanA` and `spanB` clears every `others` obstacle (siblings in a stack).
|
|
4085
|
+
*/
|
|
4086
|
+
function clearCorridorOfOthers(exit, dir, others, spanA, spanB, margin) {
|
|
4087
|
+
if (others.length === 0) return exit;
|
|
4088
|
+
let p = { ...exit };
|
|
4089
|
+
const y1 = Math.min(spanA.y, spanB.y);
|
|
4090
|
+
const y2 = Math.max(spanA.y, spanB.y);
|
|
4091
|
+
const x1 = Math.min(spanA.x, spanB.x);
|
|
4092
|
+
const x2 = Math.max(spanA.x, spanB.x);
|
|
4093
|
+
for (let guard = 0; guard < 64; guard++) {
|
|
4094
|
+
let blocked;
|
|
4095
|
+
for (const raw of others) {
|
|
4096
|
+
const o = expandRect(raw, margin);
|
|
4097
|
+
if (dir === "left" || dir === "right") {
|
|
4098
|
+
if (p.x > o.x && p.x < o.x + o.width && y2 > o.y && y1 < o.y + o.height) {
|
|
4099
|
+
blocked = o;
|
|
4100
|
+
break;
|
|
4101
|
+
}
|
|
4102
|
+
} else if (p.y > o.y && p.y < o.y + o.height && x2 > o.x && x1 < o.x + o.width) {
|
|
4103
|
+
blocked = o;
|
|
4104
|
+
break;
|
|
4105
|
+
}
|
|
4106
|
+
}
|
|
4107
|
+
if (!blocked) return p;
|
|
4108
|
+
switch (dir) {
|
|
4109
|
+
case "left":
|
|
4110
|
+
p = {
|
|
4111
|
+
x: blocked.x - 1,
|
|
4112
|
+
y: p.y
|
|
4113
|
+
};
|
|
4114
|
+
break;
|
|
4115
|
+
case "right":
|
|
4116
|
+
p = {
|
|
4117
|
+
x: blocked.x + blocked.width + 1,
|
|
4118
|
+
y: p.y
|
|
4119
|
+
};
|
|
4120
|
+
break;
|
|
4121
|
+
case "top":
|
|
4122
|
+
p = {
|
|
4123
|
+
x: p.x,
|
|
4124
|
+
y: blocked.y - 1
|
|
4125
|
+
};
|
|
4126
|
+
break;
|
|
4127
|
+
case "bottom":
|
|
4128
|
+
p = {
|
|
4129
|
+
x: p.x,
|
|
4130
|
+
y: blocked.y + blocked.height + 1
|
|
4131
|
+
};
|
|
4132
|
+
break;
|
|
4133
|
+
}
|
|
4134
|
+
}
|
|
4135
|
+
return p;
|
|
4136
|
+
}
|
|
4137
|
+
/**
|
|
4138
|
+
* When same-side exits still share an axis that crosses a blocker (top→top over
|
|
4139
|
+
* a stacked target), slide the *start* exit to the nearer free side. Keep
|
|
4140
|
+
* endEntry on the approach ray at `to` so the final stub stays ⊥ to the side.
|
|
4141
|
+
*/
|
|
4142
|
+
function offsetSameSideAroundBlockers(startExit, endEntry, dir, blockers, margin, from, to) {
|
|
4143
|
+
if (blockers.length === 0) return {
|
|
4144
|
+
startExit,
|
|
4145
|
+
endEntry
|
|
4146
|
+
};
|
|
4147
|
+
let s = { ...startExit };
|
|
4148
|
+
const e = { ...endEntry };
|
|
4149
|
+
const y1 = Math.min(from.y, to.y, s.y, e.y);
|
|
4150
|
+
const y2 = Math.max(from.y, to.y, s.y, e.y);
|
|
4151
|
+
const x1 = Math.min(from.x, to.x, s.x, e.x);
|
|
4152
|
+
const x2 = Math.max(from.x, to.x, s.x, e.x);
|
|
4153
|
+
for (const raw of blockers) {
|
|
4154
|
+
const o = expandRect(raw, margin);
|
|
4155
|
+
if (dir === "top" || dir === "bottom") {
|
|
4156
|
+
if (!(s.x > o.x && s.x < o.x + o.width && y2 > o.y && y1 < o.y + o.height)) continue;
|
|
4157
|
+
const leftX = o.x - 1;
|
|
4158
|
+
const rightX = o.x + o.width + 1;
|
|
4159
|
+
s = {
|
|
4160
|
+
...s,
|
|
4161
|
+
x: s.x <= o.x + o.width / 2 ? leftX : rightX
|
|
4162
|
+
};
|
|
4163
|
+
} else {
|
|
4164
|
+
if (!(s.y > o.y && s.y < o.y + o.height && x2 > o.x && x1 < o.x + o.width)) continue;
|
|
4165
|
+
const topY = o.y - 1;
|
|
4166
|
+
const bottomY = o.y + o.height + 1;
|
|
4167
|
+
s = {
|
|
4168
|
+
...s,
|
|
4169
|
+
y: s.y <= o.y + o.height / 2 ? topY : bottomY
|
|
4170
|
+
};
|
|
4171
|
+
}
|
|
4172
|
+
}
|
|
4173
|
+
return {
|
|
4174
|
+
startExit: s,
|
|
4175
|
+
endEntry: e
|
|
4176
|
+
};
|
|
4177
|
+
}
|
|
4178
|
+
/** True when collapsing same-side exits onto one rail would not pierce blockers. */
|
|
4179
|
+
function canAlignSameSideExits(dir, startExit, endEntry, from, to, blockers, margin) {
|
|
4180
|
+
let alignedStart = { ...startExit };
|
|
4181
|
+
let alignedEnd = { ...endEntry };
|
|
4182
|
+
if (dir === "left") {
|
|
4183
|
+
const x = Math.min(startExit.x, endEntry.x);
|
|
4184
|
+
alignedStart = {
|
|
4185
|
+
...alignedStart,
|
|
4186
|
+
x
|
|
4187
|
+
};
|
|
4188
|
+
alignedEnd = {
|
|
4189
|
+
...alignedEnd,
|
|
4190
|
+
x
|
|
4191
|
+
};
|
|
4192
|
+
} else if (dir === "right") {
|
|
4193
|
+
const x = Math.max(startExit.x, endEntry.x);
|
|
4194
|
+
alignedStart = {
|
|
4195
|
+
...alignedStart,
|
|
4196
|
+
x
|
|
4197
|
+
};
|
|
4198
|
+
alignedEnd = {
|
|
4199
|
+
...alignedEnd,
|
|
4200
|
+
x
|
|
4201
|
+
};
|
|
4202
|
+
} else if (dir === "top") {
|
|
4203
|
+
const y = Math.min(startExit.y, endEntry.y);
|
|
4204
|
+
alignedStart = {
|
|
4205
|
+
...alignedStart,
|
|
4206
|
+
y
|
|
4207
|
+
};
|
|
4208
|
+
alignedEnd = {
|
|
4209
|
+
...alignedEnd,
|
|
4210
|
+
y
|
|
4211
|
+
};
|
|
4212
|
+
} else {
|
|
4213
|
+
const y = Math.max(startExit.y, endEntry.y);
|
|
4214
|
+
alignedStart = {
|
|
4215
|
+
...alignedStart,
|
|
4216
|
+
y
|
|
4217
|
+
};
|
|
4218
|
+
alignedEnd = {
|
|
4219
|
+
...alignedEnd,
|
|
4220
|
+
y
|
|
4221
|
+
};
|
|
4222
|
+
}
|
|
4223
|
+
for (const raw of blockers) {
|
|
4224
|
+
const o = expandRect(raw, margin);
|
|
4225
|
+
if (segmentIntersectsOpenInterior(from, alignedStart, o)) return false;
|
|
4226
|
+
if (segmentIntersectsOpenInterior(alignedEnd, to, o)) return false;
|
|
4227
|
+
if (segmentIntersectsOpenInterior(alignedStart, alignedEnd, o)) return false;
|
|
4228
|
+
}
|
|
4229
|
+
return true;
|
|
4230
|
+
}
|
|
4079
4231
|
function computeEndEntry(to, toDir, exitDistance, rawTarget, rawNeighbor) {
|
|
4080
4232
|
const stub = moveByDir$1(to, toDir, exitDistance);
|
|
4081
4233
|
if (!rawTarget || !rawNeighbor) return stub;
|
|
@@ -4506,14 +4658,69 @@ function routeMidPath(startExit, endEntry, obstacles, parent, target, margin) {
|
|
|
4506
4658
|
function routeOrthogonalAround(input) {
|
|
4507
4659
|
const margin = input.margin ?? DEFAULT_MARGIN;
|
|
4508
4660
|
const exitDistance = input.exitDistance ?? DEFAULT_EXIT;
|
|
4509
|
-
const
|
|
4510
|
-
const
|
|
4511
|
-
const
|
|
4661
|
+
const others = input.others ?? [];
|
|
4662
|
+
const internalParent = !!input.parent && pointInRectClosed(input.from, input.parent) && pointInRectClosed(input.to, input.parent);
|
|
4663
|
+
const exitBlocker = internalParent ? input.source : input.parent ?? input.source;
|
|
4664
|
+
let rawStartExit = computeStartExit(input.from, input.fromDir, exitBlocker, margin, exitDistance);
|
|
4665
|
+
const gapNeighbor = internalParent ? input.source : input.parent ?? input.source;
|
|
4512
4666
|
const peeledTargetNeighbor = input.target && gapNeighbor ? peelForFacingGap(input.target, gapNeighbor) : null;
|
|
4513
|
-
|
|
4667
|
+
let rawEndEntry = computeEndEntry(input.to, input.toDir, exitDistance, peeledTargetNeighbor?.a, peeledTargetNeighbor?.b);
|
|
4668
|
+
if (input.fromDir === input.toDir) {
|
|
4669
|
+
const sameSideBlockers = [...others];
|
|
4670
|
+
if (input.target) sameSideBlockers.push(input.target);
|
|
4671
|
+
if (input.source) sameSideBlockers.push(input.source);
|
|
4672
|
+
if (sameSideBlockers.length > 0) {
|
|
4673
|
+
rawStartExit = clearCorridorOfOthers(rawStartExit, input.fromDir, sameSideBlockers, input.from, input.to, margin);
|
|
4674
|
+
rawEndEntry = clearCorridorOfOthers(rawEndEntry, input.toDir, sameSideBlockers, input.from, input.to, margin);
|
|
4675
|
+
({startExit: rawStartExit, endEntry: rawEndEntry} = offsetSameSideAroundBlockers(rawStartExit, rawEndEntry, input.fromDir, sameSideBlockers, margin, input.from, input.to));
|
|
4676
|
+
}
|
|
4677
|
+
if (canAlignSameSideExits(input.fromDir, rawStartExit, rawEndEntry, input.from, input.to, sameSideBlockers.length > 0 ? sameSideBlockers : [input.target, input.source].filter((r) => !!r), margin)) {
|
|
4678
|
+
if (input.fromDir === "left") {
|
|
4679
|
+
const x = Math.min(rawStartExit.x, rawEndEntry.x);
|
|
4680
|
+
rawStartExit = {
|
|
4681
|
+
...rawStartExit,
|
|
4682
|
+
x
|
|
4683
|
+
};
|
|
4684
|
+
rawEndEntry = {
|
|
4685
|
+
...rawEndEntry,
|
|
4686
|
+
x
|
|
4687
|
+
};
|
|
4688
|
+
} else if (input.fromDir === "right") {
|
|
4689
|
+
const x = Math.max(rawStartExit.x, rawEndEntry.x);
|
|
4690
|
+
rawStartExit = {
|
|
4691
|
+
...rawStartExit,
|
|
4692
|
+
x
|
|
4693
|
+
};
|
|
4694
|
+
rawEndEntry = {
|
|
4695
|
+
...rawEndEntry,
|
|
4696
|
+
x
|
|
4697
|
+
};
|
|
4698
|
+
} else if (input.fromDir === "top") {
|
|
4699
|
+
const y = Math.min(rawStartExit.y, rawEndEntry.y);
|
|
4700
|
+
rawStartExit = {
|
|
4701
|
+
...rawStartExit,
|
|
4702
|
+
y
|
|
4703
|
+
};
|
|
4704
|
+
rawEndEntry = {
|
|
4705
|
+
...rawEndEntry,
|
|
4706
|
+
y
|
|
4707
|
+
};
|
|
4708
|
+
} else if (input.fromDir === "bottom") {
|
|
4709
|
+
const y = Math.max(rawStartExit.y, rawEndEntry.y);
|
|
4710
|
+
rawStartExit = {
|
|
4711
|
+
...rawStartExit,
|
|
4712
|
+
y
|
|
4713
|
+
};
|
|
4714
|
+
rawEndEntry = {
|
|
4715
|
+
...rawEndEntry,
|
|
4716
|
+
y
|
|
4717
|
+
};
|
|
4718
|
+
}
|
|
4719
|
+
}
|
|
4720
|
+
}
|
|
4514
4721
|
const { startExit, endEntry } = clampFacingExitEntry(input.from, input.to, input.fromDir, input.toDir, rawStartExit, rawEndEntry);
|
|
4515
4722
|
let sourceObs = input.source ? expandRect(input.source, margin) : void 0;
|
|
4516
|
-
let parentObs = input.parent ? expandRect(input.parent, margin) : void 0;
|
|
4723
|
+
let parentObs = input.parent && !internalParent ? expandRect(input.parent, margin) : void 0;
|
|
4517
4724
|
let targetObs = input.target ? expandRect(input.target, margin) : void 0;
|
|
4518
4725
|
const openCorridor = (expA, expB, inputA, inputB) => {
|
|
4519
4726
|
const peeled = peelForFacingGap(inputA, inputB);
|
|
@@ -4527,8 +4734,9 @@ function routeOrthogonalAround(input) {
|
|
|
4527
4734
|
if (sourceObs) obstacles.push(sourceObs);
|
|
4528
4735
|
if (parentObs) obstacles.push(parentObs);
|
|
4529
4736
|
if (targetObs) obstacles.push(targetObs);
|
|
4530
|
-
const
|
|
4531
|
-
const
|
|
4737
|
+
for (const other of others) obstacles.push(expandRect(other, margin));
|
|
4738
|
+
const peeledLattice = !internalParent && input.parent && input.target ? peelForFacingGap(input.parent, input.target) : input.source && input.target ? peelForFacingGap(input.source, input.target) : null;
|
|
4739
|
+
const mid = routeMidPath(startExit, endEntry, obstacles, peeledLattice?.a ?? (internalParent ? input.source : input.parent ?? input.source), peeledLattice?.b ?? input.target, margin).slice(1, -1);
|
|
4532
4740
|
const lastMid = mid[mid.length - 1];
|
|
4533
4741
|
const alreadyApproaching = !!lastMid && isOnApproachOutside(lastMid, input.to, input.toDir, 1);
|
|
4534
4742
|
return simplifyPath$1(ensureOrthogonalPath([
|
|
@@ -4537,19 +4745,60 @@ function routeOrthogonalAround(input) {
|
|
|
4537
4745
|
...mid,
|
|
4538
4746
|
...alreadyApproaching ? [] : [endEntry],
|
|
4539
4747
|
input.to
|
|
4540
|
-
]
|
|
4748
|
+
], {
|
|
4749
|
+
exitDir: input.fromDir,
|
|
4750
|
+
enterDir: input.toDir
|
|
4751
|
+
}));
|
|
4752
|
+
}
|
|
4753
|
+
/** Elbow that leaves `prev` along `dir` first (source exit). */
|
|
4754
|
+
function elbowExitFirst(prev, next, dir) {
|
|
4755
|
+
switch (dir) {
|
|
4756
|
+
case "left":
|
|
4757
|
+
case "right": return {
|
|
4758
|
+
x: next.x,
|
|
4759
|
+
y: prev.y
|
|
4760
|
+
};
|
|
4761
|
+
case "top":
|
|
4762
|
+
case "bottom": return {
|
|
4763
|
+
x: prev.x,
|
|
4764
|
+
y: next.y
|
|
4765
|
+
};
|
|
4766
|
+
}
|
|
4767
|
+
}
|
|
4768
|
+
/** Elbow that arrives at `next` along `dir` last (target entry). */
|
|
4769
|
+
function elbowEnterLast(prev, next, dir) {
|
|
4770
|
+
switch (dir) {
|
|
4771
|
+
case "left":
|
|
4772
|
+
case "right": return {
|
|
4773
|
+
x: prev.x,
|
|
4774
|
+
y: next.y
|
|
4775
|
+
};
|
|
4776
|
+
case "top":
|
|
4777
|
+
case "bottom": return {
|
|
4778
|
+
x: next.x,
|
|
4779
|
+
y: prev.y
|
|
4780
|
+
};
|
|
4781
|
+
}
|
|
4541
4782
|
}
|
|
4542
4783
|
/** Insert elbows so no consecutive points form a diagonal (safety net). */
|
|
4543
|
-
function ensureOrthogonalPath(path) {
|
|
4784
|
+
function ensureOrthogonalPath(path, options) {
|
|
4544
4785
|
if (path.length < 2) return path;
|
|
4545
4786
|
const out = [path[0]];
|
|
4546
4787
|
for (let i = 1; i < path.length; i++) {
|
|
4547
4788
|
const prev = out[out.length - 1];
|
|
4548
4789
|
const next = path[i];
|
|
4549
|
-
if (!(Math.abs(prev.x - next.x) < .001 || Math.abs(prev.y - next.y) < .001))
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4790
|
+
if (!(Math.abs(prev.x - next.x) < .001 || Math.abs(prev.y - next.y) < .001)) {
|
|
4791
|
+
const isFirst = out.length === 1;
|
|
4792
|
+
const isLast = i === path.length - 1;
|
|
4793
|
+
let elbow;
|
|
4794
|
+
if (isFirst && options?.exitDir) elbow = elbowExitFirst(prev, next, options.exitDir);
|
|
4795
|
+
else if (isLast && options?.enterDir) elbow = elbowEnterLast(prev, next, options.enterDir);
|
|
4796
|
+
else elbow = {
|
|
4797
|
+
x: prev.x,
|
|
4798
|
+
y: next.y
|
|
4799
|
+
};
|
|
4800
|
+
out.push(elbow);
|
|
4801
|
+
}
|
|
4553
4802
|
out.push(next);
|
|
4554
4803
|
}
|
|
4555
4804
|
return out;
|
|
@@ -4582,17 +4831,58 @@ function routeDebug(tag, payload) {
|
|
|
4582
4831
|
function isSide(v) {
|
|
4583
4832
|
return v === "top" || v === "right" || v === "bottom" || v === "left";
|
|
4584
4833
|
}
|
|
4834
|
+
function pointInObstacle(p, o) {
|
|
4835
|
+
return p.x >= o.x && p.x <= o.x + o.width && p.y >= o.y && p.y <= o.y + o.height;
|
|
4836
|
+
}
|
|
4837
|
+
function pointOnObstacleOutline(p, o, tol = 1) {
|
|
4838
|
+
const onVertical = (Math.abs(p.x - o.x) < tol || Math.abs(p.x - (o.x + o.width)) < tol) && p.y >= o.y - tol && p.y <= o.y + o.height + tol;
|
|
4839
|
+
const onHorizontal = (Math.abs(p.y - o.y) < tol || Math.abs(p.y - (o.y + o.height)) < tol) && p.x >= o.x - tol && p.x <= o.x + o.width + tol;
|
|
4840
|
+
return onVertical || onHorizontal;
|
|
4841
|
+
}
|
|
4842
|
+
/** Prefer the tightest rect that contains `p` (source/composite vs ancestor). */
|
|
4843
|
+
function smallestContaining(p, obstacles, exclude) {
|
|
4844
|
+
let best;
|
|
4845
|
+
let bestArea = Infinity;
|
|
4846
|
+
for (const o of obstacles) {
|
|
4847
|
+
if (exclude.has(o) || !pointInObstacle(p, o)) continue;
|
|
4848
|
+
const area = o.width * o.height;
|
|
4849
|
+
if (area < bestArea) {
|
|
4850
|
+
best = o;
|
|
4851
|
+
bestArea = area;
|
|
4852
|
+
}
|
|
4853
|
+
}
|
|
4854
|
+
return best;
|
|
4855
|
+
}
|
|
4585
4856
|
function resolveRoutingRoles(from, to, obstacles) {
|
|
4586
|
-
const target = obstacles.find((o) => o.role === "target") ?? obstacles.find((o) =>
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4857
|
+
const target = obstacles.find((o) => o.role === "target") ?? obstacles.find((o) => pointOnObstacleOutline(to, o));
|
|
4858
|
+
const excludeForSource = /* @__PURE__ */ new Set();
|
|
4859
|
+
if (target) excludeForSource.add(target);
|
|
4860
|
+
const source = obstacles.find((o) => o.role === "source" && o !== target) ?? smallestContaining(from, obstacles, excludeForSource);
|
|
4861
|
+
const excludeForParent = /* @__PURE__ */ new Set();
|
|
4862
|
+
if (target) excludeForParent.add(target);
|
|
4863
|
+
if (source) excludeForParent.add(source);
|
|
4864
|
+
const parent = smallestContaining(from, obstacles, excludeForParent);
|
|
4865
|
+
const spanPad = 40;
|
|
4866
|
+
const span = {
|
|
4867
|
+
x: Math.min(from.x, to.x) - spanPad,
|
|
4868
|
+
y: Math.min(from.y, to.y) - spanPad,
|
|
4869
|
+
width: Math.abs(to.x - from.x) + spanPad * 2,
|
|
4870
|
+
height: Math.abs(to.y - from.y) + spanPad * 2
|
|
4871
|
+
};
|
|
4872
|
+
const rectsOverlap = (a, b) => a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;
|
|
4592
4873
|
return {
|
|
4593
|
-
parent
|
|
4874
|
+
parent,
|
|
4594
4875
|
source,
|
|
4595
|
-
target
|
|
4876
|
+
target,
|
|
4877
|
+
others: obstacles.filter((o) => {
|
|
4878
|
+
if (o === parent || o === source || o === target) return false;
|
|
4879
|
+
if (pointInObstacle(from, o) && pointInObstacle(to, o)) return false;
|
|
4880
|
+
if (parent) return pointInObstacle({
|
|
4881
|
+
x: o.x + o.width / 2,
|
|
4882
|
+
y: o.y + o.height / 2
|
|
4883
|
+
}, parent);
|
|
4884
|
+
return rectsOverlap(o, span);
|
|
4885
|
+
})
|
|
4596
4886
|
};
|
|
4597
4887
|
}
|
|
4598
4888
|
var MIN_SEGMENT_LENGTH = 20;
|
|
@@ -4702,10 +4992,11 @@ function inferApproachDir(from, to) {
|
|
|
4702
4992
|
}
|
|
4703
4993
|
/** Which side of `target` the attachment point `to` lies on (border hit). */
|
|
4704
4994
|
function sideOfPointOnRect(to, target) {
|
|
4705
|
-
const
|
|
4706
|
-
const
|
|
4707
|
-
const
|
|
4708
|
-
const
|
|
4995
|
+
const tol = 6;
|
|
4996
|
+
const onLeft = Math.abs(to.x - target.x) <= tol;
|
|
4997
|
+
const onRight = Math.abs(to.x - (target.x + target.width)) <= tol;
|
|
4998
|
+
const onTop = Math.abs(to.y - target.y) <= tol;
|
|
4999
|
+
const onBottom = Math.abs(to.y - (target.y + target.height)) <= tol;
|
|
4709
5000
|
if (onTop && !onLeft && !onRight) return "top";
|
|
4710
5001
|
if (onBottom && !onLeft && !onRight) return "bottom";
|
|
4711
5002
|
if (onLeft && !onTop && !onBottom) return "left";
|
|
@@ -5042,7 +5333,7 @@ var PolylinePathStrategy = class {
|
|
|
5042
5333
|
];
|
|
5043
5334
|
}
|
|
5044
5335
|
if (!selfLoop && isSide(fromDir) && isSide(toDir)) {
|
|
5045
|
-
const { parent, source, target } = resolveRoutingRoles(from, to, obstacles);
|
|
5336
|
+
const { parent, source, target, others } = resolveRoutingRoles(from, to, obstacles);
|
|
5046
5337
|
const approachDir = resolveApproachDir(from, to, toDir, target);
|
|
5047
5338
|
if ((parent || source || target) && isSide(approachDir)) return finish("routeOrthogonalAround", routeOrthogonalAround({
|
|
5048
5339
|
from,
|
|
@@ -5052,6 +5343,7 @@ var PolylinePathStrategy = class {
|
|
|
5052
5343
|
parent,
|
|
5053
5344
|
source,
|
|
5054
5345
|
target,
|
|
5346
|
+
others,
|
|
5055
5347
|
margin: 4,
|
|
5056
5348
|
exitDistance: 20
|
|
5057
5349
|
}));
|
|
@@ -5063,7 +5355,8 @@ var PolylinePathStrategy = class {
|
|
|
5063
5355
|
roles: {
|
|
5064
5356
|
parent: parent?.id,
|
|
5065
5357
|
source: source?.id,
|
|
5066
|
-
target: target?.id
|
|
5358
|
+
target: target?.id,
|
|
5359
|
+
others: others.map((o) => o.id).filter(Boolean)
|
|
5067
5360
|
},
|
|
5068
5361
|
obstacleRoles: obstacles.map((o) => `${o.id}:${o.role ?? "?"}`)
|
|
5069
5362
|
});
|
|
@@ -8098,22 +8391,18 @@ var EdgeEndpointUpdater = class {
|
|
|
8098
8391
|
}
|
|
8099
8392
|
}
|
|
8100
8393
|
/**
|
|
8101
|
-
* When nodes are clearly stacked,
|
|
8102
|
-
*
|
|
8394
|
+
* When nodes are clearly stacked/side-by-side, assign facing sides only to
|
|
8395
|
+
* unset endpoints. Intentional ports (including lateral reconnect with
|
|
8396
|
+
* attachToOutline off) and outlineParams are kept — orthogonal routing clears
|
|
8397
|
+
* same-side wraps around the stack.
|
|
8103
8398
|
*
|
|
8104
8399
|
* Uses bounding-box gaps (not center dx/dy): a large horizontal offset must not
|
|
8105
8400
|
* flip bottom→left while the source is still below the target — that reintroduces
|
|
8106
8401
|
* the left-edge contour jog when the user drags the node leftward.
|
|
8107
|
-
*
|
|
8108
|
-
* - Always set facing ports when unset (so lockAnchors does not pick left/right).
|
|
8109
|
-
* - Rewrite target left/right → top/bottom when there is a clear vertical gap.
|
|
8110
|
-
* - Do not rewrite an intentional source side exit (e.g. left exit from a nested
|
|
8111
|
-
* component around its parent).
|
|
8112
8402
|
*/
|
|
8113
8403
|
preferFacingSidePorts(edge, fromNode, toNode) {
|
|
8114
8404
|
if (!edge.lockAnchors) return;
|
|
8115
8405
|
if (!isNodeEdgeEndpoint(edge.from) || !isNodeEdgeEndpoint(edge.to)) return;
|
|
8116
|
-
if (edge.from.outlineParam !== void 0 || edge.to.outlineParam !== void 0) return;
|
|
8117
8406
|
const fromB = fromNode.getBounds();
|
|
8118
8407
|
const toB = toNode.getBounds();
|
|
8119
8408
|
const fromBelow = fromB.y - (toB.y + toB.height);
|
|
@@ -8122,22 +8411,20 @@ var EdgeEndpointUpdater = class {
|
|
|
8122
8411
|
const fromLeft = toB.x - (fromB.x + fromB.width);
|
|
8123
8412
|
const fc = fromNode.getCenter();
|
|
8124
8413
|
const tc = toNode.getCenter();
|
|
8414
|
+
const curFrom = this.endpointSide(edge.from, fromNode);
|
|
8415
|
+
const curTo = this.endpointSide(edge.to, toNode);
|
|
8125
8416
|
if (fromBelow > 0 || fromAbove > 0) {
|
|
8126
8417
|
const fromSide = fromBelow > 0 ? "top" : "bottom";
|
|
8127
8418
|
const toSide = fromBelow > 0 ? "bottom" : "top";
|
|
8128
|
-
const curFrom = this.portSide(edge.from.portId);
|
|
8129
|
-
const curTo = this.portSide(edge.to.portId);
|
|
8130
8419
|
if (!curFrom) this.setEndpointSidePort(edge, "from", fromNode, fromSide, tc);
|
|
8131
|
-
if (!curTo
|
|
8420
|
+
if (!curTo) this.setEndpointSidePort(edge, "to", toNode, toSide, fc);
|
|
8132
8421
|
return;
|
|
8133
8422
|
}
|
|
8134
8423
|
if (fromLeft > 0 || fromRight > 0) {
|
|
8135
8424
|
const fromSide = fromRight > 0 ? "left" : "right";
|
|
8136
8425
|
const toSide = fromRight > 0 ? "right" : "left";
|
|
8137
|
-
const curFrom = this.portSide(edge.from.portId);
|
|
8138
|
-
const curTo = this.portSide(edge.to.portId);
|
|
8139
8426
|
if (!curFrom) this.setEndpointSidePort(edge, "from", fromNode, fromSide, tc);
|
|
8140
|
-
if (!curTo
|
|
8427
|
+
if (!curTo) this.setEndpointSidePort(edge, "to", toNode, toSide, fc);
|
|
8141
8428
|
}
|
|
8142
8429
|
}
|
|
8143
8430
|
portSide(portId) {
|
|
@@ -8145,6 +8432,13 @@ var EdgeEndpointUpdater = class {
|
|
|
8145
8432
|
const side = portId.slice(ANCHOR_PORT_PREFIX.length).split(":")[0];
|
|
8146
8433
|
if (side === "top" || side === "right" || side === "bottom" || side === "left") return side;
|
|
8147
8434
|
}
|
|
8435
|
+
endpointSide(endpoint, node) {
|
|
8436
|
+
if (!isNodeEdgeEndpoint(endpoint)) return void 0;
|
|
8437
|
+
const fromPort = this.portSide(endpoint.portId);
|
|
8438
|
+
if (fromPort) return fromPort;
|
|
8439
|
+
if (endpoint.outlineParam === void 0) return void 0;
|
|
8440
|
+
return getDirectionFromOutlineParam(endpoint.outlineParam, node.getBounds());
|
|
8441
|
+
}
|
|
8148
8442
|
nearestAnchorOnSide(node, side, toward) {
|
|
8149
8443
|
const anchors = node.getAnchors().filter((a) => a.id.startsWith(`${side}:`));
|
|
8150
8444
|
if (anchors.length === 0) return null;
|