@kanzo-tech/graph 0.10.0 → 0.11.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 (204) hide show
  1. package/README.md +60 -95
  2. package/dist/core/categories.d.ts +12 -0
  3. package/dist/core/categories.d.ts.map +1 -0
  4. package/dist/core/categories.js +14 -0
  5. package/dist/core/categories.js.map +1 -0
  6. package/dist/core/channels.d.ts +34 -0
  7. package/dist/core/channels.d.ts.map +1 -0
  8. package/dist/core/channels.js +19 -0
  9. package/dist/core/channels.js.map +1 -0
  10. package/dist/core/detail.d.ts +18 -0
  11. package/dist/core/detail.d.ts.map +1 -0
  12. package/dist/core/detail.js +20 -0
  13. package/dist/core/detail.js.map +1 -0
  14. package/dist/core/filter.d.ts +34 -0
  15. package/dist/core/filter.d.ts.map +1 -0
  16. package/dist/core/filter.js +99 -0
  17. package/dist/core/filter.js.map +1 -0
  18. package/dist/core/refine.d.ts +14 -0
  19. package/dist/core/refine.d.ts.map +1 -0
  20. package/dist/core/refine.js +20 -0
  21. package/dist/core/refine.js.map +1 -0
  22. package/dist/{resident.d.ts → core/resident.d.ts} +7 -7
  23. package/dist/core/resident.d.ts.map +1 -0
  24. package/dist/core/resident.js +44 -0
  25. package/dist/core/resident.js.map +1 -0
  26. package/dist/core/scheduler.d.ts +42 -0
  27. package/dist/core/scheduler.d.ts.map +1 -0
  28. package/dist/core/scheduler.js +77 -0
  29. package/dist/core/scheduler.js.map +1 -0
  30. package/dist/core/state.d.ts +132 -0
  31. package/dist/core/state.d.ts.map +1 -0
  32. package/dist/core/store.d.ts +6 -0
  33. package/dist/core/store.d.ts.map +1 -0
  34. package/dist/core/store.js +235 -0
  35. package/dist/core/store.js.map +1 -0
  36. package/dist/core/tile-matrix.d.ts +37 -0
  37. package/dist/core/tile-matrix.d.ts.map +1 -0
  38. package/dist/core/tile-matrix.js +49 -0
  39. package/dist/core/tile-matrix.js.map +1 -0
  40. package/dist/core/tile.d.ts +54 -0
  41. package/dist/core/tile.d.ts.map +1 -0
  42. package/dist/core/tile.js +76 -0
  43. package/dist/core/tile.js.map +1 -0
  44. package/dist/core/tileset.d.ts +59 -0
  45. package/dist/core/tileset.d.ts.map +1 -0
  46. package/dist/core/tileset.js +177 -0
  47. package/dist/core/tileset.js.map +1 -0
  48. package/dist/{types.d.ts → core/types.d.ts} +9 -0
  49. package/dist/core/types.d.ts.map +1 -0
  50. package/dist/index.d.ts +37 -70
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +32 -34
  53. package/dist/index.js.map +1 -1
  54. package/dist/{use-graph-selection.d.ts → parts/gesture.d.ts} +4 -4
  55. package/dist/parts/gesture.d.ts.map +1 -0
  56. package/dist/{use-graph-selection.js → parts/gesture.js} +17 -17
  57. package/dist/parts/gesture.js.map +1 -0
  58. package/dist/parts/graph-canvas.d.ts +17 -0
  59. package/dist/parts/graph-canvas.d.ts.map +1 -0
  60. package/dist/parts/graph-canvas.js +174 -0
  61. package/dist/parts/graph-canvas.js.map +1 -0
  62. package/dist/parts/graph-inspector.d.ts +18 -0
  63. package/dist/parts/graph-inspector.d.ts.map +1 -0
  64. package/dist/parts/graph-inspector.js +84 -0
  65. package/dist/parts/graph-inspector.js.map +1 -0
  66. package/dist/parts/graph-legend.d.ts +12 -0
  67. package/dist/parts/graph-legend.d.ts.map +1 -0
  68. package/dist/parts/graph-legend.js +61 -0
  69. package/dist/parts/graph-legend.js.map +1 -0
  70. package/dist/parts/graph-toolbar.d.ts +15 -0
  71. package/dist/parts/graph-toolbar.d.ts.map +1 -0
  72. package/dist/parts/graph-toolbar.js +94 -0
  73. package/dist/parts/graph-toolbar.js.map +1 -0
  74. package/dist/parts/overlays.d.ts +33 -0
  75. package/dist/parts/overlays.d.ts.map +1 -0
  76. package/dist/{use-graph-overlays.js → parts/overlays.js} +8 -8
  77. package/dist/parts/overlays.js.map +1 -0
  78. package/dist/{shape-glyph.d.ts → parts/shape-glyph.d.ts} +1 -1
  79. package/dist/parts/shape-glyph.d.ts.map +1 -0
  80. package/dist/{shape-glyph.js → parts/shape-glyph.js} +1 -1
  81. package/dist/parts/shape-glyph.js.map +1 -0
  82. package/dist/react/graph-root.d.ts +15 -0
  83. package/dist/react/graph-root.d.ts.map +1 -0
  84. package/dist/react/graph-root.js +23 -0
  85. package/dist/react/graph-root.js.map +1 -0
  86. package/dist/{use-graph-prefs.d.ts → react/use-graph-prefs.d.ts} +2 -2
  87. package/dist/react/use-graph-prefs.d.ts.map +1 -0
  88. package/dist/{use-graph-prefs.js → react/use-graph-prefs.js} +3 -3
  89. package/dist/react/use-graph-prefs.js.map +1 -0
  90. package/dist/react/use-graph-state.d.ts +11 -0
  91. package/dist/react/use-graph-state.d.ts.map +1 -0
  92. package/dist/react/use-graph-state.js +16 -0
  93. package/dist/react/use-graph-state.js.map +1 -0
  94. package/dist/react/use-graph.d.ts +38 -0
  95. package/dist/react/use-graph.d.ts.map +1 -0
  96. package/dist/react/use-graph.js +87 -0
  97. package/dist/react/use-graph.js.map +1 -0
  98. package/dist/{adaptive.d.ts → render/adaptive.d.ts} +1 -1
  99. package/dist/render/adaptive.d.ts.map +1 -0
  100. package/dist/render/adaptive.js.map +1 -0
  101. package/dist/render/compose.d.ts +51 -0
  102. package/dist/render/compose.d.ts.map +1 -0
  103. package/dist/render/compose.js +107 -0
  104. package/dist/render/compose.js.map +1 -0
  105. package/dist/render/css-color.d.ts.map +1 -0
  106. package/dist/render/css-color.js.map +1 -0
  107. package/dist/render/encode.d.ts +42 -0
  108. package/dist/render/encode.d.ts.map +1 -0
  109. package/dist/render/encode.js +68 -0
  110. package/dist/render/encode.js.map +1 -0
  111. package/dist/render/graph-looks.d.ts.map +1 -0
  112. package/dist/render/graph-looks.js.map +1 -0
  113. package/dist/render/graph-model.d.ts +44 -0
  114. package/dist/render/graph-model.d.ts.map +1 -0
  115. package/dist/render/graph-model.js +80 -0
  116. package/dist/render/graph-model.js.map +1 -0
  117. package/dist/{graph-sim.d.ts → render/graph-sim.d.ts} +1 -1
  118. package/dist/render/graph-sim.d.ts.map +1 -0
  119. package/dist/render/graph-sim.js.map +1 -0
  120. package/dist/{obligations.d.ts → render/obligations.d.ts} +1 -1
  121. package/dist/render/obligations.d.ts.map +1 -0
  122. package/dist/render/renderer.d.ts +38 -0
  123. package/dist/render/renderer.d.ts.map +1 -0
  124. package/dist/render/renderer.js +235 -0
  125. package/dist/render/renderer.js.map +1 -0
  126. package/dist/render/webgl.d.ts +14 -0
  127. package/dist/render/webgl.d.ts.map +1 -0
  128. package/dist/render/webgl.js +19 -0
  129. package/dist/render/webgl.js.map +1 -0
  130. package/dist/render/when-ready.d.ts.map +1 -0
  131. package/dist/render/when-ready.js.map +1 -0
  132. package/package.json +11 -21
  133. package/dist/adaptive.d.ts.map +0 -1
  134. package/dist/adaptive.js.map +0 -1
  135. package/dist/bounded.d.ts +0 -364
  136. package/dist/bounded.d.ts.map +0 -1
  137. package/dist/bounded.js +0 -18
  138. package/dist/bounded.js.map +0 -1
  139. package/dist/cluster-ring.d.ts +0 -25
  140. package/dist/cluster-ring.d.ts.map +0 -1
  141. package/dist/cluster-ring.js +0 -16
  142. package/dist/cluster-ring.js.map +0 -1
  143. package/dist/css-color.d.ts.map +0 -1
  144. package/dist/css-color.js.map +0 -1
  145. package/dist/duck-source.d.ts +0 -198
  146. package/dist/duck-source.d.ts.map +0 -1
  147. package/dist/duck-source.js +0 -320
  148. package/dist/duck-source.js.map +0 -1
  149. package/dist/graph-canvas.d.ts +0 -50
  150. package/dist/graph-canvas.d.ts.map +0 -1
  151. package/dist/graph-canvas.js +0 -35
  152. package/dist/graph-canvas.js.map +0 -1
  153. package/dist/graph-looks.d.ts.map +0 -1
  154. package/dist/graph-looks.js.map +0 -1
  155. package/dist/graph-model.d.ts +0 -130
  156. package/dist/graph-model.d.ts.map +0 -1
  157. package/dist/graph-model.js +0 -104
  158. package/dist/graph-model.js.map +0 -1
  159. package/dist/graph-sim.d.ts.map +0 -1
  160. package/dist/graph-sim.js.map +0 -1
  161. package/dist/obligations.d.ts.map +0 -1
  162. package/dist/resident.d.ts.map +0 -1
  163. package/dist/resident.js +0 -44
  164. package/dist/resident.js.map +0 -1
  165. package/dist/shape-glyph.d.ts.map +0 -1
  166. package/dist/shape-glyph.js.map +0 -1
  167. package/dist/slice-client.d.ts +0 -78
  168. package/dist/slice-client.d.ts.map +0 -1
  169. package/dist/slice-client.js +0 -98
  170. package/dist/slice-client.js.map +0 -1
  171. package/dist/types.d.ts.map +0 -1
  172. package/dist/use-graph-look.d.ts +0 -28
  173. package/dist/use-graph-look.d.ts.map +0 -1
  174. package/dist/use-graph-look.js +0 -28
  175. package/dist/use-graph-look.js.map +0 -1
  176. package/dist/use-graph-overlays.d.ts +0 -44
  177. package/dist/use-graph-overlays.d.ts.map +0 -1
  178. package/dist/use-graph-overlays.js.map +0 -1
  179. package/dist/use-graph-prefs.d.ts.map +0 -1
  180. package/dist/use-graph-prefs.js.map +0 -1
  181. package/dist/use-graph-selection.d.ts.map +0 -1
  182. package/dist/use-graph-selection.js.map +0 -1
  183. package/dist/use-graph.d.ts +0 -203
  184. package/dist/use-graph.d.ts.map +0 -1
  185. package/dist/use-graph.js +0 -102
  186. package/dist/use-graph.js.map +0 -1
  187. package/dist/use-query-loop.d.ts +0 -88
  188. package/dist/use-query-loop.d.ts.map +0 -1
  189. package/dist/use-query-loop.js +0 -148
  190. package/dist/use-query-loop.js.map +0 -1
  191. package/dist/use-renderer.d.ts +0 -86
  192. package/dist/use-renderer.d.ts.map +0 -1
  193. package/dist/use-renderer.js +0 -188
  194. package/dist/use-renderer.js.map +0 -1
  195. package/dist/when-ready.d.ts.map +0 -1
  196. package/dist/when-ready.js.map +0 -1
  197. /package/dist/{adaptive.js → render/adaptive.js} +0 -0
  198. /package/dist/{css-color.d.ts → render/css-color.d.ts} +0 -0
  199. /package/dist/{css-color.js → render/css-color.js} +0 -0
  200. /package/dist/{graph-looks.d.ts → render/graph-looks.d.ts} +0 -0
  201. /package/dist/{graph-looks.js → render/graph-looks.js} +0 -0
  202. /package/dist/{graph-sim.js → render/graph-sim.js} +0 -0
  203. /package/dist/{when-ready.d.ts → render/when-ready.d.ts} +0 -0
  204. /package/dist/{when-ready.js → render/when-ready.js} +0 -0
