@oh-just-another/scene 0.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/LICENSE +21 -0
  3. package/README.md +72 -0
  4. package/dist/.tsbuildinfo +1 -0
  5. package/dist/a11y.d.ts +23 -0
  6. package/dist/a11y.d.ts.map +1 -0
  7. package/dist/a11y.js +37 -0
  8. package/dist/a11y.js.map +1 -0
  9. package/dist/anchors.d.ts +120 -0
  10. package/dist/anchors.d.ts.map +1 -0
  11. package/dist/anchors.js +322 -0
  12. package/dist/anchors.js.map +1 -0
  13. package/dist/annotation-geometry.d.ts +11 -0
  14. package/dist/annotation-geometry.d.ts.map +1 -0
  15. package/dist/annotation-geometry.js +19 -0
  16. package/dist/annotation-geometry.js.map +1 -0
  17. package/dist/annotation.d.ts +32 -0
  18. package/dist/annotation.d.ts.map +1 -0
  19. package/dist/annotation.js +2 -0
  20. package/dist/annotation.js.map +1 -0
  21. package/dist/constants.d.ts +186 -0
  22. package/dist/constants.d.ts.map +1 -0
  23. package/dist/constants.js +186 -0
  24. package/dist/constants.js.map +1 -0
  25. package/dist/container.d.ts +112 -0
  26. package/dist/container.d.ts.map +1 -0
  27. package/dist/container.js +227 -0
  28. package/dist/container.js.map +1 -0
  29. package/dist/diff.d.ts +22 -0
  30. package/dist/diff.d.ts.map +1 -0
  31. package/dist/diff.js +18 -0
  32. package/dist/diff.js.map +1 -0
  33. package/dist/edge-curve.d.ts +37 -0
  34. package/dist/edge-curve.d.ts.map +1 -0
  35. package/dist/edge-curve.js +72 -0
  36. package/dist/edge-curve.js.map +1 -0
  37. package/dist/edge-geometry.d.ts +96 -0
  38. package/dist/edge-geometry.d.ts.map +1 -0
  39. package/dist/edge-geometry.js +467 -0
  40. package/dist/edge-geometry.js.map +1 -0
  41. package/dist/edge.d.ts +206 -0
  42. package/dist/edge.d.ts.map +1 -0
  43. package/dist/edge.js +2 -0
  44. package/dist/edge.js.map +1 -0
  45. package/dist/elbow-link.d.ts +23 -0
  46. package/dist/elbow-link.d.ts.map +1 -0
  47. package/dist/elbow-link.js +419 -0
  48. package/dist/elbow-link.js.map +1 -0
  49. package/dist/elbow-router.d.ts +20 -0
  50. package/dist/elbow-router.d.ts.map +1 -0
  51. package/dist/elbow-router.js +218 -0
  52. package/dist/elbow-router.js.map +1 -0
  53. package/dist/file.d.ts +41 -0
  54. package/dist/file.d.ts.map +1 -0
  55. package/dist/file.js +13 -0
  56. package/dist/file.js.map +1 -0
  57. package/dist/heading.d.ts +42 -0
  58. package/dist/heading.d.ts.map +1 -0
  59. package/dist/heading.js +77 -0
  60. package/dist/heading.js.map +1 -0
  61. package/dist/index.d.ts +47 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +40 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/layer.d.ts +18 -0
  66. package/dist/layer.d.ts.map +1 -0
  67. package/dist/layer.js +2 -0
  68. package/dist/layer.js.map +1 -0
  69. package/dist/layout-registry.d.ts +35 -0
  70. package/dist/layout-registry.d.ts.map +1 -0
  71. package/dist/layout-registry.js +13 -0
  72. package/dist/layout-registry.js.map +1 -0
  73. package/dist/layout.d.ts +128 -0
  74. package/dist/layout.d.ts.map +1 -0
  75. package/dist/layout.js +453 -0
  76. package/dist/layout.js.map +1 -0
  77. package/dist/operations.d.ts +32 -0
  78. package/dist/operations.d.ts.map +1 -0
  79. package/dist/operations.js +105 -0
  80. package/dist/operations.js.map +1 -0
  81. package/dist/outline.d.ts +36 -0
  82. package/dist/outline.d.ts.map +1 -0
  83. package/dist/outline.js +129 -0
  84. package/dist/outline.js.map +1 -0
  85. package/dist/patch.d.ts +57 -0
  86. package/dist/patch.d.ts.map +1 -0
  87. package/dist/patch.js +47 -0
  88. package/dist/patch.js.map +1 -0
  89. package/dist/queries.d.ts +115 -0
  90. package/dist/queries.d.ts.map +1 -0
  91. package/dist/queries.js +403 -0
  92. package/dist/queries.js.map +1 -0
  93. package/dist/render-bounds.d.ts +36 -0
  94. package/dist/render-bounds.d.ts.map +1 -0
  95. package/dist/render-bounds.js +36 -0
  96. package/dist/render-bounds.js.map +1 -0
  97. package/dist/scene.d.ts +80 -0
  98. package/dist/scene.d.ts.map +1 -0
  99. package/dist/scene.js +129 -0
  100. package/dist/scene.js.map +1 -0
  101. package/dist/shape.d.ts +306 -0
  102. package/dist/shape.d.ts.map +1 -0
  103. package/dist/shape.js +192 -0
  104. package/dist/shape.js.map +1 -0
  105. package/dist/snap.d.ts +88 -0
  106. package/dist/snap.d.ts.map +1 -0
  107. package/dist/snap.js +128 -0
  108. package/dist/snap.js.map +1 -0
  109. package/dist/spatial.d.ts +38 -0
  110. package/dist/spatial.d.ts.map +1 -0
  111. package/dist/spatial.js +95 -0
  112. package/dist/spatial.js.map +1 -0
  113. package/dist/style.d.ts +87 -0
  114. package/dist/style.d.ts.map +1 -0
  115. package/dist/style.js +43 -0
  116. package/dist/style.js.map +1 -0
  117. package/dist/text-measure.d.ts +29 -0
  118. package/dist/text-measure.d.ts.map +1 -0
  119. package/dist/text-measure.js +8 -0
  120. package/dist/text-measure.js.map +1 -0
  121. package/dist/three-way-merge.d.ts +59 -0
  122. package/dist/three-way-merge.d.ts.map +1 -0
  123. package/dist/three-way-merge.js +108 -0
  124. package/dist/three-way-merge.js.map +1 -0
  125. package/dist/viewport.d.ts +78 -0
  126. package/dist/viewport.d.ts.map +1 -0
  127. package/dist/viewport.js +62 -0
  128. package/dist/viewport.js.map +1 -0
  129. package/package.json +54 -0
