@synergenius/flow-weaver 0.33.1 → 0.33.2
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 +1 -0
- package/dist/cli/commands/diagram.js +1 -1
- package/dist/cli/flow-weaver.mjs +299 -413
- package/dist/diagram/geometry.d.ts +7 -2
- package/dist/diagram/geometry.js +15 -56
- package/dist/diagram/html-viewer.js +98 -42
- package/dist/diagram/orthogonal-router.d.ts +16 -51
- package/dist/diagram/orthogonal-router.js +138 -248
- package/dist/diagram/renderer.js +79 -65
- package/dist/diagram/theme.d.ts +2 -0
- package/dist/diagram/theme.js +17 -17
- package/dist/generated-version.d.ts +1 -1
- package/dist/generated-version.js +1 -1
- package/package.json +1 -1
|
@@ -11,11 +11,11 @@ export declare const LAYER_GAP_X = 300;
|
|
|
11
11
|
export declare const LABEL_CLEARANCE = 42;
|
|
12
12
|
export declare const MIN_EDGE_GAP = 112;
|
|
13
13
|
export declare const NODE_GAP_Y = 60;
|
|
14
|
-
export declare const LABEL_HEIGHT =
|
|
14
|
+
export declare const LABEL_HEIGHT = 24;
|
|
15
15
|
export declare const LABEL_GAP = 12;
|
|
16
16
|
export declare const SCOPE_PADDING_X = 140;
|
|
17
17
|
export declare const SCOPE_PADDING_Y = 40;
|
|
18
|
-
export declare const SCOPE_PORT_COLUMN =
|
|
18
|
+
export declare const SCOPE_PORT_COLUMN = 45;
|
|
19
19
|
export declare const SCOPE_INNER_GAP_X = 240;
|
|
20
20
|
export declare const ORTHOGONAL_DISTANCE_THRESHOLD = 300;
|
|
21
21
|
export declare const STUB_DISTANCE_THRESHOLD = 500;
|
|
@@ -26,6 +26,11 @@ export declare function measureText(text: string): number;
|
|
|
26
26
|
export declare function portBadgeWidth(port: DiagramPort): number;
|
|
27
27
|
export declare function computeNodeDimensions(node: DiagramNode): void;
|
|
28
28
|
export declare function computePortPositions(node: DiagramNode): void;
|
|
29
|
+
/**
|
|
30
|
+
* Compute a quad-curve connection from B towards D with consistent tangent.
|
|
31
|
+
* U is a unit vector from B to C with the same slope as B→D.
|
|
32
|
+
*/
|
|
33
|
+
/** Straight-line fallback when orthogonal routing fails (matches platform behaviour). */
|
|
29
34
|
export declare function computeConnectionPath(sx: number, sy: number, tx: number, ty: number): string;
|
|
30
35
|
export declare function buildDiagramGraph(ast: TWorkflowAST, options?: DiagramOptions): DiagramGraph;
|
|
31
36
|
//# sourceMappingURL=geometry.d.ts.map
|
package/dist/diagram/geometry.js
CHANGED
|
@@ -15,12 +15,12 @@ export const LAYER_GAP_X = 300; // target center-to-center; actual gap adapts to
|
|
|
15
15
|
export const LABEL_CLEARANCE = 42; // breathing room between opposing port label badges
|
|
16
16
|
export const MIN_EDGE_GAP = 112; // minimum edge-to-edge gap between node boxes
|
|
17
17
|
export const NODE_GAP_Y = 60;
|
|
18
|
-
export const LABEL_HEIGHT =
|
|
18
|
+
export const LABEL_HEIGHT = 24; // 18px font + breathing room
|
|
19
19
|
export const LABEL_GAP = 12; // matches labelRootStyle bottom: calc(100% + 12px)
|
|
20
20
|
// Scope rendering constants
|
|
21
21
|
export const SCOPE_PADDING_X = 140; // horizontal padding inside scope (between port columns and children)
|
|
22
22
|
export const SCOPE_PADDING_Y = 40; // vertical padding inside scope (top/bottom)
|
|
23
|
-
export const SCOPE_PORT_COLUMN =
|
|
23
|
+
export const SCOPE_PORT_COLUMN = 45; // matches platform scopeContainerStyle minWidth/maxWidth
|
|
24
24
|
export const SCOPE_INNER_GAP_X = 240; // horizontal gap between children inside scope
|
|
25
25
|
// Routing mode threshold — connections longer than this use orthogonal routing
|
|
26
26
|
// (midpoint of original 250–350 hysteresis thresholds)
|
|
@@ -99,49 +99,9 @@ function positionPortList(ports, cx, nodeY, _nodeHeight) {
|
|
|
99
99
|
* Compute a quad-curve connection from B towards D with consistent tangent.
|
|
100
100
|
* U is a unit vector from B to C with the same slope as B→D.
|
|
101
101
|
*/
|
|
102
|
-
|
|
103
|
-
const dn = Math.abs(ay - by);
|
|
104
|
-
const cx = bx + (ux * dn) / Math.abs(uy);
|
|
105
|
-
const cy = ay;
|
|
106
|
-
return [cx, cy];
|
|
107
|
-
}
|
|
102
|
+
/** Straight-line fallback when orthogonal routing fails (matches platform behaviour). */
|
|
108
103
|
export function computeConnectionPath(sx, sy, tx, ty) {
|
|
109
|
-
|
|
110
|
-
const ax = sx + e;
|
|
111
|
-
const ay = sy + e;
|
|
112
|
-
const hx = tx - e;
|
|
113
|
-
const hy = ty - e;
|
|
114
|
-
const ramp = Math.min(20, (hx - ax) / 10);
|
|
115
|
-
const bx = ax + ramp;
|
|
116
|
-
const by = ay + e;
|
|
117
|
-
const gx = hx - ramp;
|
|
118
|
-
const gy = hy - e;
|
|
119
|
-
const curveSizeX = Math.min(60, Math.abs(ax - hx) / 4);
|
|
120
|
-
const curveSizeY = Math.min(60, Math.abs(ay - hy) / 4);
|
|
121
|
-
const curveMag = Math.sqrt(curveSizeX * curveSizeX + curveSizeY * curveSizeY);
|
|
122
|
-
const bgX = gx - bx;
|
|
123
|
-
const bgY = gy - by;
|
|
124
|
-
const bgLen = Math.sqrt(bgX * bgX + bgY * bgY);
|
|
125
|
-
const bgUx = bgX / bgLen;
|
|
126
|
-
const bgUy = bgY / bgLen;
|
|
127
|
-
const dx = bx + bgUx * curveMag;
|
|
128
|
-
const dy = by + (bgUy * curveMag) / 2;
|
|
129
|
-
const ex = gx - bgUx * curveMag;
|
|
130
|
-
const ey = gy - (bgUy * curveMag) / 2;
|
|
131
|
-
const deX = ex - dx;
|
|
132
|
-
const deY = ey - dy;
|
|
133
|
-
const deLen = Math.sqrt(deX * deX + deY * deY);
|
|
134
|
-
const deUx = deX / deLen;
|
|
135
|
-
const deUy = deY / deLen;
|
|
136
|
-
const [cx, cy] = quadCurveControl(bx, by, dx, dy, -deUx, -deUy);
|
|
137
|
-
const [fx, fy] = quadCurveControl(gx, gy, ex, ey, deUx, deUy);
|
|
138
|
-
let path = `M ${cx},${cy} M ${ax},${ay}`;
|
|
139
|
-
path += ` L ${bx},${by}`;
|
|
140
|
-
path += ` Q ${cx},${cy} ${dx},${dy}`;
|
|
141
|
-
path += ` L ${ex},${ey}`;
|
|
142
|
-
path += ` Q ${fx},${fy} ${gx},${gy}`;
|
|
143
|
-
path += ` L ${hx},${hy}`;
|
|
144
|
-
return path;
|
|
104
|
+
return `M ${sx},${sy} L ${tx},${ty}`;
|
|
145
105
|
}
|
|
146
106
|
// ---- Port ordering helpers ----
|
|
147
107
|
/**
|
|
@@ -1028,17 +988,20 @@ export function buildDiagramGraph(ast, options = {}) {
|
|
|
1028
988
|
color: targetColor,
|
|
1029
989
|
dashed,
|
|
1030
990
|
};
|
|
991
|
+
// For static SVG, offset path endpoints past port labels so connections
|
|
992
|
+
// aren't hidden behind opaque label badges. HTML viewer has interactive
|
|
993
|
+
// labels (show/hide on hover), so paths start at port center there.
|
|
994
|
+
const isStaticSvg = options.format !== 'html';
|
|
995
|
+
const pathSx = isStaticSvg ? sx + srcLabelEnd : sx;
|
|
996
|
+
const pathTx = isStaticSvg ? tx - tgtLabelEnd : tx;
|
|
1031
997
|
let path;
|
|
1032
998
|
if (xDistance > STUB_DISTANCE_THRESHOLD) {
|
|
1033
999
|
// Long-distance: static SVG hides the full path, only shows stubs
|
|
1034
1000
|
path = '';
|
|
1035
1001
|
}
|
|
1036
|
-
else if (!useCurve && distance > ORTHOGONAL_DISTANCE_THRESHOLD) {
|
|
1037
|
-
const orthoPath = calculateOrthogonalPathSafe([sx, sy], [tx, ty], nodeBoxes, pc.fromNodeId, pc.toNodeId, { fromPortIndex: pc.fromPortIndex, toPortIndex: pc.toPortIndex }, allocator);
|
|
1038
|
-
path = orthoPath ?? computeConnectionPath(sx, sy, tx, ty);
|
|
1039
|
-
}
|
|
1040
1002
|
else {
|
|
1041
|
-
|
|
1003
|
+
const orthoPath = calculateOrthogonalPathSafe([pathSx, sy], [pathTx, ty], nodeBoxes, pc.fromNodeId, pc.toNodeId, { fromPortIndex: pc.fromPortIndex, toPortIndex: pc.toPortIndex, allocator });
|
|
1004
|
+
path = orthoPath ?? computeConnectionPath(pathSx, sy, pathTx, ty);
|
|
1042
1005
|
}
|
|
1043
1006
|
connections.push({
|
|
1044
1007
|
fromNode: pc.fromNodeId, fromPort: pc.fromPortName,
|
|
@@ -1138,13 +1101,9 @@ export function buildDiagramGraph(ast, options = {}) {
|
|
|
1138
1101
|
const ddy = ty - sy;
|
|
1139
1102
|
const dist = Math.sqrt(ddx * ddx + ddy * ddy);
|
|
1140
1103
|
const useCurve = spForceCurveSet.has(sp);
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
}
|
|
1145
|
-
else {
|
|
1146
|
-
sp.conn.path = computeConnectionPath(sx, sy, tx, ty);
|
|
1147
|
-
}
|
|
1104
|
+
// Always try orthogonal routing first (matches platform style)
|
|
1105
|
+
const orthoPath = calculateOrthogonalPathSafe([sx, sy], [tx, ty], nodeBoxes, sp.fromNodeId, sp.toNodeId, { fromPortIndex: sp.fromPortIndex, toPortIndex: sp.toPortIndex, allocator });
|
|
1106
|
+
sp.conn.path = orthoPath ?? computeConnectionPath(sx, sy, tx, ty);
|
|
1148
1107
|
}
|
|
1149
1108
|
}
|
|
1150
1109
|
// Extend bounds to include connection paths (routes can go outside node area)
|
|
@@ -74,13 +74,12 @@ body.node-active [data-source].dimmed,
|
|
|
74
74
|
body.port-active [data-source].dimmed { opacity: 0.1; }
|
|
75
75
|
body.port-hovered [data-source].dimmed { opacity: 0.25; }
|
|
76
76
|
|
|
77
|
-
/* Port
|
|
78
|
-
|
|
79
|
-
circle[data-port-id]:hover { stroke-width: 3; filter: brightness(1.3); }
|
|
77
|
+
/* Port indicators are interactive — expands to circle on hover (matches platform) */
|
|
78
|
+
[data-port-id] { cursor: pointer; }
|
|
80
79
|
|
|
81
80
|
/* Port-click highlighting */
|
|
82
81
|
[data-source].highlighted { opacity: 1; }
|
|
83
|
-
|
|
82
|
+
[data-port-id].port-selected { filter: drop-shadow(0 0 6px currentColor); }
|
|
84
83
|
|
|
85
84
|
/* Node selection glow */
|
|
86
85
|
@keyframes select-pop {
|
|
@@ -285,7 +284,7 @@ circle[data-port-id].port-selected { filter: drop-shadow(0 0 6px currentColor);
|
|
|
285
284
|
<svg id="canvas" xmlns="http://www.w3.org/2000/svg" viewBox="${viewBox}">
|
|
286
285
|
<defs>
|
|
287
286
|
<pattern id="viewer-dots" width="20" height="20" patternUnits="userSpaceOnUse">
|
|
288
|
-
<circle cx="10" cy="10" r="
|
|
287
|
+
<circle cx="10" cy="10" r="0.75" fill="${dotColor}" opacity="0.4"/>
|
|
289
288
|
</pattern>
|
|
290
289
|
</defs>
|
|
291
290
|
<rect x="-100000" y="-100000" width="200000" height="200000" fill="${bg}" pointer-events="none"/>
|
|
@@ -562,33 +561,9 @@ circle[data-port-id].port-selected { filter: drop-shadow(0 0 6px currentColor);
|
|
|
562
561
|
var MIN_SEG_LEN = 3, JOG_THRESHOLD = 10, ORTHO_THRESHOLD = 300;
|
|
563
562
|
var STUB_THRESHOLD = 500, STUB_LEN = 30;
|
|
564
563
|
|
|
565
|
-
|
|
566
|
-
var dn = Math.abs(ay - by);
|
|
567
|
-
return [bx + (ux * dn) / Math.abs(uy), ay];
|
|
568
|
-
}
|
|
569
|
-
|
|
564
|
+
// Straight-line fallback when orthogonal routing fails (matches platform)
|
|
570
565
|
function computeConnectionPath(sx, sy, tx, ty) {
|
|
571
|
-
|
|
572
|
-
var ax = sx + e, ay = sy + e, hx = tx - e, hy = ty - e;
|
|
573
|
-
var ramp = Math.min(20, (hx - ax) / 10);
|
|
574
|
-
var bx = ax + ramp, by = ay + e, gx = hx - ramp, gy = hy - e;
|
|
575
|
-
var curveSizeX = Math.min(60, Math.abs(ax - hx) / 4);
|
|
576
|
-
var curveSizeY = Math.min(60, Math.abs(ay - hy) / 4);
|
|
577
|
-
var curveMag = Math.sqrt(curveSizeX * curveSizeX + curveSizeY * curveSizeY);
|
|
578
|
-
var bgX = gx - bx, bgY = gy - by;
|
|
579
|
-
var bgLen = Math.sqrt(bgX * bgX + bgY * bgY);
|
|
580
|
-
var bgUx = bgX / bgLen, bgUy = bgY / bgLen;
|
|
581
|
-
var dx = bx + bgUx * curveMag, dy = by + (bgUy * curveMag) / 2;
|
|
582
|
-
var ex = gx - bgUx * curveMag, ey = gy - (bgUy * curveMag) / 2;
|
|
583
|
-
var deX = ex - dx, deY = ey - dy;
|
|
584
|
-
var deLen = Math.sqrt(deX * deX + deY * deY);
|
|
585
|
-
var deUx = deX / deLen, deUy = deY / deLen;
|
|
586
|
-
var c = quadCurveControl(bx, by, dx, dy, -deUx, -deUy);
|
|
587
|
-
var f = quadCurveControl(gx, gy, ex, ey, deUx, deUy);
|
|
588
|
-
return 'M ' + c[0] + ',' + c[1] + ' M ' + ax + ',' + ay +
|
|
589
|
-
' L ' + bx + ',' + by + ' Q ' + c[0] + ',' + c[1] + ' ' + dx + ',' + dy +
|
|
590
|
-
' L ' + ex + ',' + ey + ' Q ' + f[0] + ',' + f[1] + ' ' + gx + ',' + gy +
|
|
591
|
-
' L ' + hx + ',' + hy;
|
|
566
|
+
return 'M ' + sx + ',' + sy + ' L ' + tx + ',' + ty;
|
|
592
567
|
}
|
|
593
568
|
|
|
594
569
|
// ---- Orthogonal router (ported from orthogonal-router.ts) ----
|
|
@@ -871,7 +846,16 @@ circle[data-port-id].port-selected { filter: drop-shadow(0 0 6px currentColor);
|
|
|
871
846
|
var portPositions = {};
|
|
872
847
|
content.querySelectorAll('[data-port-id]').forEach(function(el) {
|
|
873
848
|
var id = el.getAttribute('data-port-id');
|
|
874
|
-
|
|
849
|
+
// Ports may be circle (cx/cy) or rect (x/y/width/height) elements
|
|
850
|
+
var cx, cy;
|
|
851
|
+
if (el.tagName === 'circle') {
|
|
852
|
+
cx = parseFloat(el.getAttribute('cx'));
|
|
853
|
+
cy = parseFloat(el.getAttribute('cy'));
|
|
854
|
+
} else {
|
|
855
|
+
cx = parseFloat(el.getAttribute('x')) + parseFloat(el.getAttribute('width')) / 2;
|
|
856
|
+
cy = parseFloat(el.getAttribute('y')) + parseFloat(el.getAttribute('height')) / 2;
|
|
857
|
+
}
|
|
858
|
+
portPositions[id] = { cx: cx, cy: cy };
|
|
875
859
|
});
|
|
876
860
|
|
|
877
861
|
// Extract node bounding boxes from SVG rect elements
|
|
@@ -1033,14 +1017,9 @@ circle[data-port-id].port-selected { filter: drop-shadow(0 0 6px currentColor);
|
|
|
1033
1017
|
}
|
|
1034
1018
|
} else {
|
|
1035
1019
|
// Path mode: show path, hide stubs
|
|
1036
|
-
|
|
1037
|
-
var path;
|
|
1038
|
-
if (
|
|
1039
|
-
path = calcOrthogonalPath([sx, sy], [tx, ty], boxes, c.srcNode, c.tgtNode, c.srcIdx, c.tgtIdx, alloc);
|
|
1040
|
-
if (!path) path = computeConnectionPath(sx, sy, tx, ty);
|
|
1041
|
-
} else {
|
|
1042
|
-
path = computeConnectionPath(sx, sy, tx, ty);
|
|
1043
|
-
}
|
|
1020
|
+
// Always try orthogonal routing first (matches platform)
|
|
1021
|
+
var path = calcOrthogonalPath([sx, sy], [tx, ty], boxes, c.srcNode, c.tgtNode, c.srcIdx, c.tgtIdx, alloc);
|
|
1022
|
+
if (!path) path = computeConnectionPath(sx, sy, tx, ty);
|
|
1044
1023
|
c.el.setAttribute('d', path);
|
|
1045
1024
|
c.el.removeAttribute('display');
|
|
1046
1025
|
if (c.stubs) {
|
|
@@ -1323,14 +1302,79 @@ circle[data-port-id].port-selected { filter: drop-shadow(0 0 6px currentColor);
|
|
|
1323
1302
|
});
|
|
1324
1303
|
});
|
|
1325
1304
|
|
|
1326
|
-
// Port hover:
|
|
1305
|
+
// Port hover: animated ring→circle expansion (matches platform 0.12s ease-out)
|
|
1306
|
+
var OUTER_W = 6, OUTER_H = 18, OUTER_RX = 4;
|
|
1307
|
+
var HOVER_OUTER = 20, HOVER_OUTER_RX = 10;
|
|
1308
|
+
var HOVER_INNER = 14, HOVER_INNER_RX = 7;
|
|
1309
|
+
var PORT_ANIM_MS = 120; // matches platform transition duration
|
|
1310
|
+
|
|
1311
|
+
// Tween an SVG rect from current attrs to target attrs
|
|
1312
|
+
function tweenRect(el, target, duration, onDone) {
|
|
1313
|
+
if (!el) { if (onDone) onDone(); return; }
|
|
1314
|
+
var start = {
|
|
1315
|
+
x: parseFloat(el.getAttribute('x')),
|
|
1316
|
+
y: parseFloat(el.getAttribute('y')),
|
|
1317
|
+
w: parseFloat(el.getAttribute('width')),
|
|
1318
|
+
h: parseFloat(el.getAttribute('height')),
|
|
1319
|
+
rx: parseFloat(el.getAttribute('rx'))
|
|
1320
|
+
};
|
|
1321
|
+
var t0 = null;
|
|
1322
|
+
var frameId = el.__tweenFrame;
|
|
1323
|
+
if (frameId) cancelAnimationFrame(frameId);
|
|
1324
|
+
function step(ts) {
|
|
1325
|
+
if (!t0) t0 = ts;
|
|
1326
|
+
var p = Math.min((ts - t0) / duration, 1);
|
|
1327
|
+
// ease-out: cubic
|
|
1328
|
+
var e = 1 - Math.pow(1 - p, 3);
|
|
1329
|
+
el.setAttribute('x', String(start.x + (target.x - start.x) * e));
|
|
1330
|
+
el.setAttribute('y', String(start.y + (target.y - start.y) * e));
|
|
1331
|
+
el.setAttribute('width', String(start.w + (target.w - start.w) * e));
|
|
1332
|
+
el.setAttribute('height', String(start.h + (target.h - start.h) * e));
|
|
1333
|
+
el.setAttribute('rx', String(start.rx + (target.rx - start.rx) * e));
|
|
1334
|
+
if (p < 1) {
|
|
1335
|
+
el.__tweenFrame = requestAnimationFrame(step);
|
|
1336
|
+
} else {
|
|
1337
|
+
el.__tweenFrame = null;
|
|
1338
|
+
if (onDone) onDone();
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
el.__tweenFrame = requestAnimationFrame(step);
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1327
1344
|
content.querySelectorAll('[data-port-id]').forEach(function(portEl) {
|
|
1328
1345
|
var portId = portEl.getAttribute('data-port-id');
|
|
1329
1346
|
var nodeId = portId.split('.')[0];
|
|
1330
1347
|
var peers = (portConnections[portId] || []).concat(portId);
|
|
1331
1348
|
|
|
1349
|
+
var origX = parseFloat(portEl.getAttribute('x'));
|
|
1350
|
+
var origY = parseFloat(portEl.getAttribute('y'));
|
|
1351
|
+
var innerBar = portEl.nextElementSibling;
|
|
1352
|
+
var hasInner = innerBar && innerBar.getAttribute('pointer-events') === 'none';
|
|
1353
|
+
var innerOrig = hasInner ? {
|
|
1354
|
+
x: parseFloat(innerBar.getAttribute('x')),
|
|
1355
|
+
y: parseFloat(innerBar.getAttribute('y')),
|
|
1356
|
+
w: parseFloat(innerBar.getAttribute('width')),
|
|
1357
|
+
h: parseFloat(innerBar.getAttribute('height')),
|
|
1358
|
+
rx: parseFloat(innerBar.getAttribute('rx'))
|
|
1359
|
+
} : null;
|
|
1360
|
+
|
|
1361
|
+
var cx = origX + OUTER_W / 2;
|
|
1362
|
+
var cy = origY + OUTER_H / 2;
|
|
1363
|
+
|
|
1332
1364
|
portEl.addEventListener('mouseenter', function() {
|
|
1333
1365
|
hoveredPort = portId;
|
|
1366
|
+
if (portEl.tagName === 'rect') {
|
|
1367
|
+
tweenRect(portEl, {
|
|
1368
|
+
x: cx - HOVER_OUTER / 2, y: cy - HOVER_OUTER / 2,
|
|
1369
|
+
w: HOVER_OUTER, h: HOVER_OUTER, rx: HOVER_OUTER_RX
|
|
1370
|
+
}, PORT_ANIM_MS);
|
|
1371
|
+
if (hasInner) {
|
|
1372
|
+
tweenRect(innerBar, {
|
|
1373
|
+
x: cx - HOVER_INNER / 2, y: cy - HOVER_INNER / 2,
|
|
1374
|
+
w: HOVER_INNER, h: HOVER_INNER, rx: HOVER_INNER_RX
|
|
1375
|
+
}, PORT_ANIM_MS);
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1334
1378
|
batchLabelChanges(function() {
|
|
1335
1379
|
hideLabelsFor(nodeId);
|
|
1336
1380
|
peers.forEach(showLabel);
|
|
@@ -1347,6 +1391,18 @@ circle[data-port-id].port-selected { filter: drop-shadow(0 0 6px currentColor);
|
|
|
1347
1391
|
});
|
|
1348
1392
|
portEl.addEventListener('mouseleave', function() {
|
|
1349
1393
|
hoveredPort = null;
|
|
1394
|
+
if (portEl.tagName === 'rect') {
|
|
1395
|
+
tweenRect(portEl, {
|
|
1396
|
+
x: origX, y: origY,
|
|
1397
|
+
w: OUTER_W, h: OUTER_H, rx: OUTER_RX
|
|
1398
|
+
}, PORT_ANIM_MS);
|
|
1399
|
+
if (hasInner) {
|
|
1400
|
+
tweenRect(innerBar, {
|
|
1401
|
+
x: innerOrig.x, y: innerOrig.y,
|
|
1402
|
+
w: innerOrig.w, h: innerOrig.h, rx: innerOrig.rx
|
|
1403
|
+
}, PORT_ANIM_MS);
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1350
1406
|
// Defer so if entering another port, its mouseenter sets hoveredPort first
|
|
1351
1407
|
var myPeers = peers, myNodeId = nodeId;
|
|
1352
1408
|
Promise.resolve().then(function() {
|
|
@@ -1391,7 +1447,7 @@ circle[data-port-id].port-selected { filter: drop-shadow(0 0 6px currentColor);
|
|
|
1391
1447
|
if (!selectedPortId) return;
|
|
1392
1448
|
selectedPortId = null;
|
|
1393
1449
|
document.body.classList.remove('port-active');
|
|
1394
|
-
content.querySelectorAll('
|
|
1450
|
+
content.querySelectorAll('.port-selected').forEach(function(c) {
|
|
1395
1451
|
c.classList.remove('port-selected');
|
|
1396
1452
|
});
|
|
1397
1453
|
content.querySelectorAll('[data-source].dimmed, [data-source].highlighted').forEach(function(p) {
|
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Orthogonal connection router for SVG diagram rendering.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* Features:
|
|
8
|
-
* - L-shape and S-shape routing with rounded corners
|
|
9
|
-
* - Node collision avoidance via inflated bounding boxes
|
|
10
|
-
* - Track allocator prevents parallel connections from overlapping
|
|
11
|
-
* - Crossing minimization (evaluates up to 11 candidates per allocation)
|
|
12
|
-
* - Per-port-index stub spacing
|
|
13
|
-
* - Waypoint simplification (collinear removal, jog collapse)
|
|
14
|
-
* - Backward/self connection escape routing
|
|
4
|
+
* 1-1 port of the platform's orthogonalRouter.ts.
|
|
5
|
+
* Only change: removed gl-matrix dependency — uses plain [number, number] tuples.
|
|
15
6
|
*/
|
|
16
7
|
export interface NodeBox {
|
|
17
8
|
id: string;
|
|
@@ -28,52 +19,26 @@ export interface OrthogonalRouteOptions {
|
|
|
28
19
|
maxStubLength?: number;
|
|
29
20
|
fromPortIndex?: number;
|
|
30
21
|
toPortIndex?: number;
|
|
22
|
+
/** When true, treat same-node connections as forward (not self-loop). Used for scoped port connections. */
|
|
23
|
+
scopedInternal?: boolean;
|
|
24
|
+
/** Track allocator for preventing connections from overlapping on the same horizontal track. */
|
|
25
|
+
allocator?: TrackAllocator;
|
|
31
26
|
}
|
|
32
27
|
type Vec2 = [number, number];
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Prevents connections from running on the same horizontal track.
|
|
30
|
+
* Create one instance per render batch and pass it to all routing calls.
|
|
31
|
+
* Tracks are snapped to TRACK_SPACING grid; only connections whose X corridors
|
|
32
|
+
* overlap can conflict.
|
|
33
|
+
*/
|
|
39
34
|
export declare class TrackAllocator {
|
|
40
35
|
private claims;
|
|
41
|
-
|
|
42
|
-
/** Check if a Y value is too close to any claimed segment whose X range overlaps. */
|
|
43
|
-
private isOccupied;
|
|
44
|
-
/** Check if an X value is too close to any claimed vertical segment whose Y range overlaps. */
|
|
45
|
-
private isOccupiedVertical;
|
|
46
|
-
/** Check if a horizontal segment at Y passes through any inflated node box. */
|
|
47
|
-
private isBlockedByNode;
|
|
48
|
-
/** Check if a vertical segment at X passes through any inflated node box. */
|
|
49
|
-
private isBlockedByNodeVertical;
|
|
50
|
-
/** Count claimed vertical segments that a horizontal segment at Y would cross. */
|
|
51
|
-
countHorizontalCrossings(xMin: number, xMax: number, y: number): number;
|
|
52
|
-
/** Count claimed horizontal segments that a vertical segment at X would cross. */
|
|
53
|
-
countVerticalCrossings(yMin: number, yMax: number, x: number): number;
|
|
54
|
-
/**
|
|
55
|
-
* Find the nearest free Y to candidateY in the given X range, preferring fewer crossings.
|
|
56
|
-
* When nodeBoxes is provided, candidates inside inflated node boxes are rejected (hard constraint).
|
|
57
|
-
*/
|
|
58
|
-
findFreeY(xMin: number, xMax: number, candidateY: number, nodeBoxes?: InflatedBox[]): number;
|
|
59
|
-
/**
|
|
60
|
-
* Find the nearest free X to candidateX in the given Y range, preferring fewer crossings.
|
|
61
|
-
* When nodeBoxes is provided, candidates inside inflated node boxes are rejected (hard constraint).
|
|
62
|
-
*/
|
|
63
|
-
findFreeX(yMin: number, yMax: number, candidateX: number, nodeBoxes?: InflatedBox[]): number;
|
|
64
|
-
/** Claim a horizontal segment so later connections avoid it. */
|
|
65
|
-
claim(xMin: number, xMax: number, y: number): void;
|
|
66
|
-
/** Claim a vertical segment so later connections avoid it. */
|
|
67
|
-
claimVertical(yMin: number, yMax: number, x: number): void;
|
|
36
|
+
claim(xMin: number, xMax: number, candidateY: number): number;
|
|
68
37
|
}
|
|
38
|
+
export declare function calculateOrthogonalPath(from: Vec2, to: Vec2, nodeBoxes: NodeBox[], sourceNodeId: string, targetNodeId: string, options?: OrthogonalRouteOptions): string | null;
|
|
69
39
|
/**
|
|
70
|
-
*
|
|
71
|
-
* Returns null if the path should fall back to bezier (e.g. nearly aligned ports).
|
|
72
|
-
*/
|
|
73
|
-
export declare function calculateOrthogonalPath(from: Vec2, to: Vec2, nodeBoxes: NodeBox[], sourceNodeId: string, targetNodeId: string, options?: OrthogonalRouteOptions, allocator?: TrackAllocator): string | null;
|
|
74
|
-
/**
|
|
75
|
-
* Safe wrapper — returns null if routing fails, caller falls back to bezier.
|
|
40
|
+
* Safe wrapper: returns null if routing fails, caller falls back to straight line.
|
|
76
41
|
*/
|
|
77
|
-
export declare function calculateOrthogonalPathSafe(from: Vec2, to: Vec2, nodeBoxes: NodeBox[], sourceNodeId: string, targetNodeId: string, options?: OrthogonalRouteOptions
|
|
42
|
+
export declare function calculateOrthogonalPathSafe(from: Vec2, to: Vec2, nodeBoxes: NodeBox[], sourceNodeId: string, targetNodeId: string, options?: OrthogonalRouteOptions): string | null;
|
|
78
43
|
export {};
|
|
79
44
|
//# sourceMappingURL=orthogonal-router.d.ts.map
|