@graview/react 0.1.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 (147) hide show
  1. package/LICENSE +96 -0
  2. package/README.md +20 -0
  3. package/dist/activity.d.ts +113 -0
  4. package/dist/activity.d.ts.map +1 -0
  5. package/dist/activity.js +207 -0
  6. package/dist/activity.js.map +1 -0
  7. package/dist/animation.d.ts +64 -0
  8. package/dist/animation.d.ts.map +1 -0
  9. package/dist/animation.js +245 -0
  10. package/dist/animation.js.map +1 -0
  11. package/dist/captions.d.ts +43 -0
  12. package/dist/captions.d.ts.map +1 -0
  13. package/dist/captions.js +61 -0
  14. package/dist/captions.js.map +1 -0
  15. package/dist/channels.d.ts +15 -0
  16. package/dist/channels.d.ts.map +1 -0
  17. package/dist/channels.js +114 -0
  18. package/dist/channels.js.map +1 -0
  19. package/dist/connectors.d.ts +176 -0
  20. package/dist/connectors.d.ts.map +1 -0
  21. package/dist/connectors.js +837 -0
  22. package/dist/connectors.js.map +1 -0
  23. package/dist/context.d.ts +348 -0
  24. package/dist/context.d.ts.map +1 -0
  25. package/dist/context.js +568 -0
  26. package/dist/context.js.map +1 -0
  27. package/dist/drawn.d.ts +60 -0
  28. package/dist/drawn.d.ts.map +1 -0
  29. package/dist/drawn.js +58 -0
  30. package/dist/drawn.js.map +1 -0
  31. package/dist/edge-signs.d.ts +13 -0
  32. package/dist/edge-signs.d.ts.map +1 -0
  33. package/dist/edge-signs.js +76 -0
  34. package/dist/edge-signs.js.map +1 -0
  35. package/dist/hooks.d.ts +160 -0
  36. package/dist/hooks.d.ts.map +1 -0
  37. package/dist/hooks.js +457 -0
  38. package/dist/hooks.js.map +1 -0
  39. package/dist/index.d.ts +37 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +22 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/keyboard.d.ts +5 -0
  44. package/dist/keyboard.d.ts.map +1 -0
  45. package/dist/keyboard.js +178 -0
  46. package/dist/keyboard.js.map +1 -0
  47. package/dist/kit.d.ts +10 -0
  48. package/dist/kit.d.ts.map +1 -0
  49. package/dist/kit.js +32 -0
  50. package/dist/kit.js.map +1 -0
  51. package/dist/local-intelligence.d.ts +31 -0
  52. package/dist/local-intelligence.d.ts.map +1 -0
  53. package/dist/local-intelligence.js +66 -0
  54. package/dist/local-intelligence.js.map +1 -0
  55. package/dist/motion.d.ts +17 -0
  56. package/dist/motion.d.ts.map +1 -0
  57. package/dist/motion.js +26 -0
  58. package/dist/motion.js.map +1 -0
  59. package/dist/occupants.d.ts +45 -0
  60. package/dist/occupants.d.ts.map +1 -0
  61. package/dist/occupants.js +57 -0
  62. package/dist/occupants.js.map +1 -0
  63. package/dist/parallel.d.ts +19 -0
  64. package/dist/parallel.d.ts.map +1 -0
  65. package/dist/parallel.js +30 -0
  66. package/dist/parallel.js.map +1 -0
  67. package/dist/picking.d.ts +30 -0
  68. package/dist/picking.d.ts.map +1 -0
  69. package/dist/picking.js +140 -0
  70. package/dist/picking.js.map +1 -0
  71. package/dist/plots.d.ts +106 -0
  72. package/dist/plots.d.ts.map +1 -0
  73. package/dist/plots.js +306 -0
  74. package/dist/plots.js.map +1 -0
  75. package/dist/pointer.d.ts +27 -0
  76. package/dist/pointer.d.ts.map +1 -0
  77. package/dist/pointer.js +39 -0
  78. package/dist/pointer.js.map +1 -0
  79. package/dist/presence.d.ts +110 -0
  80. package/dist/presence.d.ts.map +1 -0
  81. package/dist/presence.js +280 -0
  82. package/dist/presence.js.map +1 -0
  83. package/dist/rails.d.ts +11 -0
  84. package/dist/rails.d.ts.map +1 -0
  85. package/dist/rails.js +15 -0
  86. package/dist/rails.js.map +1 -0
  87. package/dist/resolved-view.d.ts +78 -0
  88. package/dist/resolved-view.d.ts.map +1 -0
  89. package/dist/resolved-view.js +365 -0
  90. package/dist/resolved-view.js.map +1 -0
  91. package/dist/robot.d.ts +114 -0
  92. package/dist/robot.d.ts.map +1 -0
  93. package/dist/robot.js +93 -0
  94. package/dist/robot.js.map +1 -0
  95. package/dist/routes.d.ts +50 -0
  96. package/dist/routes.d.ts.map +1 -0
  97. package/dist/routes.js +150 -0
  98. package/dist/routes.js.map +1 -0
  99. package/dist/scene-camera.d.ts +25 -0
  100. package/dist/scene-camera.d.ts.map +1 -0
  101. package/dist/scene-camera.js +130 -0
  102. package/dist/scene-camera.js.map +1 -0
  103. package/dist/scene-hand.d.ts +110 -0
  104. package/dist/scene-hand.d.ts.map +1 -0
  105. package/dist/scene-hand.js +317 -0
  106. package/dist/scene-hand.js.map +1 -0
  107. package/dist/scene-helpers.d.ts +46 -0
  108. package/dist/scene-helpers.d.ts.map +1 -0
  109. package/dist/scene-helpers.js +108 -0
  110. package/dist/scene-helpers.js.map +1 -0
  111. package/dist/scene-lines.d.ts +76 -0
  112. package/dist/scene-lines.d.ts.map +1 -0
  113. package/dist/scene-lines.js +578 -0
  114. package/dist/scene-lines.js.map +1 -0
  115. package/dist/scene-root.d.ts +71 -0
  116. package/dist/scene-root.d.ts.map +1 -0
  117. package/dist/scene-root.js +790 -0
  118. package/dist/scene-root.js.map +1 -0
  119. package/dist/scene.d.ts +17 -0
  120. package/dist/scene.d.ts.map +1 -0
  121. package/dist/scene.js +17 -0
  122. package/dist/scene.js.map +1 -0
  123. package/dist/seat-marks.d.ts +35 -0
  124. package/dist/seat-marks.d.ts.map +1 -0
  125. package/dist/seat-marks.js +43 -0
  126. package/dist/seat-marks.js.map +1 -0
  127. package/dist/settings.d.ts +30 -0
  128. package/dist/settings.d.ts.map +1 -0
  129. package/dist/settings.js +73 -0
  130. package/dist/settings.js.map +1 -0
  131. package/dist/view-boundary.d.ts +33 -0
  132. package/dist/view-boundary.d.ts.map +1 -0
  133. package/dist/view-boundary.js +53 -0
  134. package/dist/view-boundary.js.map +1 -0
  135. package/dist/view-host.d.ts +57 -0
  136. package/dist/view-host.d.ts.map +1 -0
  137. package/dist/view-host.js +474 -0
  138. package/dist/view-host.js.map +1 -0
  139. package/dist/view-registry.d.ts +122 -0
  140. package/dist/view-registry.d.ts.map +1 -0
  141. package/dist/view-registry.js +20 -0
  142. package/dist/view-registry.js.map +1 -0
  143. package/dist/where-drawn.d.ts +151 -0
  144. package/dist/where-drawn.d.ts.map +1 -0
  145. package/dist/where-drawn.js +231 -0
  146. package/dist/where-drawn.js.map +1 -0
  147. package/package.json +60 -0