@@ -0,0 +1,419 @@
1
+ import { ELBOW_AVOID_MAX_OBSTACLES, ELBOW_CROSS_SAMPLE_STEP, ELBOW_OBSTACLE_CLEARANCE, ELBOW_OBSTACLE_MARGIN, ELBOW_TERMINAL_BUFFER, ELBOW_WRAP_HYSTERESIS, } from "./constants.js";
2
+ import { getLinkEndpointWorld } from "./edge-geometry.js";
3
+ import { elbowRoute } from "./elbow-router.js";
4
+ import { HEADING_DOWN, HEADING_LEFT, HEADING_RIGHT, HEADING_UP, headingForEdgePoint, headingForPoint, headingForPointFromElement, headingIsHorizontal, } from "./heading.js";
5
+ import { getElement } from "./queries.js";
6
+ import { getElementWorldBounds } from "./shape.js";
7
+ const req = (v) => {
8
+ if (v === undefined)
9
+ throw new Error("packages/scene: index out of range");
10
+ return v;
11
+ };
12
+ /**
13
+ * Compute the orthogonal (elbow) route for a link: the corner points
14
+ * between `from` and `to` (exclusive). This is what the editor stores on
15
+ * `Link.routedPoints` and `getLinkPath` renders as `[from, ...points, to]`.
16
+ *
17
+ * standard model: each end exits perpendicular to its shape (a "dongle"
18
+ * pushed out by `ELBOW_TERMINAL_BUFFER` along the end's heading), the A* router
19
+ * routes between the dongles around the two bound shapes, and the whole
20
+ * chain is collapsed to corner points. The result is always axis-aligned.
21
+ */
22
+ export const routeElbowLink = (scene, edge) => {
23
+ const fromP = getLinkEndpointWorld(scene, edge.from);
24
+ const toP = getLinkEndpointWorld(scene, edge.to);
25
+ if (!fromP || !toP)
26
+ return [];
27
+ const from = edge.from.kind === "floating" ? (getLinkEndpointWorld(scene, edge.from, toP) ?? fromP) : fromP;
28
+ const to = edge.to.kind === "floating" ? (getLinkEndpointWorld(scene, edge.to, fromP) ?? toP) : toP;
29
+ if (from.x === to.x && from.y === to.y)
30
+ return [];
31
+ const a = endInfo(scene, edge.from, from, to);
32
+ const b = endInfo(scene, edge.to, to, from);
33
+ // The route is structured as a fixed outward BUFFER stub at each end plus a
34
+ // movable middle between the two stub joints (bufA / bufB). `routeMiddle`
35
+ // returns [bufA, ...corners..., bufB]; the buffers themselves are the
36
+ // from→bufA / bufB→to segments that `getLinkPath` adds when it wraps
37
+ // routedPoints with from/to — they're never collapsed, so an aligned elbow
38
+ // still has 3 segments (buffer + movable + buffer) and reads as one line.
39
+ // "Avoid obstacles" mode: route around EVERY scene shape, not just the two
40
+ // bound ends. Collect the rest of the scene as obstacles and let A* find a
41
+ // clear orthogonal path (the analytic mid-S / C-wrap only know the two bound
42
+ // boxes, so they're skipped in this mode).
43
+ const avoidList = edge.avoidObstacles === true ? collectAvoidObstacles(scene, edge) : undefined;
44
+ // Perf gate: above the cap, skip whole-scene avoidance (too many obstacle
45
+ // corners to A* every frame) and fall back to the cheap two-box elbow.
46
+ const avoid = avoidList && avoidList.length <= ELBOW_AVOID_MAX_OBSTACLES ? avoidList : undefined;
47
+ // Pass the previously routed path as a side-hint so the C-wrap stays on
48
+ // the same side under a small drag (hysteresis — see wrapRoute). This is
49
+ // the only history the elbow router consults; the A* core stays pure.
50
+ let middle = routeMiddle(from, to, a, b, edge.routedPoints, avoid);
51
+ middle = applyFixedSegments(middle, edge.fixedSegments);
52
+ return middle;
53
+ };
54
+ /**
55
+ * World bboxes of every scene element EXCEPT the link's own two bound shapes
56
+ * (those are handled by the headings/dongles). Used by "avoid obstacles" mode.
57
+ */
58
+ const collectAvoidObstacles = (scene, edge) => {
59
+ const skip = new Set();
60
+ for (const ep of [edge.from, edge.to])
61
+ if (ep.kind !== "point")
62
+ skip.add(ep.elementId);
63
+ const out = [];
64
+ for (const el of scene.elements.values()) {
65
+ if (skip.has(el.id))
66
+ continue;
67
+ out.push(getElementWorldBounds(el));
68
+ }
69
+ return out;
70
+ };
71
+ /**
72
+ * Live elbow path for a connector being drawn (preview): full
73
+ * `[from, ...corners, to]`. `fromElementId` / `toElementId` give each end
74
+ * its exit heading + obstacle (null for a free end — heading then points at
75
+ * the other end). Mirrors {@link routeElbowLink} so the dashed preview
76
+ * matches the committed elbow.
77
+ */
78
+ export const routeElbowPreview = (scene, fromElementId, from, toElementId, to) => {
79
+ if (from.x === to.x && from.y === to.y)
80
+ return [from, to];
81
+ const a = pointEndInfo(scene, fromElementId, from, to);
82
+ const b = pointEndInfo(scene, toElementId, to, from);
83
+ return [from, ...routeMiddle(from, to, a, b), to];
84
+ };
85
+ /**
86
+ * Movable middle of an elbow: the path between the two outward BUFFER stub
87
+ * joints (`bufA` = from pushed out along its heading, `bufB` = to pushed out
88
+ * along its). Returns `[bufA, ...corners..., bufB]`. The from→bufA / bufB→to
89
+ * stubs are added by the caller (getLinkPath / preview) and are the fixed,
90
+ * non-movable terminal segments. On very short links the buffer is clamped so
91
+ * the two stubs don't overrun each other.
92
+ */
93
+ const routeMiddle = (from, to, a, b, prev, avoid) => {
94
+ // FIXED buffer: every terminal stub is exactly ELBOW_TERMINAL_BUFFER — one
95
+ // constant length, never shrunk. bufA / bufB are the stub joints.
96
+ const buf = ELBOW_TERMINAL_BUFFER;
97
+ const bufA = { x: from.x + a.heading.x * buf, y: from.y + a.heading.y * buf };
98
+ const bufB = { x: to.x + b.heading.x * buf, y: to.y + b.heading.y * buf };
99
+ const obstacles = [];
100
+ if (a.obstacle)
101
+ obstacles.push(a.obstacle);
102
+ if (b.obstacle)
103
+ obstacles.push(b.obstacle);
104
+ // Avoid-obstacles mode: route around the whole scene via A* (the analytic
105
+ // mid-S / C-wrap only reason about the two bound boxes). Include the bound
106
+ // boxes too so the route still keeps clear of its own ends.
107
+ if (avoid) {
108
+ const all = [...obstacles, ...avoid];
109
+ const routed = elbowRoute(bufA, bufB, all, { startHeading: a.heading, endHeading: b.heading });
110
+ const mid = routed && routed.length >= 2 ? routed : [bufA, fallbackCorner(bufA, bufB, a.heading), bufB];
111
+ return collapseColinear(mid);
112
+ }
113
+ // Collinear-opposite stubs (top↔bottom, left↔right): build a clean,
114
+ // deterministic centred S/Z whose crossover sits in the MIDDLE between the
115
+ // two stub joints — same rule whether the shapes overlap or are separated, so
116
+ // dragging never makes the crossover jump (A* would snap it to a grid line).
117
+ // Fall back to A* only if that path would clip a bound shape.
118
+ const midS = midS_(from, to, a, b, bufA, bufB);
119
+ if (midS && !pathCrossesObstacle(midS, obstacles))
120
+ return collapseColinear(midS);
121
+ // The centred crossover would clip a shape (collinear-opposite ends whose
122
+ // boxes overlap on the cross axis — e.g. lower.left → upper.right when the
123
+ // boxes overlap vertically). Wrap deterministically around the union of the
124
+ // obstacles on ONE consistently-chosen side instead of letting A* flip-flop
125
+ // between routing over the top and under the bottom as the shape is dragged.
126
+ const wrap = wrapRoute(from, to, a, b, bufA, bufB, obstacles, prev);
127
+ if (wrap && !pathCrossesObstacle(wrap, obstacles))
128
+ return collapseColinear(wrap);
129
+ const routed = elbowRoute(bufA, bufB, obstacles, {
130
+ startHeading: a.heading,
131
+ endHeading: b.heading,
132
+ });
133
+ const mid = routed && routed.length >= 2 ? routed : [bufA, fallbackCorner(bufA, bufB, a.heading), bufB];
134
+ // Collapse only the interior corners — collapseColinear keeps the bufA / bufB
135
+ // endpoints, so the stub joints survive even when colinear with the middle.
136
+ return collapseColinear(mid);
137
+ };
138
+ /**
139
+ * For two COLLINEAR-OPPOSITE stubs (top↔bottom or left↔right — the two exit
140
+ * headings point along the same axis in opposite directions), return a 4-point
141
+ * S/Z `[bufA, c1, c2, bufB]` whose crossover sits at the midpoint between the
142
+ * joints. One deterministic rule covers the whole drag:
143
+ * - OVERLAP (the joints have crossed along the exit axis, e.g. tight gap or
144
+ * the boxes overlap vertically): the crossover is the SHORT cross-axis step,
145
+ * placed at the cross-axis midpoint (mx / my) — a smooth mid-bend.
146
+ * - SEPARATED (the joints haven't crossed): the crossover is the long
147
+ * cross-run, placed at the exit-axis midpoint between the two buffers.
148
+ * Both forms degenerate to the same straight segment at the boundary (gap =
149
+ * 2×buffer), so the route is continuous as the shape is dragged — no jump to a
150
+ * grid line the way A* does. Returns null for non-collinear pairs (L-shapes,
151
+ * same-side), where A* is the right tool. `pathCrossesObstacle` rejects the
152
+ * result if the centred path would clip a bound shape (then A* takes over).
153
+ */
154
+ const midS_ = (from, to, a, b, bufA, bufB) => {
155
+ // Vertical pair (e.g. bottom→top): stubs run along Y.
156
+ if (a.heading.y !== 0 && b.heading.y === -a.heading.y) {
157
+ const overlap = Math.sign(bufA.y - bufB.y) === Math.sign(a.heading.y);
158
+ if (overlap) {
159
+ // Joints crossed: short vertical step at the horizontal midpoint.
160
+ const mx = (from.x + to.x) / 2;
161
+ return [bufA, { x: mx, y: bufA.y }, { x: mx, y: bufB.y }, bufB];
162
+ }
163
+ // Separated: horizontal cross-run at the vertical midpoint between buffers.
164
+ const my = (bufA.y + bufB.y) / 2;
165
+ return [bufA, { x: bufA.x, y: my }, { x: bufB.x, y: my }, bufB];
166
+ }
167
+ // Horizontal pair (e.g. right→left): stubs run along X.
168
+ if (a.heading.x !== 0 && b.heading.x === -a.heading.x) {
169
+ const overlap = Math.sign(bufA.x - bufB.x) === Math.sign(a.heading.x);
170
+ if (overlap) {
171
+ // Joints crossed: short horizontal step at the vertical midpoint.
172
+ const my = (from.y + to.y) / 2;
173
+ return [bufA, { x: bufA.x, y: my }, { x: bufB.x, y: my }, bufB];
174
+ }
175
+ // Separated: vertical cross-run at the horizontal midpoint between buffers.
176
+ const mx = (bufA.x + bufB.x) / 2;
177
+ return [bufA, { x: mx, y: bufA.y }, { x: mx, y: bufB.y }, bufB];
178
+ }
179
+ return null;
180
+ };
181
+ /**
182
+ * Pick one of two outside-the-union crossover coordinates (`lo` = top/left,
183
+ * `hi` = bottom/right) with HYSTERESIS so a small drag doesn't flip the side.
184
+ *
185
+ * `mid` is the endpoints' midpoint along the cross axis; `centre` the union
186
+ * centre. The natural (shortest) side is whichever the midpoint leans toward.
187
+ * But near the centre — exactly where a diagonal-overlap configuration rests —
188
+ * a bare threshold flips top↔bottom every frame under a drag (jitter). So
189
+ * within `ELBOW_WRAP_HYSTERESIS` of the centre we KEEP the previous side
190
+ * (`prevLo`: was the last route on the `lo` side?); the side only switches once
191
+ * the midpoint moves past the centre by the band. This is the only history the
192
+ * elbow router consults — the A* core stays pure.
193
+ */
194
+ const pickWrapSide = (mid, centre, lo, hi, prevLo) => {
195
+ const naturalLo = mid <= centre;
196
+ if (prevLo !== null && Math.abs(mid - centre) < ELBOW_WRAP_HYSTERESIS) {
197
+ return prevLo ? lo : hi;
198
+ }
199
+ return naturalLo ? lo : hi;
200
+ };
201
+ /**
202
+ * Deterministic C-wrap for a collinear-opposite pair whose centred crossover
203
+ * (the {@link midS_} thread) would clip a shape — i.e. the two boxes overlap on
204
+ * the cross axis, so there's no gap to thread. Route the crossover run just
205
+ * OUTSIDE the union of the obstacles, on one side (top/bottom for a horizontal
206
+ * pair, left/right for a vertical pair). The side is chosen by
207
+ * {@link pickWrapSide} with hysteresis off the previous route (`prev`) so a
208
+ * small drag near the union centre no longer flips the route ("jitter").
209
+ */
210
+ const wrapRoute = (from, to, a, b, bufA, bufB, obstacles, prev) => {
211
+ if (obstacles.length === 0)
212
+ return null;
213
+ let minX = Infinity;
214
+ let minY = Infinity;
215
+ let maxX = -Infinity;
216
+ let maxY = -Infinity;
217
+ for (const o of obstacles) {
218
+ minX = Math.min(minX, o.x);
219
+ minY = Math.min(minY, o.y);
220
+ maxX = Math.max(maxX, o.x + o.width);
221
+ maxY = Math.max(maxY, o.y + o.height);
222
+ }
223
+ const m = ELBOW_OBSTACLE_MARGIN;
224
+ minX -= m;
225
+ minY -= m;
226
+ maxX += m;
227
+ maxY += m;
228
+ // Horizontal pair (ends exit ±x): the crossover is a horizontal run; place it
229
+ // just above or below the union.
230
+ if (a.heading.y === 0 && b.heading.y === 0 && a.heading.x !== 0 && b.heading.x === -a.heading.x) {
231
+ const my = (from.y + to.y) / 2;
232
+ const yPick = pickWrapSide(my, (minY + maxY) / 2, minY, maxY, prevWrapLo(prev, "y"));
233
+ return [bufA, { x: bufA.x, y: yPick }, { x: bufB.x, y: yPick }, bufB];
234
+ }
235
+ // Vertical pair (ends exit ±y): the crossover is a vertical run; place it just
236
+ // left or right of the union.
237
+ if (a.heading.x === 0 && b.heading.x === 0 && a.heading.y !== 0 && b.heading.y === -a.heading.y) {
238
+ const mx = (from.x + to.x) / 2;
239
+ const xPick = pickWrapSide(mx, (minX + maxX) / 2, minX, maxX, prevWrapLo(prev, "x"));
240
+ return [bufA, { x: xPick, y: bufA.y }, { x: xPick, y: bufB.y }, bufB];
241
+ }
242
+ return null;
243
+ };
244
+ /**
245
+ * Which side did the previous wrap take? The crossover run sits OUTSIDE the
246
+ * union, beyond the two endpoints (`prev[0]` / `prev[last]` = the stub joints).
247
+ * So a `lo`-side wrap dips the path's minimum BELOW the endpoints' band, and a
248
+ * `hi`-side wrap pushes the maximum ABOVE it. Compare those overshoots. Returns
249
+ * `null` when there's no usable previous path (first route, degenerate, or no
250
+ * overshoot either way) — the caller then uses the natural side.
251
+ */
252
+ const prevWrapLo = (prev, axis) => {
253
+ if (!prev || prev.length < 3)
254
+ return null;
255
+ const at = (p) => (axis === "y" ? p.y : p.x);
256
+ const first = at(req(prev[0]));
257
+ const last = at(req(prev[prev.length - 1]));
258
+ const endLo = Math.min(first, last);
259
+ const endHi = Math.max(first, last);
260
+ let lo = Infinity;
261
+ let hi = -Infinity;
262
+ for (const p of prev) {
263
+ const v = at(p);
264
+ if (v < lo)
265
+ lo = v;
266
+ if (v > hi)
267
+ hi = v;
268
+ }
269
+ const overLo = endLo - lo; // how far the path dips below the endpoints
270
+ const overHi = hi - endHi; // how far it rises above them
271
+ if (overLo <= 0 && overHi <= 0)
272
+ return null; // no crossover overshoot → no hint
273
+ return overLo >= overHi;
274
+ };
275
+ /** True if any axis-aligned segment of `path` passes through an obstacle's
276
+ * interior (sampled; a margin keeps the line off the very edge). */
277
+ const pathCrossesObstacle = (path, obstacles) => {
278
+ const m = ELBOW_OBSTACLE_CLEARANCE; // inset so edge-touching isn't a "cross"
279
+ for (let i = 1; i < path.length; i++) {
280
+ const p = req(path[i - 1]);
281
+ const q = req(path[i]);
282
+ for (let t = 0; t <= 1; t += ELBOW_CROSS_SAMPLE_STEP) {
283
+ const x = p.x + (q.x - p.x) * t;
284
+ const y = p.y + (q.y - p.y) * t;
285
+ for (const o of obstacles) {
286
+ if (x > o.x + m && x < o.x + o.width - m && y > o.y + m && y < o.y + o.height - m)
287
+ return true;
288
+ }
289
+ }
290
+ }
291
+ return false;
292
+ };
293
+ /** EndInfo for a raw point that may sit on a shape (preview). */
294
+ const pointEndInfo = (scene, elId, self, other) => {
295
+ if (!elId)
296
+ return { heading: headingForPoint(other, self), obstacle: null };
297
+ const shape = getElement(scene, elId);
298
+ if (!shape)
299
+ return { heading: headingForPoint(other, self), obstacle: null };
300
+ return {
301
+ heading: headingForPointFromElement(shape, self),
302
+ obstacle: getElementWorldBounds(shape),
303
+ };
304
+ };
305
+ /**
306
+ * Pin user-dragged interior segments to their stored perpendicular
307
+ * coordinate. Moving a segment along its perpendicular only stretches the
308
+ * (perpendicular) neighbour segments — orientation is preserved — so this is
309
+ * a direct coordinate override, no propagation needed. Terminal segments
310
+ * (touching `from`/`to`) are skipped: their endpoint can't move.
311
+ */
312
+ const applyFixedSegments = (full, fixed) => {
313
+ if (!fixed || fixed.length === 0)
314
+ return full;
315
+ let out = full.map((p) => ({ ...p }));
316
+ for (const pin of fixed) {
317
+ const lastSeg = out.length - 2; // index of the last segment's start point
318
+ // Re-identify the pinned segment in the fresh route: the interior segment
319
+ // with the same axis whose centre is closest to the stored `at`. Robust to
320
+ // index / topology changes that a raw index can't survive.
321
+ let bestK = -1;
322
+ let bestD = Infinity;
323
+ for (let k = 1; k < lastSeg; k++) {
324
+ const a = req(out[k]);
325
+ const b = req(out[k + 1]);
326
+ const isH = Math.abs(a.y - b.y) < 1e-6;
327
+ const isV = Math.abs(a.x - b.x) < 1e-6;
328
+ if ((pin.axis === "h") !== isH || (pin.axis === "v") !== isV)
329
+ continue;
330
+ const centre = pin.axis === "h" ? (a.x + b.x) / 2 : (a.y + b.y) / 2;
331
+ const d = Math.abs(centre - pin.at);
332
+ if (d < bestD) {
333
+ bestD = d;
334
+ bestK = k;
335
+ }
336
+ }
337
+ if (bestK >= 0) {
338
+ // Slide an existing interior segment to its pinned coordinate.
339
+ const a = req(out[bestK]);
340
+ const b = req(out[bestK + 1]);
341
+ if (pin.axis === "h") {
342
+ a.y = pin.pos;
343
+ b.y = pin.pos;
344
+ }
345
+ else {
346
+ a.x = pin.pos;
347
+ b.x = pin.pos;
348
+ }
349
+ continue;
350
+ }
351
+ // No strict-interior match — the user dragged a segment adjacent to a
352
+ // buffer joint (or the single middle segment of an aligned elbow).
353
+ // Reconstruct the middle as a "staple" between the FIXED buffer joints
354
+ // (out[0] = bufA, out[last] = bufB) so a segment of the pinned axis sits at
355
+ // `pos` while the terminal buffers stay put (standard terminal-drag).
356
+ const j0 = req(out[0]);
357
+ const j1 = req(out[out.length - 1]);
358
+ out =
359
+ pin.axis === "h"
360
+ ? [j0, { x: j0.x, y: pin.pos }, { x: j1.x, y: pin.pos }, j1]
361
+ : [j0, { x: pin.pos, y: j0.y }, { x: pin.pos, y: j1.y }, j1];
362
+ }
363
+ return collapseColinear(out);
364
+ };
365
+ const namedHeading = (name) => {
366
+ switch (name) {
367
+ case "top":
368
+ return HEADING_UP;
369
+ case "bottom":
370
+ return HEADING_DOWN;
371
+ case "left":
372
+ return HEADING_LEFT;
373
+ case "right":
374
+ return HEADING_RIGHT;
375
+ default:
376
+ return null;
377
+ }
378
+ };
379
+ /** Heading (exit side) + obstacle bbox for one endpoint. */
380
+ const endInfo = (scene, ep, self, other) => {
381
+ if (ep.kind === "point")
382
+ return { heading: headingForPoint(other, self), obstacle: null };
383
+ const shape = getElement(scene, ep.elementId);
384
+ if (!shape)
385
+ return { heading: headingForPoint(other, self), obstacle: null };
386
+ const obstacle = getElementWorldBounds(shape);
387
+ if (ep.kind === "anchor" && ep.anchor.kind === "named") {
388
+ const h = namedHeading(ep.anchor.name);
389
+ if (h)
390
+ return { heading: h, obstacle };
391
+ }
392
+ // floating / outline / ratio anchors: `self` is already the resolved point
393
+ // ON the outline, so exit perpendicular to the edge it sits on. Nearest-edge
394
+ // (not the centre cone test) keeps the exit outward even near corners.
395
+ return { heading: headingForEdgePoint(shape, self), obstacle };
396
+ };
397
+ /** Single-bend L corner between two dongles (router-failure fallback). */
398
+ const fallbackCorner = (a, b, headingA) => headingIsHorizontal(headingA) ? { x: b.x, y: a.y } : { x: a.x, y: b.y };
399
+ /** Drop colinear midpoints from an axis-aligned polyline (tolerant). */
400
+ const collapseColinear = (points) => {
401
+ if (points.length <= 2)
402
+ return [...points];
403
+ const out = [req(points[0])];
404
+ for (let i = 1; i < points.length - 1; i++) {
405
+ const prev = req(out[out.length - 1]);
406
+ const cur = req(points[i]);
407
+ const next = req(points[i + 1]);
408
+ const eps = 1e-6;
409
+ const horizontal = Math.abs(prev.y - cur.y) < eps && Math.abs(cur.y - next.y) < eps;
410
+ const vertical = Math.abs(prev.x - cur.x) < eps && Math.abs(cur.x - next.x) < eps;
411
+ const coincident = Math.abs(prev.x - cur.x) < eps && Math.abs(prev.y - cur.y) < eps;
412
+ if (horizontal || vertical || coincident)
413
+ continue;
414
+ out.push(cur);
415
+ }
416
+ out.push(req(points[points.length - 1]));
417
+ return out;
418
+ };
419
+ //# sourceMappingURL=elbow-link.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"elbow-link.js","sourceRoot":"","sources":["../src/elbow-link.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { Bounds, Vec2 } from "@oh-just-another/types";
2
+ export interface ElbowRouteOptions {
3
+ /** Pad obstacles by this many world units. Defaults to package constant. */
4
+ readonly margin?: number;
5
+ /**
6
+ * Outward exit heading at `from` (the buffer direction). When set, the
7
+ * router's FIRST move may not go in the opposite direction — so the route
8
+ * never immediately reverses (retraces) the terminal buffer stub.
9
+ */
10
+ readonly startHeading?: Vec2;
11
+ /**
12
+ * Outward heading at `to` (the buffer direction). When set, the route may
13
+ * not ARRIVE at the goal travelling in this direction — which would force
14
+ * `bufB → to` to reverse it. Keeps the connector from doubling back at the
15
+ * arrival end.
16
+ */
17
+ readonly endHeading?: Vec2;
18
+ }
19
+ export declare const elbowRoute: (from: Vec2, to: Vec2, obstacles: readonly Bounds[], options?: ElbowRouteOptions) => readonly Vec2[] | null;
20
+ //# sourceMappingURL=elbow-router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"elbow-router.d.ts","sourceRoot":"","sources":["../src/elbow-router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AA0C3D,MAAM,WAAW,iBAAiB;IAChC,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,IAAI,CAAC;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC;CAC5B;AAED,eAAO,MAAM,UAAU,GACrB,MAAM,IAAI,EACV,IAAI,IAAI,EACR,WAAW,SAAS,MAAM,EAAE,EAC5B,UAAS,iBAAsB,KAC9B,SAAS,IAAI,EAAE,GAAG,IAgHpB,CAAC"}