package/dist/resident.js DELETED
@@ -1,44 +0,0 @@
1
- const v = 32n, x = 0xffffffffn;
2
- function a(t, o) {
3
- return BigInt(t) << v | BigInt(o);
4
- }
5
- function g(t) {
6
- return Number(t >> v);
7
- }
8
- function h(t) {
9
- return Number(t & x);
10
- }
11
- const O = new BigUint64Array(0);
12
- function p(t) {
13
- const o = (t == null ? void 0 : t.vertices) ?? O, f = Math.min((t == null ? void 0 : t.marks) ?? o.length, o.length), u = o, i = /* @__PURE__ */ new Map();
14
- for (let n = 0; n < f; n++) i.set(u[n], n);
15
- const c = (n) => n >= 0 && n < f ? u[n] : void 0, d = (n) => i.get(n);
16
- return {
17
- size: f,
18
- indexOf: d,
19
- at: c,
20
- indicesOf(n) {
21
- const e = [];
22
- for (const s of n) {
23
- const r = d(s);
24
- r !== void 0 && e.push(r);
25
- }
26
- return e;
27
- },
28
- verticesAt(n) {
29
- const e = [];
30
- for (const s of n) {
31
- const r = c(s);
32
- r !== void 0 && e.push(r);
33
- }
34
- return e;
35
- }
36
- };
37
- }
38
- export {
39
- h as denseOf,
40
- p as residentOf,
41
- g as typeOf,
42
- a as vertexId
43
- };
44
- //# sourceMappingURL=resident.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"resident.js","sources":["../src/resident.ts"],"sourcesContent":["import type { Slice } from \"./bounded\";\n\n/**\n * Who is drawn, and where the GPU is drawing them.\n *\n * **A buffer index numbers the answer, not the corpus.** cosmos.gl addresses every point by its\n * position in the arrays it was last handed, so index 7 is whatever the current answer put seventh —\n * and a resident set that comes and goes reuses every index while the vertices behind them change.\n * Anything that outlives one answer — a selection, a label, a hover, a pin — therefore has to be\n * held as an identity and re-resolved against whatever is drawn now.\n *\n * **A vertex is the pair `(type_idx, dense_id)`.** Not `dense_id` alone: it numbers\n * within one vertex type, so a union of two types repeats every value and the same number names two\n * different vertices. A `LIMIT` breaks the correspondence between an id and a position regardless of\n * how many types there are.\n */\n\n/**\n * A vertex identity — the pair, packed into one 64-bit integer so it can key a `Map` and a `Set`.\n *\n * **A `bigint`, which is strictly stronger than the brand it also wears.** A buffer index is a\n * `number`; an identity is a `bigint`. Confusing the two therefore stops being a branding\n * convention that holds while everyone remembers it and becomes a *primitive* type error — and no\n * cast quietly launders one: `7 as VertexId` was legal against `number & brand` and is a compile\n * error against `bigint & brand`. Getting one wrong now costs `as unknown as`, which is loud.\n *\n * **The evidence this is worth the friction, because it is not a precaution we invented.** Across\n * every multi-language format surveyed — Arrow, Parquet, Iceberg, Delta, MVT, PMTiles, Zarr,\n * GraphAr, H3, S2 — the failure that recurs is a 64-bit value crossing into JavaScript.\n * `mapbox/node-s2` is a **binding**, not a port: it calls the same C++ the reference implementation\n * does, and it still returned wrong cell ids — issue #92, open since 2017, `1152921504606847000`\n * where Java and Go both give `1152921504606846977`. JavaScript's `number` is 53 bits and a cell id\n * is 64, and a binding cannot protect a language boundary that cannot hold the value. H3 settled it\n * by decree before anyone could get it wrong: `h3-js` types `H3Index` as a string. This is the same\n * decree with the type JavaScript grew for it.\n *\n * **And `>>` means three different things across our three layers.** In JavaScript it converts its\n * operand to *32 bits* and takes the shift count modulo 32, so the obvious `dense | (type << 32)`\n * is not a lost high word — it is `type | dense`, silently. The packing below cannot be written on\n * `number` at all and be right.\n *\n * What goes to the GPU does not change: positions and buffer indices stay `number` and\n * `Float32Array`. The conversion happens at the edge, which is where it belongs.\n */\nexport type VertexId = bigint & { readonly vertex: unique symbol };\n\n/**\n * One type's worth of dense ids.\n *\n * `dense_id` is a `UInt32`, so the type index occupies everything above bit 32 — exactly, for all\n * 2³² of them, which is the whole point of the width. The old packing was `type * 2**32 + dense` in\n * `float64` and ran out of exactness at type index 2²¹.\n */\nconst TYPE_SHIFT = 32n;\nconst DENSE_MASK = 0xffff_ffffn;\n\nexport function vertexId(type: number, dense: number): VertexId {\n return ((BigInt(type) << TYPE_SHIFT) | BigInt(dense)) as VertexId;\n}\n\n/** Both halves come back as `number`: each is 32 bits, and a `number` holds those exactly. */\nexport function typeOf(vertex: VertexId): number {\n return Number(vertex >> TYPE_SHIFT);\n}\n\nexport function denseOf(vertex: VertexId): number {\n return Number(vertex & DENSE_MASK);\n}\n\n/**\n * The identity↔index map for whatever is drawn right now.\n *\n * Built once per residency change and never mutated, because that is what a residency change *is*: a\n * different set of vertices, in a different order. It belongs to whoever owns residency — that is\n * `useQueryLoop`, which holds the answer — and is read from there by everything else, because a\n * second copy is a second copy that can disagree with the buffers on screen.\n */\nexport interface Resident {\n /** How many points are drawn. */\n readonly size: number;\n /** Where a vertex is drawn, or `undefined` when it is not resident. */\n indexOf(vertex: VertexId): number | undefined;\n /** Which vertex is drawn at a buffer index, or `undefined` past the end of the answer. */\n at(index: number): VertexId | undefined;\n /** Where these vertices are drawn, skipping every one that is not resident. */\n indicesOf(vertices: Iterable<VertexId>): number[];\n /** Which vertices are drawn at these buffer indices, skipping any the answer does not hold. */\n verticesAt(indices: Iterable<number>): VertexId[];\n}\n\nconst NOBODY = new BigUint64Array(0);\n\n/**\n * The map an answer implies. `null` — before the first answer — is nobody resident, not an error.\n *\n * Keyed by the identity itself. `Map` and `Set` compare keys by SameValueZero, which for a `bigint`\n * is *value* equality and not reference equality — two separately-constructed `vertexId(0, 5)`\n * reach the same entry. That is asserted rather than assumed in `resident.test.ts`, because the\n * whole file rests on it and BigInt being an object-shaped primitive makes it a fair thing to doubt.\n */\nexport function residentOf(slice: Slice | null): Resident {\n const all = slice?.vertices ?? NOBODY;\n /**\n * The marks, and not the anchors past them.\n *\n * An anchor is a real vertex in the buffers at its real coordinates, there so an edge leaving the\n * window has an end — and it is never drawn. Residency is *what is drawn*, so it stops here: a\n * hover, a selection or a frame that could land on an anchor would be pointing at a vertex with no\n * ink, off screen, that the window deliberately did not return.\n */\n const size = Math.min(slice?.marks ?? all.length, all.length);\n const vertices = all;\n const index = new Map<bigint, number>();\n for (let i = 0; i < size; i++) index.set(vertices[i] as bigint, i);\n\n const at = (i: number): VertexId | undefined =>\n i >= 0 && i < size ? (vertices[i] as VertexId) : undefined;\n const indexOf = (vertex: VertexId): number | undefined => index.get(vertex);\n\n return {\n size,\n indexOf,\n at,\n indicesOf(wanted) {\n const found: number[] = [];\n for (const vertex of wanted) {\n const i = indexOf(vertex);\n if (i !== undefined) found.push(i);\n }\n return found;\n },\n verticesAt(indices) {\n const found: VertexId[] = [];\n for (const i of indices) {\n const vertex = at(i);\n if (vertex !== undefined) found.push(vertex);\n }\n return found;\n },\n };\n}\n"],"names":["TYPE_SHIFT","DENSE_MASK","vertexId","type","dense","typeOf","vertex","denseOf","NOBODY","residentOf","slice","all","size","vertices","index","i","at","indexOf","wanted","found","indices"],"mappings":"AAqDA,MAAMA,IAAa,KACbC,IAAa;AAEZ,SAASC,EAASC,GAAcC,GAAyB;AAC9D,SAAS,OAAOD,CAAI,KAAKH,IAAc,OAAOI,CAAK;AACrD;AAGO,SAASC,EAAOC,GAA0B;AAC/C,SAAO,OAAOA,KAAUN,CAAU;AACpC;AAEO,SAASO,EAAQD,GAA0B;AAChD,SAAO,OAAOA,IAASL,CAAU;AACnC;AAuBA,MAAMO,IAAS,IAAI,eAAe,CAAC;AAU5B,SAASC,EAAWC,GAA+B;AACxD,QAAMC,KAAMD,KAAA,gBAAAA,EAAO,aAAYF,GASzBI,IAAO,KAAK,KAAIF,KAAA,gBAAAA,EAAO,UAASC,EAAI,QAAQA,EAAI,MAAM,GACtDE,IAAWF,GACXG,wBAAY,IAAA;AAClB,WAASC,IAAI,GAAGA,IAAIH,GAAMG,OAAW,IAAIF,EAASE,CAAC,GAAaA,CAAC;AAEjE,QAAMC,IAAK,CAACD,MACVA,KAAK,KAAKA,IAAIH,IAAQC,EAASE,CAAC,IAAiB,QAC7CE,IAAU,CAACX,MAAyCQ,EAAM,IAAIR,CAAM;AAE1E,SAAO;AAAA,IACL,MAAAM;AAAA,IACA,SAAAK;AAAA,IACA,IAAAD;AAAA,IACA,UAAUE,GAAQ;AAChB,YAAMC,IAAkB,CAAA;AACxB,iBAAWb,KAAUY,GAAQ;AAC3B,cAAMH,IAAIE,EAAQX,CAAM;AACxB,QAAIS,MAAM,UAAWI,EAAM,KAAKJ,CAAC;AAAA,MACnC;AACA,aAAOI;AAAA,IACT;AAAA,IACA,WAAWC,GAAS;AAClB,YAAMD,IAAoB,CAAA;AAC1B,iBAAWJ,KAAKK,GAAS;AACvB,cAAMd,IAASU,EAAGD,CAAC;AACnB,QAAIT,MAAW,UAAWa,EAAM,KAAKb,CAAM;AAAA,MAC7C;AACA,aAAOa;AAAA,IACT;AAAA,EAAA;AAEJ;"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"shape-glyph.d.ts","sourceRoot":"","sources":["../src/shape-glyph.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,KAAK,EAAE,MAAM,eAAe,CAAC;AAEvD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,eAAgB,SAAQ,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC;IACpE,yEAAyE;IACzE,KAAK,EAAE,KAAK,CAAC;IACb;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,UAAU,CAAC,EAAE,KAAsB,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,EAAE,eAAe,+BAUrF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"shape-glyph.js","sources":["../src/shape-glyph.tsx"],"sourcesContent":["import { SHAPE_PATH, type Shape } from \"./graph-looks\";\n\n/**\n * The glyph the canvas draws for a category, in the DOM — a legend key, a hover card, a preview.\n *\n * **This replaced `SHAPE_PATH` on the barrel, and the difference is what a host is made responsible\n * for.** The path table was exported for one reason: a host drawing a legend wanted the same glyph\n * the point shader fills, because a legend drawn from a second set of shapes is a legend that\n * explains a picture nobody is looking at. What the host got was path *data*, and with it three\n * facts it had to keep in step with `graph-looks.ts` by reading a comment — that the box is twelve\n * units, that the path wants a `fill` rather than a `stroke`, and which enum index each glyph is\n * keyed by. Every one of those was silent when it drifted.\n *\n * A component carries all three and cannot disagree with itself. What is left at the call site is\n * the only thing the host actually decides: how big, and what colour.\n *\n * **No `\"use client\"`, deliberately.** It is a function of its props with no state, no effect and no\n * event handler, so it renders on a server the way any other markup does. The rest of this package\n * is client-only because a WebGL context is; a `<path>` is not.\n */\nexport interface ShapeGlyphProps extends React.SVGProps<SVGSVGElement> {\n /** Which glyph — the name `scaleOf(...).shape(ordinal)` answers with. */\n shape: Shape;\n /**\n * What to fill it with. Defaults to `currentColor`, so a glyph inside a legend row inherits the\n * row's colour and a host that is colouring by category passes `scaleOf(...).color(ordinal)`.\n */\n color?: string;\n}\n\nexport function ShapeGlyph({ color = \"currentColor\", shape, ...rest }: ShapeGlyphProps) {\n return (\n // The `viewBox` is the glyph's own box and is not a prop: the paths are authored inside a 12×12\n // square, which is the fact the host used to have to know. Size it from outside — a `className`,\n // a `width`/`height`, or `x`/`y`/`width`/`height` when this is nested inside another `<svg>`,\n // which is how a preview places a glyph at a vertex.\n <svg viewBox=\"0 0 12 12\" {...rest}>\n <path d={SHAPE_PATH[shape]} fill={color} />\n </svg>\n );\n}\n"],"names":["ShapeGlyph","color","shape","rest","jsx","SHAPE_PATH"],"mappings":";;AA8BO,SAASA,EAAW,EAAE,OAAAC,IAAQ,gBAAgB,OAAAC,GAAO,GAAGC,KAAyB;AACtF;AAAA;AAAA;AAAA;AAAA;AAAA,IAKE,gBAAAC,EAAC,OAAA,EAAI,SAAQ,aAAa,GAAGD,GAC3B,UAAA,gBAAAC,EAAC,QAAA,EAAK,GAAGC,EAAWH,CAAK,GAAG,MAAMD,GAAO,EAAA,CAC3C;AAAA;AAEJ;"}
@@ -1,78 +0,0 @@
1
- import { MosaicClient, Coordinator, FilterExpr, Selection } from '@kanzo-tech/mosaic';
2
- /**
3
- * One read of a corpus, as a client of the page's coordinator.
4
- *
5
- * **A source used to query outside the protocol, and that was the whole defect.** `onceQuery`
6
- * connected a throwaway `MosaicClient` per query, took the answer and disconnected — a second query
7
- * path beside the one every chart on the page uses, and one that no selection could reach. So a
8
- * graph inside a crossfilter had to be joined to it from outside: something else asked which ids
9
- * survived, and the canvas painted grey over the ones that had not. Two round trips, a full scan and
10
- * a texture upload, to express a `WHERE` clause.
11
- *
12
- * A read is the same protocol a histogram uses, in the two directions it runs:
13
- *
14
- * `query(filter)` — the coordinator hands us the page's predicate and we build the SQL around it,
15
- * so what comes back is *what survives* rather than everything with a mask beside it.
16
- * `queryResult(data)` — the answer, whether we asked for it or the page's filters moved.
17
- *
18
- * **The second direction is the one that pays.** A selection change re-runs this read with the new
19
- * predicate and no camera involvement at all: the coordinator already walks its clients on every
20
- * selection update, so a graph that is one of them is re-queried the way a plot is. Nothing
21
- * subscribes to anything, and there is no window where the picture and the page's filters disagree.
22
- *
23
- * **Not `makeClient`.** That helper takes a `query` and a `queryResult` as options and is right for a
24
- * client with one standing question. A slice is two reads that have to arrive together, over a
25
- * question the camera rewrites — so what is needed is a handle the source can re-aim, which is a
26
- * class with a method rather than a closure fixed at construction.
27
- */
28
- export declare class SliceRead extends MosaicClient {
29
- #private;
30
- /**
31
- * @param filterBy The crossfilter this read lives inside, if any. The unfiltered reads — how big
32
- * the corpus is, where it is, what its tile footers say — take none: those are facts about the
33
- * corpus rather than about what the page is looking at, and filtering them would make
34
- * "20,000 of 1,000,000" a fraction of itself.
35
- */
36
- constructor(coordinator: Coordinator, filterBy?: Selection);
37
- /**
38
- * Pre-aggregation cannot help here, and saying so costs a getter instead of a query.
39
- *
40
- * `preaggColumns` reaches the same "no" by calling `query()` and finding a string where it wanted a
41
- * `SelectQuery` — a slice is a CTE over window functions and is not expressible in the builder —
42
- * but it calls `query()` to find out, on every selection change. The claim is true rather than
43
- * defensive: the filter decides which rows are numbered, so it moves the groupby domain outright,
44
- * which is exactly what this flag is asked about.
45
- */
46
- get filterStable(): boolean;
47
- /**
48
- * What the rest of the page is filtering by, right now.
49
- *
50
- * The same value the coordinator would hand `query()`. A source that needs the predicate *before*
51
- * it can build SQL — because it has to know which relation to point at, or because it is assembling
52
- * one answer out of two reads — asks here rather than reaching into the selection and re-deriving
53
- * the client exemption by hand.
54
- */
55
- get predicate(): FilterExpr;
56
- /**
57
- * Ask, and *keep* the question. The coordinator issues, consolidates, caches and re-runs it.
58
- *
59
- * `build` is retained rather than consumed, because it is the standing question: after the page
60
- * filters something the camera is still over the same rectangle, so the coordinator re-running this
61
- * with a new predicate is precisely the query anybody would have written by hand.
62
- */
63
- ask(build: (filter: FilterExpr) => string): Promise<unknown>;
64
- /**
65
- * Where an answer goes when nobody asked for it — which is the page's filters moving.
66
- *
67
- * Told apart from a pull by which of the two is outstanding rather than by a flag, so the two
68
- * cannot disagree: an answer settles the promise when one is waiting, and is reported here when
69
- * none is.
70
- */
71
- set onAnswer(handle: ((data: unknown) => void) | null);
72
- /** Let go: no more queries, and the coordinator stops walking us on every selection change. */
73
- release(): void;
74
- query(filter?: FilterExpr): string | null;
75
- queryResult(data: unknown): this;
76
- queryError(error: Error): this;
77
- }
78
- //# sourceMappingURL=slice-client.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"slice-client.d.ts","sourceRoot":"","sources":["../src/slice-client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAG7E;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAAa,SAAU,SAAQ,YAAY;;IAOzC;;;;;OAKG;gBACS,WAAW,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,SAAS;IAK1D;;;;;;;;OAQG;IACH,IAAa,YAAY,IAAI,OAAO,CAEnC;IAED;;;;;;;OAOG;IACH,IAAI,SAAS,IAAI,UAAU,CAE1B;IAED;;;;;;OAMG;IACH,GAAG,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA4B5D;;;;;;OAMG;IACH,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC,GAAG,IAAI,EAEpD;IAED,+FAA+F;IAC/F,OAAO,IAAI,IAAI;IAWN,KAAK,CAAC,MAAM,GAAE,UAAe,GAAG,MAAM,GAAG,IAAI;IAI7C,WAAW,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IAQhC,UAAU,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;CAMxC"}
@@ -1,98 +0,0 @@
1
- "use client";
2
- var p = (t) => {
3
- throw TypeError(t);
4
- };
5
- var m = (t, r, e) => r.has(t) || p("Cannot " + e);
6
- var l = (t, r, e) => (m(t, r, "read from private field"), e ? e.call(t) : r.get(t)), o = (t, r, e) => r.has(t) ? p("Cannot add the same private member more than once") : r instanceof WeakSet ? r.add(t) : r.set(t, e), i = (t, r, e, s) => (m(t, r, "write to private field"), s ? s.call(t, e) : r.set(t, e), e);
7
- import { MosaicClient as y } from "@kanzo-tech/mosaic";
8
- import { abortError as w } from "./bounded.js";
9
- var u, c, n, a, h;
10
- class x extends y {
11
- /**
12
- * @param filterBy The crossfilter this read lives inside, if any. The unfiltered reads — how big
13
- * the corpus is, where it is, what its tile footers say — take none: those are facts about the
14
- * corpus rather than about what the page is looking at, and filtering them would make
15
- * "20,000 of 1,000,000" a fraction of itself.
16
- */
17
- constructor(e, s) {
18
- super(s);
19
- o(this, u);
20
- o(this, c, null);
21
- o(this, n, null);
22
- o(this, a, null);
23
- o(this, h, !1);
24
- i(this, u, e);
25
- }
26
- /**
27
- * Pre-aggregation cannot help here, and saying so costs a getter instead of a query.
28
- *
29
- * `preaggColumns` reaches the same "no" by calling `query()` and finding a string where it wanted a
30
- * `SelectQuery` — a slice is a CTE over window functions and is not expressible in the builder —
31
- * but it calls `query()` to find out, on every selection change. The claim is true rather than
32
- * defensive: the filter decides which rows are numbered, so it moves the groupby domain outright,
33
- * which is exactly what this flag is asked about.
34
- */
35
- get filterStable() {
36
- return !1;
37
- }
38
- /**
39
- * What the rest of the page is filtering by, right now.
40
- *
41
- * The same value the coordinator would hand `query()`. A source that needs the predicate *before*
42
- * it can build SQL — because it has to know which relation to point at, or because it is assembling
43
- * one answer out of two reads — asks here rather than reaching into the selection and re-deriving
44
- * the client exemption by hand.
45
- */
46
- get predicate() {
47
- var e;
48
- return ((e = this.filterBy) == null ? void 0 : e.predicate(this)) ?? [];
49
- }
50
- /**
51
- * Ask, and *keep* the question. The coordinator issues, consolidates, caches and re-runs it.
52
- *
53
- * `build` is retained rather than consumed, because it is the standing question: after the page
54
- * filters something the camera is still over the same rectangle, so the coordinator re-running this
55
- * with a new predicate is precisely the query anybody would have written by hand.
56
- */
57
- ask(e) {
58
- i(this, c, e);
59
- const s = new Promise((d, q) => {
60
- var f;
61
- (f = l(this, n)) == null || f.reject(w("superseded: this read was re-aimed at a newer question")), i(this, n, { resolve: d, reject: q });
62
- });
63
- return l(this, h) ? this.requestQuery() : (i(this, h, !0), l(this, u).connect(this)), s;
64
- }
65
- /**
66
- * Where an answer goes when nobody asked for it — which is the page's filters moving.
67
- *
68
- * Told apart from a pull by which of the two is outstanding rather than by a flag, so the two
69
- * cannot disagree: an answer settles the promise when one is waiting, and is reported here when
70
- * none is.
71
- */
72
- set onAnswer(e) {
73
- i(this, a, e);
74
- }
75
- /** Let go: no more queries, and the coordinator stops walking us on every selection change. */
76
- release() {
77
- var e;
78
- (e = l(this, n)) == null || e.reject(w("released: this read let go of the coordinator")), i(this, n, null), i(this, a, null), i(this, c, null), l(this, h) && (i(this, h, !1), l(this, u).disconnect(this));
79
- }
80
- query(e = []) {
81
- var s;
82
- return ((s = l(this, c)) == null ? void 0 : s.call(this, e)) ?? null;
83
- }
84
- queryResult(e) {
85
- var d;
86
- const s = l(this, n);
87
- return i(this, n, null), s ? s.resolve(e) : (d = l(this, a)) == null || d.call(this, e), this;
88
- }
89
- queryError(e) {
90
- const s = l(this, n);
91
- return i(this, n, null), s && s.reject(e), this;
92
- }
93
- }
94
- u = new WeakMap(), c = new WeakMap(), n = new WeakMap(), a = new WeakMap(), h = new WeakMap();
95
- export {
96
- x as SliceRead
97
- };
98
- //# sourceMappingURL=slice-client.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"slice-client.js","sources":["../src/slice-client.ts"],"sourcesContent":["\"use client\";\n\nimport { MosaicClient } from \"@kanzo-tech/mosaic\";\nimport type { Coordinator, FilterExpr, Selection } from \"@kanzo-tech/mosaic\";\nimport { abortError } from \"./bounded\";\n\n/**\n * One read of a corpus, as a client of the page's coordinator.\n *\n * **A source used to query outside the protocol, and that was the whole defect.** `onceQuery`\n * connected a throwaway `MosaicClient` per query, took the answer and disconnected — a second query\n * path beside the one every chart on the page uses, and one that no selection could reach. So a\n * graph inside a crossfilter had to be joined to it from outside: something else asked which ids\n * survived, and the canvas painted grey over the ones that had not. Two round trips, a full scan and\n * a texture upload, to express a `WHERE` clause.\n *\n * A read is the same protocol a histogram uses, in the two directions it runs:\n *\n * `query(filter)` — the coordinator hands us the page's predicate and we build the SQL around it,\n * so what comes back is *what survives* rather than everything with a mask beside it.\n * `queryResult(data)` — the answer, whether we asked for it or the page's filters moved.\n *\n * **The second direction is the one that pays.** A selection change re-runs this read with the new\n * predicate and no camera involvement at all: the coordinator already walks its clients on every\n * selection update, so a graph that is one of them is re-queried the way a plot is. Nothing\n * subscribes to anything, and there is no window where the picture and the page's filters disagree.\n *\n * **Not `makeClient`.** That helper takes a `query` and a `queryResult` as options and is right for a\n * client with one standing question. A slice is two reads that have to arrive together, over a\n * question the camera rewrites — so what is needed is a handle the source can re-aim, which is a\n * class with a method rather than a closure fixed at construction.\n */\nexport class SliceRead extends MosaicClient {\n #coordinator: Coordinator;\n #build: ((filter: FilterExpr) => string) | null = null;\n #settle: { resolve: (data: unknown) => void; reject: (error: unknown) => void } | null = null;\n #onAnswer: ((data: unknown) => void) | null = null;\n #connected = false;\n\n /**\n * @param filterBy The crossfilter this read lives inside, if any. The unfiltered reads — how big\n * the corpus is, where it is, what its tile footers say — take none: those are facts about the\n * corpus rather than about what the page is looking at, and filtering them would make\n * \"20,000 of 1,000,000\" a fraction of itself.\n */\n constructor(coordinator: Coordinator, filterBy?: Selection) {\n super(filterBy);\n this.#coordinator = coordinator;\n }\n\n /**\n * Pre-aggregation cannot help here, and saying so costs a getter instead of a query.\n *\n * `preaggColumns` reaches the same \"no\" by calling `query()` and finding a string where it wanted a\n * `SelectQuery` — a slice is a CTE over window functions and is not expressible in the builder —\n * but it calls `query()` to find out, on every selection change. The claim is true rather than\n * defensive: the filter decides which rows are numbered, so it moves the groupby domain outright,\n * which is exactly what this flag is asked about.\n */\n override get filterStable(): boolean {\n return false;\n }\n\n /**\n * What the rest of the page is filtering by, right now.\n *\n * The same value the coordinator would hand `query()`. A source that needs the predicate *before*\n * it can build SQL — because it has to know which relation to point at, or because it is assembling\n * one answer out of two reads — asks here rather than reaching into the selection and re-deriving\n * the client exemption by hand.\n */\n get predicate(): FilterExpr {\n return this.filterBy?.predicate(this) ?? [];\n }\n\n /**\n * Ask, and *keep* the question. The coordinator issues, consolidates, caches and re-runs it.\n *\n * `build` is retained rather than consumed, because it is the standing question: after the page\n * filters something the camera is still over the same rectangle, so the coordinator re-running this\n * with a new predicate is precisely the query anybody would have written by hand.\n */\n ask(build: (filter: FilterExpr) => string): Promise<unknown> {\n this.#build = build;\n const answer = new Promise<unknown>((resolve, reject) => {\n // A superseded question is settled rather than dropped. The camera moves faster than DuckDB\n // answers, so the previous promise has a caller awaiting it; leaving it unsettled leaves that\n // caller's `finally` unrun and the loop reporting a query in flight for the rest of the session.\n //\n // An `AbortError`, because that is what it is: the older caller does not want this answer any\n // more, and there is no signal to reach for — nobody aborted it, a newer question overtook it.\n // It used to be a `SUPERSEDED` symbol of ours, which meant a caller had to import a name from\n // this package to tell a cancellation from a failure. `bounded.ts` carries that argument.\n this.#settle?.reject(abortError(\"superseded: this read was re-aimed at a newer question\"));\n this.#settle = { resolve, reject };\n });\n if (this.#connected) {\n this.requestQuery();\n } else {\n // Connected on first use rather than at construction, and that is what keeps a null question\n // out of the protocol: a connected client is one the coordinator may re-query on any selection\n // change, and before the first camera question there is nothing to re-query it *with*.\n // `connect` initializes, and initializing requests a query — so the opening read is issued by\n // the coordinator's own lifecycle rather than beside it.\n this.#connected = true;\n this.#coordinator.connect(this);\n }\n return answer;\n }\n\n /**\n * Where an answer goes when nobody asked for it — which is the page's filters moving.\n *\n * Told apart from a pull by which of the two is outstanding rather than by a flag, so the two\n * cannot disagree: an answer settles the promise when one is waiting, and is reported here when\n * none is.\n */\n set onAnswer(handle: ((data: unknown) => void) | null) {\n this.#onAnswer = handle;\n }\n\n /** Let go: no more queries, and the coordinator stops walking us on every selection change. */\n release(): void {\n this.#settle?.reject(abortError(\"released: this read let go of the coordinator\"));\n this.#settle = null;\n this.#onAnswer = null;\n this.#build = null;\n if (this.#connected) {\n this.#connected = false;\n this.#coordinator.disconnect(this);\n }\n }\n\n override query(filter: FilterExpr = []): string | null {\n return this.#build?.(filter) ?? null;\n }\n\n override queryResult(data: unknown): this {\n const settle = this.#settle;\n this.#settle = null;\n if (settle) settle.resolve(data);\n else this.#onAnswer?.(data);\n return this;\n }\n\n override queryError(error: Error): this {\n const settle = this.#settle;\n this.#settle = null;\n if (settle) settle.reject(error);\n return this;\n }\n}\n"],"names":["_coordinator"],"mappings":";;;;;;;;;AAgCO;AAAqC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAcxC;AAbF;AACA;AACA;AACA;AACA;AAUE;AAAoB;AACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYE;AAAO;AACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAWE;AAAyC;AAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUE;AACA;;AASE;AAC0B;AAE5B;AAWO;AACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUE;AAAiB;AACnB;AAAA;;AAIE;AAMmC;AAErC;;AAGE;AAAgC;AAClC;;AAGE;AACA;AAGO;AACT;AAGE;AACA;AAEO;AAEX;AAtHEA;;;;"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;;;;;GAMG;AACH,MAAM,MAAM,IAAI,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;AAE3C,mEAAmE;AACnE,MAAM,MAAM,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEtD;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AAE7E;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,SAAS;IACxB;;;;;;OAMG;IACH,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACrB,MAAM,EAAE,eAAe,CAAC;IACxB,gCAAgC;IAChC,KAAK,EAAE,MAAM,CAAC;CACf;AAED,yFAAyF;AACzF,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,GAAG,IAAI,IAAI,CAAC;IACZ,KAAK,IAAI,IAAI,CAAC;IACd,MAAM,IAAI,IAAI,CAAC;IACf,OAAO,IAAI,IAAI,CAAC;IAChB,iFAAiF;IACjF,KAAK,IAAI,IAAI,CAAC;IACd,oCAAoC;IACpC,MAAM,CAAC,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC/B,wDAAwD;IACxD,cAAc,IAAI,IAAI,CAAC;IACvB,KAAK,IAAI,IAAI,CAAC;CACf"}
@@ -1,28 +0,0 @@
1
- import { Graph } from '@cosmos.gl/graph';
2
- import { Slice } from './bounded';
3
- import { Channels } from './graph-model';
4
- import { Look } from './graph-looks';
5
- export declare function useGraphLook(options: {
6
- getGraph: () => Graph | null;
7
- /** The element the tokens are resolved against — inside the canvas' own tree. */
8
- hostRef: React.RefObject<HTMLElement | null>;
9
- /** The answer currently drawn. Every slice is a fresh set of points, so every slice repaints. */
10
- slice: Slice | null;
11
- look: Look;
12
- /**
13
- * What each channel is bound to.
14
- *
15
- * Only the bindings the *buffers* read arrive here — `fill` when it is a constant, `symbol`,
16
- * `stroke`. `fill` as a column name and `r` name columns a query has to fetch, so those reach the
17
- * source instead. One vocabulary at the call site, two destinations underneath.
18
- */
19
- channels?: Channels;
20
- /**
21
- * Ask the overlays to reposition: point sizes changed, so the labels sit differently.
22
- *
23
- * Optional, because it is only owed to `useGraphOverlays` — a host drawing no labels has no
24
- * overlays to reposition, and was writing a no-op to say so.
25
- */
26
- schedule?: () => void;
27
- }): void;
28
- //# sourceMappingURL=use-graph-look.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"use-graph-look.d.ts","sourceRoot":"","sources":["../src/use-graph-look.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAI9C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAuB,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AA+B1C,wBAAgB,YAAY,CAAC,OAAO,EAAE;IACpC,QAAQ,EAAE,MAAM,KAAK,GAAG,IAAI,CAAC;IAC7B,iFAAiF;IACjF,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC7C,iGAAiG;IACjG,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,IAAI,CAAC;IACX;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB,GAAG,IAAI,CAmCP"}
@@ -1,28 +0,0 @@
1
- "use client";
2
- import { useEffect as u } from "react";
3
- import { useThemeTick as C } from "@kanzo-tech/ui";
4
- import { buffers as P, appearance as T } from "./graph-model.js";
5
- import { whenReady as l } from "./when-ready.js";
6
- const z = () => {
7
- };
8
- function b(m) {
9
- const { channels: h, getGraph: s, hostRef: r, look: n, schedule: p = z, slice: o } = m, f = C();
10
- u(() => {
11
- const t = s(), e = r.current;
12
- if (!o || !t || !e) return;
13
- const { colors: c, linkColors: a, shapes: k, sizes: g } = P(o, n, e, h);
14
- return l(t, (i) => {
15
- i.setPointColors(c), i.setPointSizes(g), i.setPointShapes(k), i.setLinkColors(a);
16
- });
17
- }, [h, s, r, n, o, f]), u(() => {
18
- const t = s(), e = r.current;
19
- if (!(!o || !t || !e))
20
- return l(t, (c) => {
21
- c.setConfigPartial(T(n, e)), c.render(), p();
22
- });
23
- }, [s, r, n, p, o, f]);
24
- }
25
- export {
26
- b as useGraphLook
27
- };
28
- //# sourceMappingURL=use-graph-look.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"use-graph-look.js","sources":["../src/use-graph-look.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect } from \"react\";\nimport type { Graph } from \"@cosmos.gl/graph\";\n// The root barrel, not `/analytics`: `useThemeTick` moved there with the barrel review. Anything\n// that paints from tokens needs it, which is more surfaces than the chart half.\nimport { useThemeTick } from \"@kanzo-tech/ui\";\nimport type { Slice } from \"./bounded\";\nimport { appearance, buffers, type Channels } from \"./graph-model\";\nimport type { Look } from \"./graph-looks\";\nimport { whenReady } from \"./when-ready\";\n\n/**\n * Putting a look on the canvas, and keeping it there when the theme flips.\n *\n * Four buffer uploads and one `setConfig` — no query, no restart, no rebuilt graph. That is the\n * whole reason a look is data rather than a variant of the component: changing one costs the GPU a\n * few arrays and costs the database nothing.\n *\n * Two effects, not one, because those are two different costs. The buffers depend on the data, the\n * look and the theme; the config depends on the same three and is far cheaper. Fused, every tick of\n * the Edge opacity and Node size sliders rebuilt all four arrays and re-uploaded them: 41,440 bytes\n * on this corpus — 31,440 of buffer plus the 10,000-byte padded float texture cosmos.gl expands the\n * sizes into — and six forced style recalcs, because `buffers` resolves every token off the live\n * DOM. All of it to move two scalars the GPU reads from a uniform.\n *\n * Those two sliders are gone — see `lookFrom` — but the split stays, and it stays for what is left:\n * `renderLinks` and `linkOpacity` are uniforms, so turning the edge layer off uploads nothing.\n *\n * The theme half is the part that is easy to forget. A look names its colours as `var(--chart-1)`,\n * and those tokens change under the reader — the `.dark` flip, and a tenant's palette document\n * swapped underneath — without React having any reason to re-render. `useThemeTick` watches\n * `<html>` for exactly that.\n *\n * It is the library's, not a local copy. The copy this replaced filtered attributes down to\n * `class`, `style` and `data-theme`, so any axis added after it was written stopped repainting the\n * graph — a filter is a list of the axes that existed the day someone typed it.\n */\nconst noop = () => {};\n\nexport function useGraphLook(options: {\n getGraph: () => Graph | null;\n /** The element the tokens are resolved against — inside the canvas' own tree. */\n hostRef: React.RefObject<HTMLElement | null>;\n /** The answer currently drawn. Every slice is a fresh set of points, so every slice repaints. */\n slice: Slice | null;\n look: Look;\n /**\n * What each channel is bound to.\n *\n * Only the bindings the *buffers* read arrive here — `fill` when it is a constant, `symbol`,\n * `stroke`. `fill` as a column name and `r` name columns a query has to fetch, so those reach the\n * source instead. One vocabulary at the call site, two destinations underneath.\n */\n channels?: Channels;\n /**\n * Ask the overlays to reposition: point sizes changed, so the labels sit differently.\n *\n * Optional, because it is only owed to `useGraphOverlays` — a host drawing no labels has no\n * overlays to reposition, and was writing a no-op to say so.\n */\n schedule?: () => void;\n}): void {\n const { channels, getGraph, hostRef, look, schedule = noop, slice } = options;\n const themeTick = useThemeTick();\n\n useEffect(() => {\n const graph = getGraph();\n const host = hostRef.current;\n if (!slice || !graph || !host) return;\n const { colors, linkColors, shapes, sizes } = buffers(slice, look, host, channels);\n // **This is the quiet half of the readiness race and the one that cost the most to find.**\n // These four raise a dirty flag and return, so a call before the device exists is not an error\n // anywhere — it is simply a picture that never gets uploaded. Geometry can arrive correctly,\n // the badge can report a full slice, and every point is still drawn at no size in no colour.\n return whenReady(graph, (ready) => {\n ready.setPointColors(colors);\n ready.setPointSizes(sizes);\n ready.setPointShapes(shapes);\n ready.setLinkColors(linkColors);\n });\n }, [channels, getGraph, hostRef, look, slice, themeTick]);\n\n // The paint, once, for both. These deps are a superset of the ones above, so whenever the buffers\n // are rebuilt this runs in the same commit and right after — and `setPointColors` and friends only\n // raise a dirty flag, which `render()` is what discharges. Painting in both would mean two\n // `graph.update()` passes (a full re-derivation, adjacency lists included) for one change.\n useEffect(() => {\n const graph = getGraph();\n const host = hostRef.current;\n if (!slice || !graph || !host) return;\n return whenReady(graph, (ready) => {\n ready.setConfigPartial(appearance(look, host));\n ready.render();\n schedule();\n });\n }, [getGraph, hostRef, look, schedule, slice, themeTick]);\n}\n"],"names":[],"mappings":";;;;;AAsCA;AAAoB;AAEb;AAuBL;AAGA;AACE;AAEA;AACA;AAKA;AACE;AAG8B;AAC/B;AAQD;AAEA;AACA;AACE;AAEA;AACD;AAEL;;;;"}
@@ -1,44 +0,0 @@
1
- import { VertexId } from './resident';
2
- import { GraphApi } from './use-graph';
3
- export interface GraphOverlays {
4
- /** The box the overlays are positioned within — the canvas' own bounds. */
5
- hostRef: React.RefObject<HTMLDivElement | null>;
6
- gridRef: React.RefObject<HTMLDivElement | null>;
7
- cardRef: React.RefObject<HTMLDivElement | null>;
8
- /** A `ref` callback for a given vertex's label. */
9
- labelRef: (vertex: VertexId) => (element: HTMLElement | null) => void;
10
- /** The labelled vertices, in the order the declutter pass should place them. */
11
- setLabelOrder: (vertices: VertexId[]) => void;
12
- setHovered: (vertex: VertexId | null) => void;
13
- /**
14
- * Re-register the tracked points with cosmos.gl.
15
- *
16
- * Call it after the graph exists and whenever the set of overlaid nodes changes. It has to be
17
- * driven from outside because effects run in declaration order, so this hook's own effects cannot
18
- * see a graph that a later hook is about to construct — and that construction is exactly what
19
- * clears the registration.
20
- */
21
- track: () => void;
22
- /** Ask for a repaint. Coalesced — many calls in a frame cost one. */
23
- schedule: () => void;
24
- }
25
- /**
26
- * **The api, not two getters off it.**
27
- *
28
- * This took `{ getGraph, getResident }` — an options object whose two members were copied out of
29
- * `GraphApi` — and that shape is the reason `getGraph` and `getResident` are on the api at all
30
- * beside the `slice` and `resident` values it already publishes. A host composing the two wrote the
31
- * hook's argument by hand out of the object it had just been given, which is a re-statement rather
32
- * than a decision: there is no useful call where the two come from different graphs.
33
- *
34
- * `GraphOverlayOptions` went with it. It named a shape a caller had to assemble, and what a caller
35
- * has is the api.
36
- *
37
- * **The ordering follows, and it is the honest one.** This has to be called *after* `useGraph`,
38
- * because it now takes what `useGraph` returns. The other half of the cycle — a look change owes the
39
- * overlays a repaint, and a look change does not tick — stays where it was: `useGraph` takes a
40
- * `schedule` callback, and a host bridges the two with one ref. One indirection, in the direction
41
- * that genuinely needs one, instead of two accessors threaded around an object the host is holding.
42
- */
43
- export declare function useGraphOverlays(api: GraphApi): GraphOverlays;
44
- //# sourceMappingURL=use-graph-overlays.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"use-graph-overlays.d.ts","sourceRoot":"","sources":["../src/use-graph-overlays.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AA8C5C,MAAM,WAAW,aAAa;IAC5B,2EAA2E;IAC3E,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAChD,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAChD,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAChD,mDAAmD;IACnD,QAAQ,EAAE,CAAC,MAAM,EAAE,QAAQ,KAAK,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;IACtE,gFAAgF;IAChF,aAAa,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAC;IAC9C,UAAU,EAAE,CAAC,MAAM,EAAE,QAAQ,GAAG,IAAI,KAAK,IAAI,CAAC;IAC9C;;;;;;;OAOG;IACH,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,qEAAqE;IACrE,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,QAAQ,GAAG,aAAa,CA+N7D"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"use-graph-overlays.js","sources":["../src/use-graph-overlays.ts"],"sourcesContent":["\"use client\";\n\nimport { useCallback, useEffect, useRef } from \"react\";\nimport type { VertexId } from \"./resident\";\nimport type { GraphApi } from \"./use-graph\";\nimport { whenReady } from \"./when-ready\";\n\n/**\n * Everything that floats over the canvas and has to keep up with it: the hub labels, the hover\n * card, and the grid's lock to the graph's own space.\n *\n * One rAF for all three, because they answer the same question — *where is the camera now* — and\n * three independent loops would read the same transform three times a frame. React never runs: the\n * overlays move by imperative style writes, and a re-render per frame would be a re-render per\n * frame.\n *\n * **An overlay is attached to a vertex, not to a slot.** Everything here outlives an answer — a\n * label element is kept across renders, a hover survives a query — so the tracked set is identities\n * and the buffer index is resolved through `Resident` at the moment of painting. Held as indices, a\n * label would keep its position and change which node it was naming the first time the resident set\n * moved, with the text and the dot disagreeing and nothing raised.\n *\n * This lived inside the canvas component among seven other concerns, and that is not a filing\n * detail: the scheduler below once kept a cancelled `requestAnimationFrame` handle in `frame`,\n * which silently disabled every overlay for the life of the page. It took a long time to find in a\n * 700-line component and would have been obvious here.\n */\n\n/**\n * Dot spacing at zoom 1. The painter keeps the on-screen spacing inside [GRID, 2·GRID).\n *\n * **Not on the barrel any more.** Both call sites that imported it wrote the same line —\n * `backgroundSize: \\`${GRID}px ${GRID}px\\`` — as the *initial* value of a style `paint` overwrites\n * on its first frame. So the number was public to spell a value this hook was about to replace, and\n * the effect below writes it instead: the element the hook owns is seeded by the hook that owns it.\n */\nconst GRID = 22;\n\n/** How far the hover card clears its node, and the margin it keeps from the canvas edge. */\nconst CARD_GAP = 14;\nconst CARD_EDGE = 8;\n\n/** How far a label floats above its node, how tall its box is, and the air it demands around it. */\nconst LABEL_LIFT = 10;\nconst LABEL_HEIGHT = 12;\nconst LABEL_GAP = 3;\n\nconst clamp = (value: number, low: number, high: number) =>\n Math.min(Math.max(value, low), Math.max(low, high));\n\nexport interface GraphOverlays {\n /** The box the overlays are positioned within — the canvas' own bounds. */\n hostRef: React.RefObject<HTMLDivElement | null>;\n gridRef: React.RefObject<HTMLDivElement | null>;\n cardRef: React.RefObject<HTMLDivElement | null>;\n /** A `ref` callback for a given vertex's label. */\n labelRef: (vertex: VertexId) => (element: HTMLElement | null) => void;\n /** The labelled vertices, in the order the declutter pass should place them. */\n setLabelOrder: (vertices: VertexId[]) => void;\n setHovered: (vertex: VertexId | null) => void;\n /**\n * Re-register the tracked points with cosmos.gl.\n *\n * Call it after the graph exists and whenever the set of overlaid nodes changes. It has to be\n * driven from outside because effects run in declaration order, so this hook's own effects cannot\n * see a graph that a later hook is about to construct — and that construction is exactly what\n * clears the registration.\n */\n track: () => void;\n /** Ask for a repaint. Coalesced — many calls in a frame cost one. */\n schedule: () => void;\n}\n\n/**\n * **The api, not two getters off it.**\n *\n * This took `{ getGraph, getResident }` — an options object whose two members were copied out of\n * `GraphApi` — and that shape is the reason `getGraph` and `getResident` are on the api at all\n * beside the `slice` and `resident` values it already publishes. A host composing the two wrote the\n * hook's argument by hand out of the object it had just been given, which is a re-statement rather\n * than a decision: there is no useful call where the two come from different graphs.\n *\n * `GraphOverlayOptions` went with it. It named a shape a caller had to assemble, and what a caller\n * has is the api.\n *\n * **The ordering follows, and it is the honest one.** This has to be called *after* `useGraph`,\n * because it now takes what `useGraph` returns. The other half of the cycle — a look change owes the\n * overlays a repaint, and a look change does not tick — stays where it was: `useGraph` takes a\n * `schedule` callback, and a host bridges the two with one ref. One indirection, in the direction\n * that genuinely needs one, instead of two accessors threaded around an object the host is holding.\n */\nexport function useGraphOverlays(api: GraphApi): GraphOverlays {\n // Both are built once by `useGraph` and are stable for the life of the component, which is what\n // makes them safe to name in the dependency arrays below.\n const { getGraph, getResident } = api;\n const hostRef = useRef<HTMLDivElement>(null);\n const gridRef = useRef<HTMLDivElement>(null);\n const cardRef = useRef<HTMLDivElement>(null);\n const labelEls = useRef(new Map<VertexId, HTMLElement>());\n /** Label widths, measured once each — reading `offsetWidth` every frame would force layout. */\n const widths = useRef(new Map<VertexId, number>());\n const order = useRef<VertexId[]>([]);\n const hoveredRef = useRef<VertexId | null>(null);\n /** The card's box, measured once per hover, for the same reason. */\n const cardSize = useRef<{ width: number; height: number } | null>(null);\n /** The canvas' own box, kept by a `ResizeObserver` — see the effect below. */\n const box = useRef<{ width: number; height: number } | null>(null);\n const frame = useRef(0);\n\n /**\n * Tell cosmos.gl which points the overlays are watching: the labelled ones, plus the hovered one.\n *\n * Registration is *not* self-maintaining. `Points.updatePositions()` ends in an argument-less\n * `trackPointsByIndices()` that clears it, and that runs whenever `isPointPositionsUpdateNeeded`\n * is set — which only `setPointPositions` does. So the registration survives every look change and\n * every slider, and is lost exactly once per graph: at construction, on the `render()` that\n * follows `setPointPositions`. Hence a caller-driven re-register rather than a one-shot.\n */\n const track = useCallback(() => {\n const graph = getGraph();\n if (!graph) return;\n const hovered = hoveredRef.current;\n const watched =\n hovered === null || order.current.includes(hovered)\n ? order.current\n : [...order.current, hovered];\n // Registration is a device call like any other, and this one is *only* reached before the\n // device in the case that matters: a host registers its labels the moment the first slice\n // lands, which is the same commit the graph is still being built in. Dropped there, the\n // tracked map stays empty and every overlay sits at `opacity: 0` for ever.\n whenReady(graph, (ready) => ready.trackPointPositionsByIndices(getResident().indicesOf(watched)));\n }, [getGraph, getResident]);\n\n const paint = useCallback(() => {\n const graph = getGraph();\n if (!graph) return;\n const resident = getResident();\n /**\n * Positions come from the tracking API, not from `getPointPositions()`.\n *\n * The difference is what gets read back per frame. `getPointPositions()` is a synchronous\n * `readPixels` of the *whole* position framebuffer — 10,000 bytes at this corpus size, plus an\n * O(n) array build — on every animation frame the simulation runs. Tracking reads a\n * `ceil(√k)²` texture for the k points that actually carry an overlay: 576 bytes for Atlas'\n * 26 labels and a hovered node. It also caches while the simulation is stopped, so a settled\n * graph costs no readback at all until something moves.\n */\n // Read lazily: with labels off and nothing hovered, the only overlay left is the grid, which\n // needs the transform and not the points.\n let positions: ReadonlyMap<number, [number, number]> | null = null;\n /**\n * Where a vertex is on screen, or `null` when it is not drawn at all.\n *\n * Two ways to be absent and they are one answer here: not resident — the query moved on and this\n * vertex is not in the current buffers — or resident and not yet tracked. Both mean *do not draw\n * an overlay for it*, and the alternative to asking is drawing it at whatever the stale index now\n * holds, which is a label on the wrong node.\n */\n const at = (vertex: VertexId): [number, number] | null => {\n const index = resident.indexOf(vertex);\n if (index === undefined) return null;\n positions ??= graph.getTrackedPointPositionsMap();\n return positions.get(index) ?? null;\n };\n\n // Placed boxes, in importance order. A label that would land on one already down is dropped\n // rather than drawn over it — an unreadable pile of overlapping names is worse than a sparser\n // set of legible ones.\n const placed: [number, number, number, number][] = [];\n const bounds = box.current;\n for (const vertex of order.current) {\n const element = labelEls.current.get(vertex);\n if (!element) continue;\n const point = at(vertex);\n if (!point) {\n element.style.opacity = \"0\";\n continue;\n }\n const [x, y] = graph.spaceToScreenPosition(point);\n let width = widths.current.get(vertex);\n if (width === undefined) {\n width = element.offsetWidth;\n widths.current.set(vertex, width);\n }\n const x1 = x - width / 2;\n const x2 = x1 + width;\n const y2 = y - LABEL_LIFT;\n const y1 = y2 - LABEL_HEIGHT;\n const offscreen =\n bounds !== null && (x2 < 0 || y2 < 0 || x1 > bounds.width || y1 > bounds.height);\n const collides = placed.some((r) => x1 < r[2] && x2 > r[0] && y1 < r[3] && y2 > r[1]);\n if (offscreen || collides) {\n element.style.opacity = \"0\";\n continue;\n }\n placed.push([x1 - LABEL_GAP, y1 - LABEL_GAP, x2 + LABEL_GAP, y2 + LABEL_GAP]);\n // Positioned at the box that was just tested, in pixels. The previous version measured a\n // rectangle here and then drew the label somewhere else — `translate(-50%, -160%)` offsets by\n // percentages of the element's own size, so the collision box sat about nine pixels below the\n // text it was meant to protect and neighbouring labels overlapped anyway.\n element.style.transform = `translate(${Math.round(x1)}px, ${Math.round(y1)}px)`;\n element.style.opacity = \"1\";\n }\n // The grid belongs to the graph's space, not to the viewport: it slides with a pan and\n // subdivides on zoom, so the spacing on screen never leaves [GRID, 2·GRID). Without that a\n // fixed grid reads as wallpaper and the canvas stops feeling like somewhere you can move.\n const grid = gridRef.current;\n if (grid) {\n const k = graph.getZoomLevel();\n if (k > 0) {\n const step = (GRID * k) / 2 ** Math.floor(Math.log2(k));\n const [ox, oy] = graph.spaceToScreenPosition([0, 0]);\n const wrap = (v: number) => ((v % step) + step) % step;\n grid.style.backgroundSize = `${step}px ${step}px`;\n grid.style.backgroundPosition = `${wrap(ox)}px ${wrap(oy)}px`;\n }\n }\n\n // The card sits above the node, flips below when the top runs out, and is held inside the\n // canvas on both axes. It used to be centred with percentage transforms, which cannot know\n // about an edge — and since this layer clips, a node near a border showed half a tooltip.\n const card = cardRef.current;\n const hovered = hoveredRef.current;\n if (card && hovered !== null && bounds) {\n const point = at(hovered);\n if (point) {\n const [x, y] = graph.spaceToScreenPosition(point);\n let size = cardSize.current;\n if (!size) {\n size = { width: card.offsetWidth, height: card.offsetHeight };\n cardSize.current = size;\n }\n const above = y - CARD_GAP - size.height;\n const below = y + CARD_GAP;\n const top = above >= CARD_EDGE ? above : below;\n card.style.transform = `translate(${Math.round(\n clamp(x - size.width / 2, CARD_EDGE, bounds.width - size.width - CARD_EDGE),\n )}px, ${Math.round(clamp(top, CARD_EDGE, bounds.height - size.height - CARD_EDGE))}px)`;\n card.style.opacity = \"1\";\n }\n }\n }, [getGraph, getResident]);\n\n // The canvas' box, measured when it changes rather than when it is read. `getBoundingClientRect()`\n // inside `paint` was one forced layout per animation frame, in a painter that caches `offsetWidth`\n // for exactly that reason.\n useEffect(() => {\n const host = hostRef.current;\n if (!host) return;\n const observer = new ResizeObserver(([entry]) => {\n const size = entry?.contentRect;\n if (size) box.current = { width: size.width, height: size.height };\n });\n observer.observe(host);\n return () => observer.disconnect();\n }, []);\n\n /**\n * The grid's spacing at rest, written before the camera has said anything.\n *\n * `paint` sets this every frame from the live zoom, but only once there is a graph and a zoom to\n * read — and the element is mounted well before that. The two hosts that drew a grid were each\n * writing this same line inline off an exported `GRID`, which is a constant published so a call\n * site could spell the value this hook was about to overwrite. Seeding it here is the same picture\n * with the number staying where the painter that maintains it lives.\n *\n * The `backgroundImage` is not seeded: what the dots are made of is the host's decision — the\n * border colour, a gradient, whatever the surface wants — and only the *spacing* has to agree with\n * the camera.\n */\n useEffect(() => {\n const grid = gridRef.current;\n if (grid) grid.style.backgroundSize = `${GRID}px ${GRID}px`;\n }, []);\n\n const schedule = useCallback(() => {\n if (frame.current) return;\n frame.current = requestAnimationFrame(() => {\n frame.current = 0;\n paint();\n });\n }, [paint]);\n\n useEffect(\n () => () => {\n if (frame.current) cancelAnimationFrame(frame.current);\n // Clearing the handle is the whole point of this cleanup, not the cancel. `frame` doubles as\n // the \"a paint is already queued\" flag, and StrictMode runs setup → cleanup → setup on the\n // same instance, so the refs survive. Leaving a cancelled handle behind made every later\n // `schedule()` believe a frame was still pending and return early — permanently.\n frame.current = 0;\n },\n [],\n );\n\n const labelRef = useCallback(\n (vertex: VertexId) => (element: HTMLElement | null) => {\n if (element) labelEls.current.set(vertex, element);\n else labelEls.current.delete(vertex);\n },\n [],\n );\n\n const setLabelOrder = useCallback((vertices: VertexId[]) => {\n order.current = vertices;\n widths.current.clear();\n }, []);\n\n const setHovered = useCallback((vertex: VertexId | null) => {\n hoveredRef.current = vertex;\n cardSize.current = null;\n }, []);\n\n return { hostRef, gridRef, cardRef, labelRef, setLabelOrder, setHovered, track, schedule };\n}\n"],"names":[],"mappings":";;;AAoCA;AAuDO;AAGL;AAyBE;AACA;AACA;AASA;AAAgG;AAIhG;AACA;AACA;AAaA;AASA;AACE;AACA;AAE+B;AAQjC;AACE;AACA;AACA;AACA;AACE;AACA;AAAA;AAEF;AACA;AACA;AAIA;AAOA;AACE;AACA;AAAA;AAEF;AAMwB;AAK1B;AACA;AACE;AACA;AACE;AAGA;AACyD;AAC3D;AAMF;AAEA;AACE;AACA;AACE;AACA;AACA;AAIA;AAGA;AAAyC;AACmC;AAEvD;AACvB;AACF;AAMF;AACE;AACA;AACA;AACE;AACA;AAA0D;AAE5D;AACsB;AAiBtB;AACA;AAAuD;AAGzD;AACE;AAEE;AACA;AACD;AAGH;AAAA;AAEI;AAKgB;AAClB;AACA;AAGF;AAAiB;AAEb;AACmC;AACrC;AACA;AAIA;AACe;AAIf;AACmB;AAGrB;AACF;;;;"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"use-graph-prefs.d.ts","sourceRoot":"","sources":["../src/use-graph-prefs.ts"],"names":[],"mappings":"AAIA,OAAO,EAAY,KAAK,IAAI,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAW,KAAK,GAAG,EAAE,MAAM,aAAa,CAAC;AAGhD;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,IAAI;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,GAAG,CAAA;CAAE,CASxD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"use-graph-prefs.js","sources":["../src/use-graph-prefs.ts"],"sourcesContent":["\"use client\";\n\nimport { useMemo } from \"react\";\nimport { useKanzoTheme } from \"@kanzo-tech/ui\";\nimport { lookFrom, type Look } from \"./graph-looks\";\nimport { simFrom, type Sim } from \"./graph-sim\";\nimport { GRAPH_SECTION } from \"./section\";\n\n/**\n * The graph section's resolved preferences, as the two objects `useGraph` takes.\n *\n * The join between the provider's `sectionPrefs` and the two readers of this package's manifest.\n * Every host that drew a graph under a preferences panel wrote it — the docs' workspace and keasy's\n * discover page, the same eight lines each — and it knows nothing a host decides: the namespace,\n * the keys, the defaults and the bounds are all `GRAPH_SECTION`'s, and the resolution chain is the\n * provider's. What reaches `lookFrom`/`simFrom` is the resolved value, never the stored one.\n *\n * Needs `KanzoThemeProvider` with `GRAPH_SECTION` among its `sections`. Without it the namespace\n * resolves to nothing and both objects are the manifest's defaults.\n */\nexport function useGraphPrefs(): { look: Look; sim: Sim } {\n const resolved = useKanzoTheme().sectionPrefs[GRAPH_SECTION.namespace];\n\n return useMemo(() => {\n const values = Object.fromEntries(\n Object.entries(resolved ?? {}).map(([key, pref]) => [key, pref.value]),\n );\n return { look: lookFrom(values), sim: simFrom(values) };\n }, [resolved]);\n}\n"],"names":[],"mappings":";;;;;;AAoBO;AACL;AAEA;AACE;AAAsB;AACiD;AAEvE;AAAoD;AAExD;;;;"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"use-graph-selection.d.ts","sourceRoot":"","sources":["../src/use-graph-selection.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAGhE;;;;;;;;;;GAUG;AACH,MAAM,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAErC,wFAAwF;AACxF,MAAM,MAAM,IAAI,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,KAAK,CAAC;IAAC,EAAE,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,KAAK,EAAE,CAAA;CAAE,CAAC;AAS/F,4FAA4F;AAC5F,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,KAAK,CAAC,aAAa,CAIjE;AAED,MAAM,WAAW,qBAAqB;IACpC,0DAA0D;IAC1D,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,oFAAoF;IACpF,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,mFAAmF;IACnF,MAAM,EAAE,IAAI,CAAC;IACb,QAAQ,EAAE;QACR,aAAa,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,YAAY,KAAK,IAAI,CAAC;QACnD,aAAa,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,YAAY,KAAK,IAAI,CAAC;QACnD,WAAW,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,YAAY,KAAK,IAAI,CAAC;QACjD,eAAe,EAAE,MAAM,IAAI,CAAC;KAC7B,CAAC;CACH;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,KAAK,GAAG,IAAI,CAAC;IAC7B;;;;;;OAMG;IACH,WAAW,EAAE,MAAM,QAAQ,CAAC;IAC5B,wEAAwE;IACxE,YAAY,EAAE,MAAM,SAAS,GAAG,IAAI,CAAC;IACrC,MAAM,EAAE,CAAC,QAAQ,EAAE,GAAG,CAAC,QAAQ,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzF,IAAI,EAAE,IAAI,CAAC;IACX,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;CAC/B;AAOD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,qBAAqB,GAAG,qBAAqB,CA6IvF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"use-graph-selection.js","sources":["../src/use-graph-selection.ts"],"sourcesContent":["\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type React from \"react\";\nimport type { Graph } from \"@cosmos.gl/graph\";\nimport type { Resident, VertexId } from \"./resident\";\nimport type { Selection, SelectionSource, Tool } from \"./types\";\nimport { isReady } from \"./when-ready\";\n\n/**\n * Drawing a selection on the canvas: the marquee, the lasso, and the keys that modify them.\n *\n * The whole gesture lives here — what is being drawn, how many nodes it currently holds, and what\n * happens when the pointer comes up. The component gets handlers to spread onto an overlay and the\n * state to draw it with; it does not need to know that Shift borrows the marquee or that a hit test\n * costs a GPU readback.\n *\n * Modifiers are read at *release*, not at press, because that is when the reader has decided:\n * `Alt` removes what was drawn from the selection, `⌘`/`Ctrl` adds it, and neither replaces.\n */\nexport type Point = [number, number];\n\n/** A selection gesture in flight. The two tools differ only in what they accumulate. */\nexport type Drag = { tool: \"rect\"; from: Point; to: Point } | { tool: \"lasso\"; path: Point[] };\n\n/** Below this the hit test would be answering about a click, not a box. */\nconst MIN_RECT = 3;\n/** A raw pointer stream makes a polygon of near-duplicate vertices; the hit test walks every one. */\nconst PATH_STEP = 4;\n/** Slower than the hand, faster than the eye — and one GPU readback per gesture frame is plenty. */\nconst MEASURE_MS = 60;\n\n/** The running count rides just off the cursor, at the edge the gesture is growing from. */\nexport function cursorChip(drag: Drag | null): React.CSSProperties {\n if (!drag) return { display: \"none\" };\n const [x = 0, y = 0] = drag.tool === \"rect\" ? drag.to : (drag.path[drag.path.length - 1] ?? []);\n return { left: x + 14, top: y + 14 };\n}\n\nexport interface GraphSelectionGesture {\n /** The gesture being drawn, for the overlay to render. */\n drag: Drag | null;\n /** How many nodes it holds right now, so the number lands before the mouse does. */\n preview: number | null;\n /** The tool actually in force: the chosen one, or the marquee Shift is lending. */\n active: Tool;\n handlers: {\n onPointerDown: (event: React.PointerEvent) => void;\n onPointerMove: (event: React.PointerEvent) => void;\n onPointerUp: (event: React.PointerEvent) => void;\n onPointerCancel: () => void;\n };\n}\n\nexport interface GraphSelectionOptions {\n getGraph: () => Graph | null;\n /**\n * Who is drawn right now — the only thing that can turn a hit-test index into an identity.\n *\n * A gesture selects positions on screen, and a position is a buffer index, which the next\n * residency reuses for a different vertex. So the gesture resolves to identities here and now,\n * while the answer that produced them is still the one on screen.\n */\n getResident: () => Resident;\n /** The live selection, for the modifiers to add to or subtract from. */\n getSelection: () => Selection | null;\n commit: (vertices: Set<VertexId> | null, source: SelectionSource, label: string) => void;\n tool: Tool;\n setTool: (tool: Tool) => void;\n}\n\nconst NAME: Record<\"rect\" | \"lasso\", { source: SelectionSource; label: string }> = {\n rect: { source: \"marquee\", label: \"Marquee\" },\n lasso: { source: \"lasso\", label: \"Lasso\" },\n};\n\nexport function useGraphSelection(options: GraphSelectionOptions): GraphSelectionGesture {\n const { commit, getGraph, getResident, getSelection, setTool, tool } = options;\n const [drag, setDrag] = useState<Drag | null>(null);\n const [preview, setPreview] = useState<number | null>(null);\n const [shift, setShift] = useState(false);\n const measured = useRef(0);\n\n const hitTest = useCallback(\n (shape: Drag): number[] => {\n const graph = getGraph();\n if (!graph) return [];\n // **Upstream's own words, on these two and on nothing else in its `.d.ts`:** *this method is\n // synchronous and must only be called when the graph is ready*. There is nothing to await in\n // a pointer handler, so the answer for a device that is not there is the honest one — a\n // gesture over a graph that has not drawn selects nothing, which is what it looks like.\n if (!isReady(graph)) return [];\n if (shape.tool === \"rect\") {\n const [[ax, ay], [bx, by]] = [shape.from, shape.to];\n if (Math.abs(bx - ax) < MIN_RECT || Math.abs(by - ay) < MIN_RECT) return [];\n return Array.from(\n graph.findPointsInRect([\n [Math.min(ax, bx), Math.min(ay, by)],\n [Math.max(ax, bx), Math.max(ay, by)],\n ]),\n );\n }\n if (shape.path.length < 3) return [];\n return Array.from(graph.findPointsInPolygon(shape.path));\n },\n [getGraph],\n );\n\n // Both hit tests are a GPU pass followed by a synchronous `readPixels`, so running one per\n // pointer event would stall the frame the gesture is being drawn into.\n const measure = useCallback(\n (shape: Drag) => {\n const now = performance.now();\n if (now - measured.current < MEASURE_MS) return;\n measured.current = now;\n setPreview(hitTest(shape).length);\n },\n [hitTest],\n );\n\n // Escape is the way out of everything, in layers: it abandons a drag in progress, then drops the\n // tool, then clears what is held. And a window that loses focus never delivers the Shift keyup,\n // which would leave the canvas unable to pan.\n useEffect(() => {\n const down = (event: KeyboardEvent) => {\n if (event.key === \"Shift\") setShift(true);\n if (event.key !== \"Escape\") return;\n setDrag((current) => {\n if (current) return null;\n if (tool) setTool(null);\n else commit(null, \"node\", \"\");\n return null;\n });\n setPreview(null);\n };\n const up = (event: KeyboardEvent) => {\n if (event.key === \"Shift\") setShift(false);\n };\n const blur = () => setShift(false);\n window.addEventListener(\"keydown\", down);\n window.addEventListener(\"keyup\", up);\n window.addEventListener(\"blur\", blur);\n return () => {\n window.removeEventListener(\"keydown\", down);\n window.removeEventListener(\"keyup\", up);\n window.removeEventListener(\"blur\", blur);\n };\n }, [commit, setTool, tool]);\n\n /** Screen coordinates inside the canvas — the space every cosmos.gl hit test speaks. */\n const at = (event: React.PointerEvent): Point => {\n const box = event.currentTarget.getBoundingClientRect();\n return [event.clientX - box.left, event.clientY - box.top];\n };\n\n const finish = (shape: Drag | null, event: React.PointerEvent) => {\n setDrag(null);\n setPreview(null);\n if (!shape) return;\n // The one line the whole gesture turns on: the hit test answers in buffer indices, and they stop\n // meaning anything the moment the resident set changes.\n const vertices = new Set(getResident().verticesAt(hitTest(shape)));\n // A gesture that caught nothing and asked for nothing is a misfire, not a request to clear —\n // clearing is what the corner's own button and Escape are for.\n if (vertices.size === 0 && !event.altKey) return;\n const { label, source } = NAME[shape.tool];\n const held = getSelection()?.vertices ?? [];\n if (event.altKey) {\n const next = new Set(held);\n for (const vertex of vertices) next.delete(vertex);\n commit(next.size > 0 ? next : null, source, label);\n } else if (event.metaKey || event.ctrlKey) {\n commit(new Set([...held, ...vertices]), source, label);\n } else {\n commit(vertices, source, label);\n }\n };\n\n const active: Tool = tool ?? (shift ? \"rect\" : null);\n\n return {\n drag,\n preview,\n active,\n handlers: {\n onPointerDown: (event) => {\n if (event.button !== 0) return;\n event.currentTarget.setPointerCapture(event.pointerId);\n const start = at(event);\n setDrag(\n active === \"rect\"\n ? { tool: \"rect\", from: start, to: start }\n : { tool: \"lasso\", path: [start] },\n );\n },\n onPointerMove: (event) => {\n if (!drag) return;\n const next = at(event);\n if (drag.tool === \"rect\") {\n const shape: Drag = { ...drag, to: next };\n setDrag(shape);\n measure(shape);\n return;\n }\n const last = drag.path[drag.path.length - 1] as Point;\n if (Math.hypot(next[0] - last[0], next[1] - last[1]) < PATH_STEP) return;\n const shape: Drag = { tool: \"lasso\", path: [...drag.path, next] };\n setDrag(shape);\n measure(shape);\n },\n onPointerUp: (event) => finish(drag, event),\n onPointerCancel: () => {\n setDrag(null);\n setPreview(null);\n },\n },\n };\n}\n"],"names":[],"mappings":";;;AA0BA;AAOO;AACL;AACA;AACA;AACF;AAkCA;AAAmF;AAC/C;AAEpC;AAEO;AACL;AAMgB;AAEZ;AACA;AAKA;AACA;AACE;AACA;AACa;AACY;AACc;AACA;AACpC;AAAA;AAGL;AACuD;AACzD;AACS;AAKK;AAEZ;AACA;AAEgC;AAClC;AACQ;AAMV;AACE;AAEE;AAOe;AAGf;AAAyC;AAG3C;AAIE;AAEuC;AACzC;AAIF;AACE;AACA;AAAyD;;AAMzD;AAGA;AAGA;AACA;AAEA;AACE;AACA;AACA;AAAiD;AAInB;AAMlC;AAAO;AACL;AACA;AACA;AACU;AAEN;AACA;AACA;AACA;AAAA;AAGmC;AAAE;AAEvC;AAEE;AACA;AACA;AACE;AACA;AAEA;AAAA;AAEF;AACA;AACA;AACA;AACa;AACf;AAC0C;AAExC;AACe;AACjB;AAAA;AAGN;;;;;"}