@@ -0,0 +1,837 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { edgeSelectionId, edgeOfSelection } from "@graview/layout";
3
+ import { CONNECTOR_DASH, connectorStroke, connectorWidth } from "@graview/render";
4
+ import { kitConnector, useKit } from "./kit.js";
5
+ import { clipPolyline, latticePoints, orthogonalPoints, polylineD, roundedPolylineD, routedQuadratic, } from "./routes.js";
6
+ import { channelRoute } from "./channels.js";
7
+ import { parallelOffsets } from "./parallel.js";
8
+ import { altitudeOpacity, drawnBox, measureVisible, onScreen, stackOpacity } from "./where-drawn.js";
9
+ /**
10
+ * Where a line toward `towards` should MEET a box: on its border, not at its
11
+ * centre.
12
+ *
13
+ * Centre-anchored lines cross the card's own interior on the way out — the
14
+ * yellow dash sawing through the middle of REASONS was this — and where
15
+ * several relations share an endpoint they converge to a single point at the
16
+ * centre, which reads as a knot rather than as several roads arriving. The
17
+ * border is where a road meets a building.
18
+ */
19
+ /**
20
+ * HOW A TIE RUNS between two measured drawings — a pure decision, so it is
21
+ * testable without a browser.
22
+ *
23
+ * Two drawings stacked in one column used to get a near-straight vertical
24
+ * aimed centre-to-centre — a line THROUGH every row between them, whose
25
+ * hit corridor then stole those rows' clicks. Boxes that share a column
26
+ * stitch along their common right edge, in the gutter; boxes that share a
27
+ * row stitch over the top. Only ends with clear air between them take the
28
+ * direct arc. Returns null when no honest line exists: one drawing inside
29
+ * another is the view's own composition, and anchors within a few pixels
30
+ * are one drawing restating itself.
31
+ */
32
+ export function tieRoute(fromBox, toBox) {
33
+ const xOverlap = Math.min(fromBox.x + fromBox.width, toBox.x + toBox.width) - Math.max(fromBox.x, toBox.x);
34
+ const yOverlap = Math.min(fromBox.y + fromBox.height, toBox.y + toBox.height) - Math.max(fromBox.y, toBox.y);
35
+ if (xOverlap > 0 && yOverlap > 0)
36
+ return null;
37
+ const stacked = xOverlap > 0.5 * Math.min(fromBox.width, toBox.width);
38
+ const abreast = !stacked && yOverlap > 0.5 * Math.min(fromBox.height, toBox.height);
39
+ if (stacked) {
40
+ const from = { x: fromBox.x + fromBox.width, y: fromBox.y + fromBox.height / 2 };
41
+ const to = { x: toBox.x + toBox.width, y: toBox.y + toBox.height / 2 };
42
+ return {
43
+ from,
44
+ to,
45
+ control: { x: Math.max(from.x, to.x) + 18, y: (from.y + to.y) / 2 },
46
+ mode: "stacked",
47
+ };
48
+ }
49
+ if (abreast) {
50
+ const from = { x: fromBox.x + fromBox.width / 2, y: fromBox.y };
51
+ const to = { x: toBox.x + toBox.width / 2, y: toBox.y };
52
+ return {
53
+ from,
54
+ to,
55
+ control: { x: (from.x + to.x) / 2, y: Math.min(from.y, to.y) - 18 },
56
+ mode: "abreast",
57
+ };
58
+ }
59
+ const toCentre = { x: toBox.x + toBox.width / 2, y: toBox.y + toBox.height / 2 };
60
+ const fromCentre = { x: fromBox.x + fromBox.width / 2, y: fromBox.y + fromBox.height / 2 };
61
+ const from = edgePoint(fromBox, toCentre);
62
+ const to = edgePoint(toBox, fromCentre);
63
+ const direct = Math.hypot(to.x - from.x, to.y - from.y);
64
+ if (direct < 8)
65
+ return null;
66
+ const bow = Math.min(36, direct * 0.12);
67
+ const nx = -(to.y - from.y) / direct;
68
+ const ny = (to.x - from.x) / direct;
69
+ return {
70
+ from,
71
+ to,
72
+ control: { x: (from.x + to.x) / 2 + nx * bow, y: (from.y + to.y) / 2 + ny * bow },
73
+ mode: "direct",
74
+ };
75
+ }
76
+ function edgePoint(box, towards) {
77
+ const cx = box.x + box.width / 2;
78
+ const cy = box.y + box.height / 2;
79
+ const dx = towards.x - cx;
80
+ const dy = towards.y - cy;
81
+ if (dx === 0 && dy === 0)
82
+ return { x: cx, y: cy };
83
+ // How far along the direction the border sits, on whichever side is hit
84
+ // first — the standard slab intersection, for an axis-aligned box.
85
+ const tx = dx === 0 ? Infinity : box.width / 2 / Math.abs(dx);
86
+ const ty = dy === 0 ? Infinity : box.height / 2 / Math.abs(dy);
87
+ const t = Math.min(tx, ty);
88
+ // A few pixels shy of the border, so the stroke's rounded cap does not
89
+ // poke into the card.
90
+ const out = Math.max(0, t * 0.98);
91
+ return { x: cx + dx * out, y: cy + dy * out };
92
+ }
93
+ /**
94
+ * Whether the connector layer draws this line — ONE rule, shared with the
95
+ * ties layer so a relation is never drawn twice with different manners.
96
+ * Above the stack every relation earns its ink; inside it a line must touch
97
+ * a raised node and must not end on a receded group ("some of these" is not
98
+ * a relationship anyone can read).
99
+ */
100
+ function connectorShows(connector, byId, overview) {
101
+ const from = byId.get(connector.from);
102
+ const to = byId.get(connector.to);
103
+ if (!from || !to)
104
+ return false;
105
+ if (overview)
106
+ return true;
107
+ const raised = Math.round(from.plane) === 1 || Math.round(to.plane) === 1;
108
+ const vagueEnd = (node) => Boolean(node.aggregate) && Math.round(node.plane) === 2;
109
+ return raised && !vagueEnd(from) && !vagueEnd(to);
110
+ }
111
+ const newPass = () => ({ clips: new Map(), members: new Map(), marks: new Map() });
112
+ function marksIn(host, pass) {
113
+ let marks = pass.marks.get(host);
114
+ if (marks)
115
+ return marks;
116
+ marks = new Map();
117
+ for (const el of host.querySelectorAll("[data-graview-pick], [data-graview-slot]")) {
118
+ for (const id of new Set([el.getAttribute("data-graview-pick"), el.getAttribute("data-graview-slot")])) {
119
+ if (id === null)
120
+ continue;
121
+ const list = marks.get(id);
122
+ if (list)
123
+ list.push(el);
124
+ else
125
+ marks.set(id, [el]);
126
+ }
127
+ }
128
+ pass.marks.set(host, marks);
129
+ return marks;
130
+ }
131
+ function clipOf(parent, pass) {
132
+ let held = pass.clips.get(parent);
133
+ if (held === undefined) {
134
+ const style = getComputedStyle(parent);
135
+ const clips = style.overflow !== "visible" || style.overflowX !== "visible" || style.overflowY !== "visible";
136
+ held = clips ? parent.getBoundingClientRect() : null;
137
+ pass.clips.set(parent, held);
138
+ }
139
+ return held;
140
+ }
141
+ function withinItsScroller(el, rect, pass) {
142
+ let parent = el.parentElement;
143
+ while (parent !== null) {
144
+ const box = clipOf(parent, pass);
145
+ if (box) {
146
+ const overlaps = rect.right > box.left + 1 &&
147
+ rect.left < box.right - 1 &&
148
+ rect.bottom > box.top + 1 &&
149
+ rect.top < box.bottom - 1;
150
+ if (!overlaps)
151
+ return false;
152
+ }
153
+ if (parent.hasAttribute("data-graview-view"))
154
+ break;
155
+ parent = parent.parentElement;
156
+ }
157
+ return true;
158
+ }
159
+ /**
160
+ * The elements a view draws for a MEMBER of a drawn node — a session's span
161
+ * in the week, a drill's chip in a session card — measured in stage space
162
+ * and sorted compact-first. Chrome is not scene: an offstage rail repeating
163
+ * a name as a chip is never an anchor.
164
+ */
165
+ function memberBoxes(stageEl, host, memberId, pass) {
166
+ if (!stageEl || !host || typeof document === "undefined")
167
+ return [];
168
+ let inHost = pass.members.get(host);
169
+ if (!inHost)
170
+ pass.members.set(host, (inHost = new Map()));
171
+ const known = inHost.get(memberId);
172
+ if (known)
173
+ return known;
174
+ const stage = stageEl.getBoundingClientRect();
175
+ const boxes = [];
176
+ for (const el of marksIn(host, pass).get(memberId) ?? []) {
177
+ if (el.closest("[data-graview-offstage]"))
178
+ continue;
179
+ const rect = el.getBoundingClientRect();
180
+ if (rect.width <= 2 || rect.height <= 2)
181
+ continue;
182
+ /*
183
+ * A MEMBER SCROLLED OUT OF ITS PANEL IS NOT AN ANCHOR.
184
+ *
185
+ * `getBoundingClientRect` answers for an element that has been scrolled
186
+ * past the edge of its own scroller just as happily as for one you can
187
+ * see — with coordinates that are outside the panel, and often outside
188
+ * the stage. A line drawn to that lands somewhere there is nothing,
189
+ * usually across a neighbouring card, and reads as the picture lying
190
+ * about the graph.
191
+ *
192
+ * Where the member is out of sight the line falls back to the host,
193
+ * which is the honest answer: the thing is in there somewhere, and the
194
+ * panel is the finest box that is actually true.
195
+ */
196
+ if (!withinItsScroller(el, rect, pass))
197
+ continue;
198
+ boxes.push({
199
+ x: rect.left - stage.left,
200
+ y: rect.top - stage.top,
201
+ width: rect.width,
202
+ height: rect.height,
203
+ });
204
+ }
205
+ boxes.sort((a, b) => a.width * a.height - b.width * b.height);
206
+ inHost.set(memberId, boxes);
207
+ return boxes;
208
+ }
209
+ /**
210
+ * Resolves the frame's connectors into the strands the scene will draw.
211
+ *
212
+ * Pure of React and null-safe without a DOM: headless, every strand is
213
+ * anchored on the layout's own boxes, which is the old behaviour exactly.
214
+ * From altitude nothing is unpicked — the constellation is a picture of
215
+ * kinds, and a line from a span inside the shrunk stamp would say nothing
216
+ * the road between two districts does not.
217
+ */
218
+ export function connectorStrands(nodes, connectors, stageEl, overview, scheme) {
219
+ const byId = new Map(nodes.map((node) => [node.id, node]));
220
+ const pass = newPass();
221
+ const hostEls = new Map();
222
+ const hostOf = (id) => {
223
+ if (!stageEl || typeof document === "undefined")
224
+ return null;
225
+ let held = hostEls.get(id);
226
+ if (held === undefined) {
227
+ held = stageEl.querySelector(`[data-graview-view="${CSS.escape(id)}"]`);
228
+ hostEls.set(id, held);
229
+ }
230
+ return held;
231
+ };
232
+ // Every drawn box, measured once; a line dives under any it merely crosses.
233
+ const boxes = new Map();
234
+ for (const node of nodes) {
235
+ const box = measureVisible(stageEl, node.id, overview) ?? drawnBox(node, scheme);
236
+ if (box)
237
+ boxes.set(node.id, box);
238
+ }
239
+ /*
240
+ * THE DRAWINGS INSIDE THE VIEWS ARE OBSTACLES TOO.
241
+ *
242
+ * A line dives under any card it merely crosses — but "card" meant a
243
+ * DRAWN NODE, and the things a lens draws inside itself are not drawn
244
+ * nodes. So a line across the week passed over ten shift cards, and the
245
+ * connector layer sits above the hosts, so it passed over them literally:
246
+ * a stroke painted across somebody's Tuesday. Going under is what the
247
+ * clipping already does; it just had nothing to clip against in there.
248
+ *
249
+ * Measured once per pass, keyed by the id each drawing wears, so a
250
+ * strand can leave its own two ends out.
251
+ */
252
+ const memberBoxesById = new Map();
253
+ if (stageEl && typeof document !== "undefined") {
254
+ const stage = stageEl.getBoundingClientRect();
255
+ for (const el of stageEl.querySelectorAll("[data-graview-pick], [data-graview-slot]")) {
256
+ if (el.closest("[data-graview-offstage]"))
257
+ continue;
258
+ const id = el.getAttribute("data-graview-pick") ?? el.getAttribute("data-graview-slot");
259
+ if (id === null)
260
+ continue;
261
+ const rect = el.getBoundingClientRect();
262
+ if (rect.width <= 2 || rect.height <= 2)
263
+ continue;
264
+ /*
265
+ * A ROTATED HEADER'S BOUNDING BOX is a huge diagonal rectangle whose
266
+ * border is nowhere near the visible text — a matrix's column names
267
+ * are written on the slant, and taking their boxes as obstacles
268
+ * erased every line in the picture. Anything approaching the size of
269
+ * the stage is furniture, not a drawing.
270
+ */
271
+ if (rect.width > stage.width * 0.6 && rect.height > stage.height * 0.6)
272
+ continue;
273
+ const held = memberBoxesById.get(id);
274
+ const box = { x: rect.left - stage.left, y: rect.top - stage.top, width: rect.width, height: rect.height };
275
+ if (held)
276
+ held.push(box);
277
+ else
278
+ memberBoxesById.set(id, [box]);
279
+ }
280
+ }
281
+ const obstaclesFor = (a, b) => {
282
+ const out = [];
283
+ for (const [id, box] of boxes)
284
+ if (id !== a && id !== b)
285
+ out.push(box);
286
+ for (const [id, drawn] of memberBoxesById)
287
+ if (id !== a && id !== b)
288
+ out.push(...drawn);
289
+ return out;
290
+ };
291
+ // The relation band's chips, for a line whose both ends are in it.
292
+ const inBand = (id) => Math.round(byId.get(id)?.plane ?? -1) === 1;
293
+ const bandBoxes = [];
294
+ for (const node of nodes) {
295
+ const box = boxes.get(node.id);
296
+ if (box && Math.round(node.plane) === 1)
297
+ bandBoxes.push(box);
298
+ }
299
+ const bandFor = (a, b) => (!overview && inBand(a) && inBand(b) ? bandBoxes : undefined);
300
+ const strands = [];
301
+ for (const connector of connectors) {
302
+ if (!connectorShows(connector, byId, overview))
303
+ continue;
304
+ const fromHost = boxes.get(connector.from);
305
+ const toHost = boxes.get(connector.to);
306
+ if (!fromHost || !toHost)
307
+ continue;
308
+ const obstacles = obstaclesFor(connector.from, connector.to);
309
+ const edges = connector.edges;
310
+ /*
311
+ * At altitude a line runs district to district — unless a district has
312
+ * been opened in place and draws the member the line is about, in which
313
+ * case it lands on the member. Whether one is drawn is the only test.
314
+ */
315
+ const drawnMember = overview &&
316
+ edges.some((edge) => (edge.from !== connector.from && memberBoxes(stageEl, hostOf(connector.from), edge.from, pass).length > 0) ||
317
+ (edge.to !== connector.to && memberBoxes(stageEl, hostOf(connector.to), edge.to, pass).length > 0));
318
+ const band = bandFor(connector.from, connector.to);
319
+ if ((overview && !drawnMember) || connector.loop || edges.length === 0) {
320
+ strands.push({
321
+ key: connector.id,
322
+ connector,
323
+ edges,
324
+ fromBox: fromHost,
325
+ toBox: toHost,
326
+ fromAnchor: connector.from,
327
+ toAnchor: connector.to,
328
+ obstacles,
329
+ hosts: [],
330
+ ...(band ? { band } : {}),
331
+ });
332
+ continue;
333
+ }
334
+ const groups = new Map();
335
+ for (const edge of edges) {
336
+ const fromCandidates = edge.from === connector.from ? [] : memberBoxes(stageEl, hostOf(connector.from), edge.from, pass);
337
+ const toCandidates = edge.to === connector.to ? [] : memberBoxes(stageEl, hostOf(connector.to), edge.to, pass);
338
+ /*
339
+ * THE FOCUS THAT DRAWS BOTH ENDS HAS DRAWN THE RELATION. The lists
340
+ * view draws every task inside its list; a line from each list column
341
+ * to the same task's chip in the band restated, twelve times over the
342
+ * panel, what the columns already said. Only the FOCUS counts: a card
343
+ * in the band summarising its members as chips has not drawn the
344
+ * relation between the focus's spans and itself — those lines are the
345
+ * point of raising it, and silencing them left a calendar whose
346
+ * entries seemed to belong to no list until one was selected.
347
+ */
348
+ const focusEnd = [connector.from, connector.to].find((id) => Math.round(byId.get(id)?.plane ?? -1) === 0);
349
+ if (!overview &&
350
+ focusEnd !== undefined &&
351
+ (edge.from !== connector.from || edge.to !== connector.to) &&
352
+ memberBoxes(stageEl, hostOf(focusEnd), focusEnd === connector.from ? edge.to : edge.from, pass).length > 0) {
353
+ continue;
354
+ }
355
+ /*
356
+ * A LINE LANDS ON A DRAWING OF THE THING, OR IS NOT DRAWN. A group in
357
+ * focus draws what its view draws — a calendar, the entries with a
358
+ * time — and an edge to a member it does not draw has no end here.
359
+ * Anchored on the panel instead, three someday tasks with no time
360
+ * became one line from their list into the middle of the week.
361
+ */
362
+ const focusNode = focusEnd !== undefined ? byId.get(focusEnd) : undefined;
363
+ if (!overview &&
364
+ focusNode?.aggregate &&
365
+ stageEl &&
366
+ (focusEnd === connector.from ? edge.from !== connector.from && fromCandidates.length === 0 : edge.to !== connector.to && toCandidates.length === 0)) {
367
+ continue;
368
+ }
369
+ const fromAnchor = fromCandidates.length > 0 ? edge.from : connector.from;
370
+ const toAnchor = toCandidates.length > 0 ? edge.to : connector.to;
371
+ const key = `${connector.id}|${fromAnchor}|${toAnchor}`;
372
+ const held = groups.get(key);
373
+ if (held) {
374
+ held.edges.push(edge);
375
+ continue;
376
+ }
377
+ /*
378
+ * THE CLOSEST PAIR of drawings, when an end is drawn more than once —
379
+ * the same rule a tie follows, so a line and a tie to the same chip
380
+ * agree about which chip.
381
+ */
382
+ const froms = fromCandidates.length > 0 ? fromCandidates : [fromHost];
383
+ const tos = toCandidates.length > 0 ? toCandidates : [toHost];
384
+ let fromBox = froms[0];
385
+ let toBox = tos[0];
386
+ let nearest = Infinity;
387
+ for (const a of froms) {
388
+ for (const b of tos) {
389
+ const gap = Math.hypot(a.x + a.width / 2 - (b.x + b.width / 2), a.y + a.height / 2 - (b.y + b.height / 2));
390
+ if (gap < nearest) {
391
+ nearest = gap;
392
+ fromBox = a;
393
+ toBox = b;
394
+ }
395
+ }
396
+ }
397
+ groups.set(key, { edges: [edge], fromBox, toBox, fromAnchor, toAnchor });
398
+ }
399
+ for (const [key, group] of groups) {
400
+ strands.push({
401
+ key,
402
+ connector,
403
+ obstacles,
404
+ hosts: [
405
+ ...(group.fromAnchor !== connector.from ? [fromHost] : []),
406
+ ...(group.toAnchor !== connector.to ? [toHost] : []),
407
+ ],
408
+ ...(band ? { band } : {}),
409
+ ...group,
410
+ });
411
+ }
412
+ }
413
+ return strands;
414
+ }
415
+ function quadraticAt(q, t) {
416
+ const a = (1 - t) * (1 - t);
417
+ const b = 2 * (1 - t) * t;
418
+ const c = t * t;
419
+ return {
420
+ x: a * q.p0.x + b * q.c.x + c * q.p1.x,
421
+ y: a * q.p0.y + b * q.c.y + c * q.p1.y,
422
+ };
423
+ }
424
+ /** The piece of a quadratic between parameters `a` and `b`, as a quadratic. */
425
+ function subQuadratic(q, a, b) {
426
+ // The polar form: the control point of the piece is the blossom at (a, b).
427
+ const w0 = (1 - a) * (1 - b);
428
+ const w1 = (1 - a) * b + a * (1 - b);
429
+ const w2 = a * b;
430
+ return {
431
+ p0: quadraticAt(q, a),
432
+ c: { x: w0 * q.p0.x + w1 * q.c.x + w2 * q.p1.x, y: w0 * q.p0.y + w1 * q.c.y + w2 * q.p1.y },
433
+ p1: quadraticAt(q, b),
434
+ };
435
+ }
436
+ /**
437
+ * THE VISIBLE RUNS of a curve: every stretch of it that lies outside the
438
+ * given boxes, as exact pieces of the same curve.
439
+ *
440
+ * A line used to be clipped by sitting BEHIND the cards, which was free and
441
+ * was also why a line anchored on a span inside the week never showed the
442
+ * stretch from the span to the panel's edge — it was under the panel. The
443
+ * lines now sit over the cards and clip themselves: out of the box they
444
+ * start in, into the box they end in, and under any card they cross on
445
+ * the way. Sampled, then each crossing refined by bisection, so a run ends
446
+ * on a border rather than a sample shy of it.
447
+ */
448
+ export function clipQuadratic(q, boxes) {
449
+ const inside = (p) => boxes.some((box) => p.x > box.x && p.x < box.x + box.width && p.y > box.y && p.y < box.y + box.height);
450
+ const out = (t) => !inside(quadraticAt(q, t));
451
+ const STEPS = 64;
452
+ // Where a sample and its neighbour disagree, the border lies between them.
453
+ const refine = (lo, hi, loOut) => {
454
+ for (let i = 0; i < 10; i++) {
455
+ const mid = (lo + hi) / 2;
456
+ if (out(mid) === loOut)
457
+ lo = mid;
458
+ else
459
+ hi = mid;
460
+ }
461
+ return (lo + hi) / 2;
462
+ };
463
+ const runs = [];
464
+ let open = null;
465
+ let wasOut = out(0);
466
+ if (wasOut)
467
+ open = 0;
468
+ for (let i = 1; i <= STEPS; i++) {
469
+ const t = i / STEPS;
470
+ const isOut = out(t);
471
+ if (isOut === wasOut)
472
+ continue;
473
+ const at = refine((i - 1) / STEPS, t, wasOut);
474
+ if (isOut)
475
+ open = at;
476
+ else if (open !== null) {
477
+ if (at - open > 1e-3)
478
+ runs.push(subQuadratic(q, open, at));
479
+ open = null;
480
+ }
481
+ wasOut = isOut;
482
+ }
483
+ if (open !== null && 1 - open > 1e-3)
484
+ runs.push(subQuadratic(q, open, 1));
485
+ return runs;
486
+ }
487
+ /**
488
+ * Connectors, drawn in SVG over the scene on BOTH renderer paths.
489
+ *
490
+ * A deliberate choice, not an omission: the GPU pipeline earns its cost on
491
+ * per-plane blur of captured pixels, and lines are the one thing it would be
492
+ * worse at. SVG strokes stay crisp at any scale, carry their edge kind into
493
+ * the DOM where a test or a screen reader can find it, and cost nothing to
494
+ * restyle. The capture inside a `layoutsubtree` canvas is not disturbed,
495
+ * because this sits outside it.
496
+ */
497
+ export function Connectors({ strands, result, scheme, overview, selection, emphasis, stageRef, liveOf, onPickEdge, }) {
498
+ const kit = useKit();
499
+ /*
500
+ * ROADS. From altitude, with the districts on the lattice, a line between
501
+ * two of them runs along the lattice's diagonals rather than bowing
502
+ * around the middle — a road on the same grid the buildings stand on.
503
+ * Inside the stack the kit's route stands.
504
+ */
505
+ const onLattice = new Set(result.nodes.filter((node) => node.plot !== undefined && Math.round(node.plane) === 2).map((node) => node.id));
506
+ const roadBetween = (connector) => overview && result.city !== undefined && onLattice.has(connector.from) && onLattice.has(connector.to);
507
+ /*
508
+ * Selecting DRAWS ITS RELATIONS and recedes the rest.
509
+ *
510
+ * The selection holds real node ids; a connector in the overview holds the
511
+ * ids of whatever is DRAWN, which up there is always a kind card. Comparing
512
+ * them directly matched nothing — so carrying an ordinary selection into the
513
+ * Graview, or clicking a target inside the shrunk picture, receded every
514
+ * line at once and blanked the thing you rose to look at.
515
+ *
516
+ * So the selection is resolved the same way `connectorsFor` resolves an
517
+ * endpoint: a node that is not drawn is represented by the card that stands
518
+ * for it. And a selection that resolves to nothing on screen means NO
519
+ * emphasis rather than none-of-the-above, which is the rule every view here
520
+ * follows.
521
+ */
522
+ const chosen = onScreen(result.nodes, selection);
523
+ const touches = (connector) => chosen.size === 0 || chosen.has(connector.from) || chosen.has(connector.to);
524
+ /*
525
+ * Inside the stack the SELECTED THING'S OWN LINES come forward.
526
+ *
527
+ * A strand knows the real edges it stands for, so no resolution is needed:
528
+ * a line touches the selection when one of its edges does. This is what
529
+ * the ties layer used to add on top — and then had to yield, one relation
530
+ * at a time, to the line already here. One line, lit.
531
+ */
532
+ const chosenReal = new Set(selection.filter((id) => edgeOfSelection(id) === null));
533
+ /*
534
+ * The live view standing in the middle of the ring, whose box is the one
535
+ * thing a road between districts must not run beneath.
536
+ */
537
+ const stampNode = overview
538
+ ? result.nodes.find((node) => Math.round(node.plane) === 0)
539
+ : undefined;
540
+ const stamp = stampNode
541
+ ? (measureVisible(stageRef.current, stampNode.id, false) ?? drawnBox(stampNode, scheme))
542
+ : null;
543
+ if (strands.length === 0)
544
+ return null;
545
+ /*
546
+ * A CHOSEN MEMBER IS NOT ITS WHOLE DISTRICT.
547
+ *
548
+ * Up at altitude a selection is resolved to the card that stands for it,
549
+ * because that is what a connector actually points at — and that is right
550
+ * for deciding WHICH CARDS a line touches. It is wrong for deciding which
551
+ * LINE: with the volunteers opened, every one of the seven covered-by
552
+ * strands ends at the volunteers card, so choosing Ada lit Bo's shifts,
553
+ * and Cass's, and Dev's. Clicking a name in an opened district changed
554
+ * nothing about the picture, which is the one thing clicking a name is for.
555
+ *
556
+ * A strand knows the real edges it stands for. When the selection names
557
+ * something those edges actually mention, that is the finer and truer
558
+ * answer and it wins; when it names none of them — a district chosen as a
559
+ * district, a stop carried in from elsewhere — the card rule stands.
560
+ */
561
+ const touchesChosen = (strand) => strand.edges.some((edge) => chosenReal.has(edge.from) || chosenReal.has(edge.to));
562
+ const anyStrandChosen = chosenReal.size > 0 && strands.some(touchesChosen);
563
+ /** How many lines each relation is drawing at once, for the crowd rule. */
564
+ const siblings = new Map();
565
+ for (const strand of strands)
566
+ siblings.set(strand.connector.id, (siblings.get(strand.connector.id) ?? 0) + 1);
567
+ // Lines that share both ends, fanned so each is its own line (see parallel.ts).
568
+ const fanned = parallelOffsets(strands.map((strand) => ({ key: strand.key, a: strand.fromAnchor, b: strand.toAnchor })));
569
+ const drawn = strands.map((strand, lane) => {
570
+ const { connector, fromBox, toBox } = strand;
571
+ const fromCentre = { x: fromBox.x + fromBox.width / 2, y: fromBox.y + fromBox.height / 2 };
572
+ const toCentre = { x: toBox.x + toBox.width / 2, y: toBox.y + toBox.height / 2 };
573
+ /*
574
+ * In the overview a line meets a card at its border, facing the
575
+ * other end — the border is honest up there, because a kind card
576
+ * fills its box. Inside the stack a host is a band slot with the
577
+ * panel centred somewhere in it, so a border anchor dangles in open
578
+ * ground; centre-to-centre is right, and the run inside each box is
579
+ * clipped away below.
580
+ */
581
+ const from = overview ? edgePoint(fromBox, toCentre) : fromCentre;
582
+ const to = overview ? edgePoint(toBox, fromCentre) : toCentre;
583
+ /*
584
+ * A LOOP, where both ends are the same card.
585
+ *
586
+ * "A task waits for a task" is a real fact about the domain and the
587
+ * constellation is exactly where you would look for it — but as a line
588
+ * it has zero length. Drawn as an arc leaving the card's top and
589
+ * returning to its right, which is how every graph drawing has shown a
590
+ * self-relation for fifty years.
591
+ */
592
+ const self = connector.loop === true;
593
+ // Stroke treatment is derived from the edge kind, so `protects` can
594
+ // never be mistaken for `assigned-to`.
595
+ const { connector: kitLine, style } = kitConnector(kit, connector.kind);
596
+ // A kind the kit keeps quiet is not drawn; the legend still lists it.
597
+ if (!kitLine.visible)
598
+ return null;
599
+ const radius = self ? Math.max(22, Math.min(fromBox.width, fromBox.height) * 0.3) : 0;
600
+ /*
601
+ * The loop SITS ON the card's top edge, off to the right.
602
+ *
603
+ * Centred it drew a ring straight through the card's own name; pushed
604
+ * clear of the corner it became a circle floating in the ground next
605
+ * to a card, which from the Graview read as a stray mark rather than
606
+ * as a relation belonging to anything. Overlapping the edge by a few
607
+ * pixels is what makes it hang off the card instead of near it.
608
+ */
609
+ const anchor = self
610
+ ? {
611
+ x: fromCentre.x + fromBox.width * 0.22,
612
+ y: fromCentre.y - fromBox.height / 2 - radius + 7,
613
+ }
614
+ : from;
615
+ /*
616
+ * A gentle curve, bowed AWAY from the live view. Straight lines read
617
+ * as lasers; and in the overview the middle is where the live view
618
+ * stands, so a road between districts goes around it rather than
619
+ * underneath it. The bow is sized to what actually needs clearing:
620
+ * a chord that would cross the view's box bows until its apex is
621
+ * outside it, and a chord that already misses keeps only a gentle
622
+ * arc — bowing everything as though it crossed left slack cables
623
+ * sagging across open ground.
624
+ */
625
+ const midX = (from.x + to.x) / 2;
626
+ const midY = (from.y + to.y) / 2;
627
+ const dist = Math.hypot(to.x - from.x, to.y - from.y);
628
+ let bow = Math.min(overview ? 40 : 90, dist * (overview ? 0.09 : 0.16));
629
+ let nx = 0;
630
+ let ny = 0;
631
+ if (dist > 0) {
632
+ nx = -(to.y - from.y) / dist;
633
+ ny = (to.x - from.x) / dist;
634
+ let awayX = midX - result.width / 2;
635
+ let awayY = midY - result.height / 2;
636
+ if (stamp && !self) {
637
+ const cx = stamp.x + stamp.width / 2;
638
+ const cy = stamp.y + stamp.height / 2;
639
+ awayX = midX - cx;
640
+ awayY = midY - cy;
641
+ // How close the chord passes to the view's centre, against how
642
+ // far the view's corner reaches: the shortfall, doubled (the
643
+ // apex of a quadratic sits halfway to its control), is the bow
644
+ // that clears it.
645
+ const d = Math.abs(nx * (cx - from.x) + ny * (cy - from.y));
646
+ const reach = Math.hypot(stamp.width / 2, stamp.height / 2) * 0.85 + 24;
647
+ const along = ((cx - from.x) * (to.x - from.x) + (cy - from.y) * (to.y - from.y)) / (dist * dist);
648
+ if (d < reach && along > 0.1 && along < 0.9) {
649
+ bow = Math.min(180, Math.max(bow, (reach - d) * 2));
650
+ }
651
+ }
652
+ if (nx * awayX + ny * awayY < 0) {
653
+ nx = -nx;
654
+ ny = -ny;
655
+ }
656
+ }
657
+ // A second relation between the same two ends bows beside the first.
658
+ bow += 2 * (fanned.get(strand.key) ?? 0);
659
+ // The route is the kit's call: the bowed arc, its chord, or two elbows.
660
+ const curve = routedQuadratic(kitLine.route, { p0: from, c: { x: midX + nx * bow, y: midY + ny * bow }, p1: to });
661
+ /* A road runs between two districts on the lattice; a line to the
662
+ picture standing in the middle keeps the kit's own route. */
663
+ const road = roadBetween(connector);
664
+ const orthogonal = kitLine.route === "orthogonal" || road;
665
+ const bends = (a, b) => (road ? latticePoints(a, b) : orthogonalPoints(a, b));
666
+ /*
667
+ * The visible runs: out of the box it starts in, into the box it
668
+ * ends in, and under any card it crosses between. A line with no
669
+ * run in the open — one drawing inside another — has nothing
670
+ * honest to show.
671
+ */
672
+ /*
673
+ * TWO CHIPS OF ONE BAND take the gutters: a road through the grid
674
+ * that crosses nothing, so it is drawn whole rather than clipped to
675
+ * the pieces an arc left between the chips it ran under.
676
+ */
677
+ const channelled = !self && strand.band ? channelRoute(fromBox, toBox, strand.band, lane) : null;
678
+ const runs = self || orthogonal || channelled ? [] : clipQuadratic(curve, [fromBox, toBox, ...strand.obstacles]);
679
+ const legs = channelled ? [channelled] : !self && orthogonal ? clipPolyline(bends(from, to), [fromBox, toBox, ...strand.obstacles]) : [];
680
+ if (!self && runs.length === 0 && legs.length === 0)
681
+ return null;
682
+ const lastLeg = legs[legs.length - 1];
683
+ const firstDrawn = orthogonal || channelled ? legs[0]?.[0] : runs[0]?.p0;
684
+ const lastDrawn = orthogonal || channelled ? lastLeg?.[lastLeg.length - 1] : runs[runs.length - 1]?.p1;
685
+ const quad = (segments) => segments
686
+ .map((run) => `M ${run.p0.x} ${run.p0.y} Q ${run.c.x} ${run.c.y} ${run.p1.x} ${run.p1.y}`)
687
+ .join(" ");
688
+ const loopD =
689
+ // An arc that leaves and returns: two arcs of the same
690
+ // circle, so it closes cleanly at any size.
691
+ `M ${anchor.x - radius} ${anchor.y} A ${radius} ${radius} 0 1 1 ${anchor.x + radius} ${anchor.y}` +
692
+ ` A ${radius} ${radius} 0 0 1 ${anchor.x - radius} ${anchor.y}`;
693
+ const d = self ? loopD : channelled ? roundedPolylineD(channelled, 10) : orthogonal ? polylineD(legs) : quad(runs);
694
+ // The hit stroke also keeps out of the cards an end is drawn inside.
695
+ /*
696
+ * The hit corridor keeps OFF the cards by its own half-width: a
697
+ * road leaves a district at its border, and a fourteen-pixel
698
+ * corridor centred on that border sat seven pixels inside the
699
+ * card — so a press on the card's corner selected the road, and a
700
+ * double-click there travelled down it instead of into the kind.
701
+ */
702
+ const clear = (box) => ({
703
+ x: box.x - 8,
704
+ y: box.y - 8,
705
+ width: box.width + 16,
706
+ height: box.height + 16,
707
+ });
708
+ const keptOff = [fromBox, toBox, ...strand.hosts, ...strand.obstacles].map(clear);
709
+ const hitD = self
710
+ ? loopD
711
+ : channelled
712
+ ? polylineD(legs)
713
+ : orthogonal
714
+ ? polylineD(clipPolyline(bends(from, to), keptOff))
715
+ : quad(clipQuadratic(curve, keptOff));
716
+ const only = strand.edges.length === 1 ? strand.edges[0] : null;
717
+ const edgeId = only ? edgeSelectionId(connector.kind, only.from, only.to) : null;
718
+ const edgeChosen = edgeId !== null && selection.includes(edgeId);
719
+ const mine = strand.edges.some((edge) => chosenReal.has(edge.from) || chosenReal.has(edge.to));
720
+ const lit = !overview && chosenReal.size > 0 && mine;
721
+ const stressed = (emphasis !== null && connector.kind === emphasis) || edgeChosen;
722
+ /*
723
+ * FROM ALTITUDE THE ROADS ARE ON THE GROUND (the plots layer draws
724
+ * one per pair of districts, kerb to kerb). A line up here is drawn
725
+ * only when it says something the road cannot: the relation the
726
+ * legend is asking about, the edge that is chosen, a member of the
727
+ * selection, or a change that just happened. A focused screen's
728
+ * every member wired across the city was the picture this replaces.
729
+ */
730
+ const live = liveOf?.(connector) !== undefined;
731
+ if (overview && !stressed && !mine && !(chosen.size > 0 && touches(connector)) && !live)
732
+ return null;
733
+ const opacity = overview
734
+ ? altitudeOpacity({
735
+ emphasised: emphasis !== null,
736
+ stressed,
737
+ anyChosen: anyStrandChosen,
738
+ mine,
739
+ touches: touches(connector),
740
+ siblings: siblings.get(connector.id) ?? 1,
741
+ })
742
+ : stackOpacity({
743
+ edgeChosen,
744
+ lit,
745
+ // A relation the key is hovering lights the picture the same
746
+ // way a selection does, so the rest recedes for it too.
747
+ anyLit: anyStrandChosen || emphasis !== null,
748
+ own: style.opacity,
749
+ kit: kit.emphasis,
750
+ });
751
+ /*
752
+ * A lit line MARKS ITS FAR END, as a tie does: the destination is
753
+ * where the eye is being sent, and a dot says the line lands on
754
+ * something specific rather than trailing off.
755
+ */
756
+ const far = lit && firstDrawn && lastDrawn
757
+ ? strand.edges.some((edge) => chosenReal.has(edge.from))
758
+ ? lastDrawn
759
+ : firstDrawn
760
+ : null;
761
+ const line = (_jsxs("g", { children: [_jsx("path", { "data-graview-connector": connector.kind, "data-graview-edges": strand.edges.length, "data-graview-lit": lit || undefined, "data-graview-activity": liveOf?.(connector)?.manner, opacity: opacity * (connector.opacity ?? 1), d: d, fill: "none", stroke: connectorStroke(style),
762
+ /*
763
+ * Above the stack the LINES ARE THE CONTENT.
764
+ *
765
+ * Inside the scene a connector is an aside — it says how the
766
+ * thing you are looking at is caught up in something else, and
767
+ * drawing it loudly would compete with the thing itself. From
768
+ * the Graview the shape of the domain IS the subject.
769
+ */
770
+ strokeWidth: (lit ? Math.max(1.6, connectorWidth(style, overview)) : connectorWidth(style, overview)) +
771
+ (stressed ? 0.6 : 0), strokeDasharray: CONNECTOR_DASH[style.pattern], strokeLinecap: "round" }), far ? (_jsx("circle", { cx: far.x, cy: far.y, r: 2.6, fill: connectorStroke(style), opacity: opacity })) : null, self && overview ? (_jsx("text", { x: anchor.x, y: anchor.y - radius - 5, textAnchor: "middle", opacity: opacity, style: {
772
+ font: "9px var(--graview-font-body, system-ui)",
773
+ letterSpacing: "0.09em",
774
+ textTransform: "uppercase",
775
+ fill: "var(--graview-ink-faint)",
776
+ }, children: connector.kind.replace(/-/g, " ") })) : null] }, strand.key));
777
+ /*
778
+ * The HIT PATH for a line that stands for one edge: the SAME visible
779
+ * runs, hosted on a layer above the panels. Because the runs are
780
+ * already clipped to open ground, the pickable region is exactly
781
+ * what a person can see, and pressing a line never steals a card's
782
+ * click.
783
+ */
784
+ let hit = null;
785
+ // A line the picture has faded to a ghost must not keep a click
786
+ // band: pickability follows visibility.
787
+ if (edgeId && onPickEdge && !self && opacity >= 0.2) {
788
+ hit = (_jsx("path", { "data-graview-edge": edgeId, className: "graview-edge-hit", d: hitD, fill: "none",
789
+ // The ends stay clear: a line lands ON a chip, and a hit stroke that reached
790
+ // the chip took the click meant for it. Seven percent off each end.
791
+ pathLength: 1, strokeDasharray: "0.86", strokeDashoffset: -0.07, stroke: "transparent", strokeWidth: 14, style: { pointerEvents: "stroke", cursor: "pointer" },
792
+ /*
793
+ * EITHER button opens the menu AT THE LINE. A left-click
794
+ * that only swapped the rail's contents changed the world
795
+ * quietly, three hundred pixels from the pointer — a line
796
+ * has no page to travel to, so its one meaning is "act on
797
+ * this relation, here".
798
+ */
799
+ onClick: (event) => {
800
+ event.stopPropagation();
801
+ onPickEdge(edgeId, { x: event.clientX, y: event.clientY });
802
+ }, onContextMenu: (event) => {
803
+ event.preventDefault();
804
+ event.stopPropagation();
805
+ onPickEdge(edgeId, { x: event.clientX, y: event.clientY });
806
+ } }, `hit:${strand.key}`));
807
+ }
808
+ return { key: strand.key, line, hit };
809
+ });
810
+ return (_jsxs(_Fragment, { children: [_jsx("svg", { "aria-hidden": "true", "data-graview-world": "", width: result.width, height: result.height, style: {
811
+ position: "absolute",
812
+ left: 0,
813
+ top: 0,
814
+ pointerEvents: "none",
815
+ /*
816
+ * OVER the cards, on both paths.
817
+ *
818
+ * Behind them was free clipping on the DOM path and no clipping at
819
+ * all on the GPU path, whose canvas is opaque — and it hid the one
820
+ * stretch a line anchored inside a panel most needs to show, from
821
+ * the span to the panel's edge. Every run is clipped to open ground
822
+ * now, so sitting on top costs nothing the picture can see.
823
+ */
824
+ zIndex: 2,
825
+ }, children: drawn.map((piece) => piece?.line) }), drawn.some((piece) => piece?.hit) ? (_jsx("svg", { "data-graview-world": "", width: result.width, height: result.height, style: {
826
+ position: "absolute",
827
+ left: 0,
828
+ top: 0,
829
+ pointerEvents: "none",
830
+ // Above the hosts (auto) but BELOW the scene chrome at 5 —
831
+ // the legend and the quick-select must win their own corners;
832
+ // the paths inside are clipped to open ground, so nothing that
833
+ // looks like a panel behaves like a line.
834
+ zIndex: 4,
835
+ }, children: drawn.map((piece) => piece?.hit) })) : null] }));
836
+ }
837
+ //# sourceMappingURL=connectors.js.map