@kanzo-tech/graph 0.18.0 → 0.20.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 (76) hide show
  1. package/README.md +25 -7
  2. package/dist/core/detail.d.ts +3 -1
  3. package/dist/core/detail.d.ts.map +1 -1
  4. package/dist/core/detail.js +55 -30
  5. package/dist/core/detail.js.map +1 -1
  6. package/dist/core/error.d.ts +16 -0
  7. package/dist/core/error.d.ts.map +1 -0
  8. package/dist/core/error.js +14 -0
  9. package/dist/core/error.js.map +1 -0
  10. package/dist/core/filter.d.ts +0 -13
  11. package/dist/core/filter.d.ts.map +1 -1
  12. package/dist/core/filter.js +47 -55
  13. package/dist/core/filter.js.map +1 -1
  14. package/dist/core/load.d.ts +5 -0
  15. package/dist/core/load.d.ts.map +1 -1
  16. package/dist/core/load.js +93 -78
  17. package/dist/core/load.js.map +1 -1
  18. package/dist/core/state.d.ts +21 -6
  19. package/dist/core/state.d.ts.map +1 -1
  20. package/dist/core/store.d.ts.map +1 -1
  21. package/dist/core/store.js +107 -101
  22. package/dist/core/store.js.map +1 -1
  23. package/dist/core/types.d.ts +4 -4
  24. package/dist/core/types.d.ts.map +1 -1
  25. package/dist/index.d.ts +8 -3
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +27 -19
  28. package/dist/index.js.map +1 -1
  29. package/dist/parts/graph-canvas.js +45 -45
  30. package/dist/parts/graph-canvas.js.map +1 -1
  31. package/dist/parts/graph-counts.d.ts +15 -0
  32. package/dist/parts/graph-counts.d.ts.map +1 -0
  33. package/dist/parts/graph-counts.js +41 -0
  34. package/dist/parts/graph-counts.js.map +1 -0
  35. package/dist/parts/graph-inspector.d.ts.map +1 -1
  36. package/dist/parts/graph-inspector.js +48 -48
  37. package/dist/parts/graph-inspector.js.map +1 -1
  38. package/dist/parts/graph-legend.d.ts +3 -2
  39. package/dist/parts/graph-legend.d.ts.map +1 -1
  40. package/dist/parts/graph-legend.js +30 -48
  41. package/dist/parts/graph-legend.js.map +1 -1
  42. package/dist/parts/graph-looks.d.ts +16 -0
  43. package/dist/parts/graph-looks.d.ts.map +1 -0
  44. package/dist/parts/graph-looks.js +138 -0
  45. package/dist/parts/graph-looks.js.map +1 -0
  46. package/dist/parts/graph-search.d.ts +17 -0
  47. package/dist/parts/graph-search.d.ts.map +1 -0
  48. package/dist/parts/graph-search.js +89 -0
  49. package/dist/parts/graph-search.js.map +1 -0
  50. package/dist/react/use-graph-prefs.d.ts +6 -2
  51. package/dist/react/use-graph-prefs.d.ts.map +1 -1
  52. package/dist/react/use-graph-prefs.js +7 -7
  53. package/dist/react/use-graph-prefs.js.map +1 -1
  54. package/dist/react/use-graph.js +61 -55
  55. package/dist/react/use-graph.js.map +1 -1
  56. package/dist/render/camera.d.ts +26 -0
  57. package/dist/render/camera.d.ts.map +1 -0
  58. package/dist/render/camera.js +40 -0
  59. package/dist/render/camera.js.map +1 -0
  60. package/dist/render/graph-looks.d.ts +10 -0
  61. package/dist/render/graph-looks.d.ts.map +1 -1
  62. package/dist/render/graph-looks.js +37 -20
  63. package/dist/render/graph-looks.js.map +1 -1
  64. package/dist/render/placement.d.ts +23 -0
  65. package/dist/render/placement.d.ts.map +1 -0
  66. package/dist/render/placement.js +23 -0
  67. package/dist/render/placement.js.map +1 -0
  68. package/dist/render/renderer.d.ts +1 -0
  69. package/dist/render/renderer.d.ts.map +1 -1
  70. package/dist/render/renderer.js +115 -96
  71. package/dist/render/renderer.js.map +1 -1
  72. package/dist/render/webgl.d.ts +5 -4
  73. package/dist/render/webgl.d.ts.map +1 -1
  74. package/dist/render/webgl.js +14 -11
  75. package/dist/render/webgl.js.map +1 -1
  76. package/package.json +10 -9
@@ -1,61 +1,43 @@
1
1
  "use client";
2
- import { jsxs as s, jsx as a } from "react/jsx-runtime";
3
- import { useChartCapacity as S, Show as x, cn as b } from "@kanzo-tech/ui";
4
- import { useMemo as O } from "react";
5
- import { nameOf as G } from "../core/categories.js";
6
- import { bindingOf as j } from "../core/channels.js";
7
- import { useGraphState as r } from "../react/use-graph-state.js";
8
- import { scaleOf as z } from "../render/graph-model.js";
9
- import { ShapeGlyph as y } from "./shape-glyph.js";
10
- const C = (l, c) => l.length === c.length && l.every((p, n) => p === c[n]);
11
- function E({ className: l, slot: c, ...p }) {
12
- const n = r((e) => e.options), u = r((e) => {
2
+ import { jsx as a, jsxs as m } from "react/jsx-runtime";
3
+ import { useChartCapacity as b, Show as N, cn as w } from "@kanzo-tech/ui";
4
+ import { useMemo as S } from "react";
5
+ import { nameOf as O } from "../core/categories.js";
6
+ import { bindingOf as G } from "../core/channels.js";
7
+ import { useGraphState as d } from "../react/use-graph-state.js";
8
+ import { scaleOf as j } from "../render/graph-model.js";
9
+ import { ShapeGlyph as f } from "./shape-glyph.js";
10
+ const v = (n, r) => n.length === r.length && n.every((i, o) => i === r[o]);
11
+ function B({ className: n, slot: r, ...i }) {
12
+ const o = d((e) => e.options), p = d((e) => {
13
13
  var t;
14
14
  return ((t = e.drawn) == null ? void 0 : t.domain) ?? e.domain;
15
- }, C), i = r((e) => {
15
+ }, v), l = d((e) => {
16
16
  var t;
17
17
  return ((t = e.drawn) == null ? void 0 : t.tally) ?? null;
18
- }), N = r((e) => {
19
- var t;
20
- return ((t = e.drawn) == null ? void 0 : t.vertices) ?? null;
21
- }), w = r((e) => e.total), o = S(), d = O(() => z(n, o), [n, o]), g = j(n), f = g.byTable || g.category !== void 0, h = u.slice(0, o), v = i ? i.slice(o).reduce((e, t) => e + t, 0) : null, m = (e) => e == null ? "—" : e.toLocaleString();
22
- return /* @__PURE__ */ s(
18
+ }), s = b(), c = S(() => j(o, s), [o, s]), h = G(o), x = h.byTable || h.category !== void 0, u = p.slice(0, s), y = l ? l.slice(s).reduce((e, t) => e + t, 0) : null, g = (e) => e === null ? "—" : e.toLocaleString();
19
+ return !x || u.length === 0 ? null : /* @__PURE__ */ a(
23
20
  "div",
24
21
  {
25
- ...p,
26
- className: b("rounded-lg border bg-card px-2.5 py-1.5 text-xs", l),
27
- "data-slot": c ?? "graph-legend",
28
- children: [
29
- /* @__PURE__ */ a(x, { when: f && h.length > 0, children: /* @__PURE__ */ s("ul", { className: "space-y-1", "data-slot": "graph-legend-items", children: [
30
- h.map((e, t) => /* @__PURE__ */ s("li", { className: "flex items-center gap-2", "data-slot": "graph-legend-item", children: [
31
- /* @__PURE__ */ a(y, { className: "size-2.5 shrink-0", color: d.color(t), shape: d.shape(t) }),
32
- /* @__PURE__ */ a("span", { className: "truncate", children: G(e, n.categories) }),
33
- /* @__PURE__ */ a("span", { className: "ms-auto ps-4 text-muted-foreground tabular-nums", children: m(i ? i[t] ?? 0 : null) })
34
- ] }, String(e))),
35
- /* @__PURE__ */ a(x, { when: u.length > o, children: /* @__PURE__ */ s("li", { className: "flex items-center gap-2", "data-slot": "graph-legend-item", children: [
36
- /* @__PURE__ */ a(y, { className: "size-2.5 shrink-0", color: d.color(o), shape: d.shape(o) }),
37
- /* @__PURE__ */ a("span", { children: "Other" }),
38
- /* @__PURE__ */ a("span", { className: "ms-auto ps-4 text-muted-foreground tabular-nums", children: m(v) })
39
- ] }) })
40
- ] }) }),
41
- /* @__PURE__ */ s(
42
- "p",
43
- {
44
- className: b("text-muted-foreground tabular-nums", f && h.length > 0 && "mt-1 border-t pt-1"),
45
- "data-slot": "graph-legend-count",
46
- children: [
47
- m(N),
48
- " of ",
49
- m(w),
50
- " drawn"
51
- ]
52
- }
53
- )
54
- ]
22
+ ...i,
23
+ className: w("rounded-lg border bg-card px-2.5 py-2 text-xs", n),
24
+ "data-slot": r ?? "graph-legend",
25
+ children: /* @__PURE__ */ m("ul", { className: "space-y-1.5", "data-slot": "graph-legend-items", children: [
26
+ u.map((e, t) => /* @__PURE__ */ m("li", { className: "flex items-center gap-2", "data-slot": "graph-legend-item", children: [
27
+ /* @__PURE__ */ a(f, { className: "size-2.5 shrink-0", color: c.color(t), shape: c.shape(t) }),
28
+ /* @__PURE__ */ a("span", { className: "truncate", children: O(e, o.categories) }),
29
+ /* @__PURE__ */ a("span", { className: "ms-auto ps-4 text-muted-foreground tabular-nums", children: g(l ? l[t] ?? 0 : null) })
30
+ ] }, String(e))),
31
+ /* @__PURE__ */ a(N, { when: p.length > s, children: /* @__PURE__ */ m("li", { className: "flex items-center gap-2 border-t pt-1.5", "data-slot": "graph-legend-item", children: [
32
+ /* @__PURE__ */ a(f, { className: "size-2.5 shrink-0", color: c.color(s), shape: c.shape(s) }),
33
+ /* @__PURE__ */ a("span", { children: "Other" }),
34
+ /* @__PURE__ */ a("span", { className: "ms-auto ps-4 text-muted-foreground tabular-nums", children: g(y) })
35
+ ] }) })
36
+ ] })
55
37
  }
56
38
  );
57
39
  }
58
40
  export {
59
- E as GraphLegend
41
+ B as GraphLegend
60
42
  };
61
43
  //# sourceMappingURL=graph-legend.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"graph-legend.js","sources":["../../src/parts/graph-legend.tsx"],"sourcesContent":["\"use client\";\n\nimport { cn, Show, useChartCapacity } from \"@kanzo-tech/ui\";\nimport { useMemo } from \"react\";\nimport { nameOf } from \"../core/categories\";\nimport { bindingOf } from \"../core/channels\";\nimport { useGraphState } from \"../react/use-graph-state\";\nimport { scaleOf } from \"../render/graph-model\";\nimport { ShapeGlyph } from \"./shape-glyph\";\n\nexport type GraphLegendProps = React.ComponentProps<\"div\">;\n\nconst shallow = (a: readonly unknown[], b: readonly unknown[]) => a.length === b.length && a.every((v, i) => v === b[i]);\n\n/**\n * **The categorical scale the canvas draws, as rows** — glyph, name and how many vertices of that\n * category are drawn, then what is drawn of the whole. Unbound, the categories are the vertex types.\n * The rows are the domain fixed before the graph loaded, so a row never moves and a colour never\n * changes hands; a category past the palette's capacity is Other, as it is on the canvas.\n *\n * Names are the root's `categories`, never this part's: a legend that renamed a category would be\n * encoding one product's policy, which is the parts rule's reversal.\n */\nexport function GraphLegend({ className, slot, ...rest }: GraphLegendProps) {\n const options = useGraphState((s) => s.options);\n const domain = useGraphState((s) => s.drawn?.domain ?? s.domain, shallow);\n const tally = useGraphState((s) => s.drawn?.tally ?? null);\n const vertices = useGraphState((s) => s.drawn?.vertices ?? null);\n const total = useGraphState((s) => s.total);\n const capacity = useChartCapacity();\n const scale = useMemo(() => scaleOf(options, capacity), [options, capacity]);\n const binding = bindingOf(options);\n const bound = binding.byTable || binding.category !== undefined;\n const shown = domain.slice(0, capacity);\n const other = tally ? tally.slice(capacity).reduce((sum, n) => sum + n, 0) : null;\n const count = (n: number | null | undefined) => (n === null || n === undefined ? \"—\" : n.toLocaleString());\n\n return (\n <div\n {...rest}\n className={cn(\"rounded-lg border bg-card px-2.5 py-1.5 text-xs\", className)}\n data-slot={slot ?? \"graph-legend\"}\n >\n <Show when={bound && shown.length > 0}>\n <ul className=\"space-y-1\" data-slot=\"graph-legend-items\">\n {shown.map((value, rank) => (\n <li className=\"flex items-center gap-2\" data-slot=\"graph-legend-item\" key={String(value)}>\n <ShapeGlyph className=\"size-2.5 shrink-0\" color={scale.color(rank)} shape={scale.shape(rank)} />\n <span className=\"truncate\">{nameOf(value, options.categories)}</span>\n <span className=\"ms-auto ps-4 text-muted-foreground tabular-nums\">{count(tally ? (tally[rank] ?? 0) : null)}</span>\n </li>\n ))}\n <Show when={domain.length > capacity}>\n <li className=\"flex items-center gap-2\" data-slot=\"graph-legend-item\">\n <ShapeGlyph className=\"size-2.5 shrink-0\" color={scale.color(capacity)} shape={scale.shape(capacity)} />\n <span>Other</span>\n <span className=\"ms-auto ps-4 text-muted-foreground tabular-nums\">{count(other)}</span>\n </li>\n </Show>\n </ul>\n </Show>\n <p\n className={cn(\"text-muted-foreground tabular-nums\", bound && shown.length > 0 && \"mt-1 border-t pt-1\")}\n data-slot=\"graph-legend-count\"\n >\n {count(vertices)} of {count(total)} drawn\n </p>\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;AAYA;AAWO;AACL;;AACoC;AAAqB;;AACtB;AAAkB;;AACf;AAAqB;AAU3D;AACE;AAAC;AAAA;AACK;AACsE;AACvD;AAEnB;AAEK;AAEG;AAA8F;AAChC;AAC8C;AAE/G;AAGG;AAAsG;AAC3F;AACqE;AAEpF;AAEJ;AACA;AAAC;AAAA;AACsG;AAC3F;AAET;AAAc;AAAE;AAAgB;AAAE;AAAA;AAAA;AAAA;AACrC;AAAA;AAGN;;;;"}
1
+ {"version":3,"file":"graph-legend.js","sources":["../../src/parts/graph-legend.tsx"],"sourcesContent":["\"use client\";\n\nimport { cn, Show, useChartCapacity } from \"@kanzo-tech/ui\";\nimport { useMemo } from \"react\";\nimport { nameOf } from \"../core/categories\";\nimport { bindingOf } from \"../core/channels\";\nimport { useGraphState } from \"../react/use-graph-state\";\nimport { scaleOf } from \"../render/graph-model\";\nimport { ShapeGlyph } from \"./shape-glyph\";\n\nexport type GraphLegendProps = React.ComponentProps<\"div\">;\n\nconst shallow = (a: readonly unknown[], b: readonly unknown[]) => a.length === b.length && a.every((v, i) => v === b[i]);\n\n/**\n * **The categorical scale the canvas draws, as rows** — glyph, name and how many vertices of that\n * category are drawn; what is drawn of the whole is `GraphCounts`. Unbound, the categories are the\n * vertex types, and with no category at all there is nothing to key, so nothing is drawn.\n * The rows are the domain fixed before the graph loaded, so a row never moves and a colour never\n * changes hands; a category past the palette's capacity is Other, as it is on the canvas.\n *\n * Names are the root's `categories`, never this part's: a legend that renamed a category would be\n * encoding one product's policy, which is the parts rule's reversal.\n */\nexport function GraphLegend({ className, slot, ...rest }: GraphLegendProps) {\n const options = useGraphState((s) => s.options);\n const domain = useGraphState((s) => s.drawn?.domain ?? s.domain, shallow);\n const tally = useGraphState((s) => s.drawn?.tally ?? null);\n const capacity = useChartCapacity();\n const scale = useMemo(() => scaleOf(options, capacity), [options, capacity]);\n const binding = bindingOf(options);\n const bound = binding.byTable || binding.category !== undefined;\n const shown = domain.slice(0, capacity);\n const other = tally ? tally.slice(capacity).reduce((sum, n) => sum + n, 0) : null;\n const count = (n: number | null) => (n === null ? \"—\" : n.toLocaleString());\n if (!bound || shown.length === 0) return null;\n\n return (\n <div\n {...rest}\n className={cn(\"rounded-lg border bg-card px-2.5 py-2 text-xs\", className)}\n data-slot={slot ?? \"graph-legend\"}\n >\n <ul className=\"space-y-1.5\" data-slot=\"graph-legend-items\">\n {shown.map((value, rank) => (\n <li className=\"flex items-center gap-2\" data-slot=\"graph-legend-item\" key={String(value)}>\n <ShapeGlyph className=\"size-2.5 shrink-0\" color={scale.color(rank)} shape={scale.shape(rank)} />\n <span className=\"truncate\">{nameOf(value, options.categories)}</span>\n <span className=\"ms-auto ps-4 text-muted-foreground tabular-nums\">{count(tally ? (tally[rank] ?? 0) : null)}</span>\n </li>\n ))}\n <Show when={domain.length > capacity}>\n <li className=\"flex items-center gap-2 border-t pt-1.5\" data-slot=\"graph-legend-item\">\n <ShapeGlyph className=\"size-2.5 shrink-0\" color={scale.color(capacity)} shape={scale.shape(capacity)} />\n <span>Other</span>\n <span className=\"ms-auto ps-4 text-muted-foreground tabular-nums\">{count(other)}</span>\n </li>\n </Show>\n </ul>\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;AAYA;AAYO;AACL;;AACoC;AAAqB;;AACtB;AAAkB;AAQrD;AAGE;AAAC;AAAA;AACK;AACoE;AACrD;AAGhB;AAEG;AAA8F;AAChC;AAC8C;AAE/G;AAGG;AAAsG;AAC3F;AACqE;AAEpF;AACF;AAAA;AAGN;;;;"}
@@ -0,0 +1,16 @@
1
+ export type GraphLooksProps = React.ComponentProps<"div">;
2
+ /**
3
+ * **The graph's picture, as three looks and the axes under them** — GitHub's appearance settings:
4
+ * pick by looking, then customise. The looks are `PRESETS` over `GRAPH_SECTION`'s axes; wearing one
5
+ * writes every axis it names, and a look is checked exactly when the resolved axes are its own, so
6
+ * there is no name stored beside them to go stale. "Customize" holds the axes themselves — Marks as
7
+ * a segmented control, the rest as the section declares them — and opens on its own when the axes
8
+ * are no preset.
9
+ *
10
+ * Under `KanzoThemeProvider` with `GRAPH_SECTION` among its `sections` — without it there is nothing
11
+ * to write and it draws nothing, as `PreferencesSections` does for an unknown namespace. It needs no
12
+ * `GraphRoot`, so it sits in a preferences panel as well as in a dock. A look whose axes a tenant pinned is disabled,
13
+ * because wearing it would change nothing.
14
+ */
15
+ export declare function GraphLooks({ className, slot, ...rest }: GraphLooksProps): import("react").JSX.Element | null;
16
+ //# sourceMappingURL=graph-looks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-looks.d.ts","sourceRoot":"","sources":["../../src/parts/graph-looks.tsx"],"names":[],"mappings":"AAyBA,MAAM,MAAM,eAAe,GAAG,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;AAsG1D;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,EAAE,eAAe,sCA6DvE"}
@@ -0,0 +1,138 @@
1
+ "use client";
2
+ import { jsxs as o, jsx as e, Fragment as y } from "react/jsx-runtime";
3
+ import { useKanzoTheme as v, PreferencesFieldSet as k, RadioGroup as C, RadioGroupCard as S, Collapsible as w, CollapsibleTrigger as N, Button as E, Show as g, CollapsibleIndicator as O, CollapsibleContent as A, SegmentGroup as G, PreferencesSections as P, cn as $ } from "@kanzo-tech/ui";
4
+ import { useId as z } from "react";
5
+ import { useGraphPrefs as M } from "../react/use-graph-prefs.js";
6
+ import { lookFrom as j, PRESETS as b } from "../render/graph-looks.js";
7
+ import { scaleOf as R } from "../render/graph-model.js";
8
+ import { GRAPH_SECTION as I } from "../section.js";
9
+ import { ShapeGlyph as L } from "./shape-glyph.js";
10
+ const m = I.namespace, h = {
11
+ nebula: { label: "Nebula", blurb: "Dense, dim points and additive links: a picture that reads as flow.", channels: {} },
12
+ atlas: {
13
+ label: "Atlas",
14
+ blurb: "Map-steady points, links that just bow, generous labels.",
15
+ channels: { stroke: "var(--muted-foreground)" }
16
+ },
17
+ ink: {
18
+ label: "Ink",
19
+ blurb: "Large, legible marks in one ink, identity on shape — the print-and-projector register.",
20
+ channels: { fill: "var(--foreground)", symbol: "category", stroke: "var(--muted-foreground)" }
21
+ }
22
+ }, T = ["links", "labels", "additive-links", "bowed-links", "vignette", "grid"], x = [
23
+ { x: 22, y: 20, rank: 0, ramp: 1 },
24
+ { x: 58, y: 8, rank: 1, ramp: 0.3 },
25
+ { x: 58, y: 32, rank: 1, ramp: 0.3 },
26
+ { x: 98, y: 20, rank: 2, ramp: 0.7 },
27
+ { x: 140, y: 10, rank: 3, ramp: 0.45 },
28
+ { x: 140, y: 31, rank: 2, ramp: 0.2 },
29
+ { x: 184, y: 21, rank: 0, ramp: 0.85 }
30
+ ], D = [
31
+ [0, 1],
32
+ [0, 2],
33
+ [1, 2],
34
+ [0, 3],
35
+ [3, 4],
36
+ [3, 5],
37
+ [4, 6],
38
+ [5, 6]
39
+ ], F = 0.55;
40
+ function B({ channels: d, look: l }) {
41
+ const p = z(), i = R(d), u = (t) => (l.size[0] + t * (l.size[1] - l.size[0])) * F;
42
+ return /* @__PURE__ */ o("svg", { "aria-hidden": !0, className: "h-16 w-full rounded-[4px] border bg-background", preserveAspectRatio: "xMidYMid meet", viewBox: "0 0 208 40", children: [
43
+ D.map(([t, s]) => {
44
+ const n = x[t], r = x[s], f = (n.x + r.x) / 2 - (r.y - n.y) * l.link.curve, a = (n.y + r.y) / 2 + (r.x - n.x) * l.link.curve;
45
+ return /* @__PURE__ */ e(
46
+ "path",
47
+ {
48
+ d: `M${n.x} ${n.y}Q${f} ${a} ${r.x} ${r.y}`,
49
+ fill: "none",
50
+ stroke: d.stroke ?? i.color(n.rank),
51
+ strokeOpacity: l.link.opacity,
52
+ strokeWidth: l.link.width
53
+ },
54
+ `${t}-${s}`
55
+ );
56
+ }),
57
+ x.map((t, s) => {
58
+ const n = u(t.ramp);
59
+ return /* @__PURE__ */ e(
60
+ L,
61
+ {
62
+ color: i.color(t.rank),
63
+ height: n * 2,
64
+ shape: i.shape(t.rank),
65
+ width: n * 2,
66
+ x: t.x - n,
67
+ y: t.y - n
68
+ },
69
+ s
70
+ );
71
+ }),
72
+ l.vignette ? /* @__PURE__ */ o(y, { children: [
73
+ /* @__PURE__ */ e("defs", { children: /* @__PURE__ */ o("radialGradient", { id: p, children: [
74
+ /* @__PURE__ */ e("stop", { offset: "55%", stopColor: "var(--background)", stopOpacity: "0" }),
75
+ /* @__PURE__ */ e("stop", { offset: "100%", stopColor: "var(--background)", stopOpacity: "0.85" })
76
+ ] }) }),
77
+ /* @__PURE__ */ e("rect", { fill: `url(#${p})`, height: "40", width: "208" })
78
+ ] }) : null
79
+ ] });
80
+ }
81
+ function _({ className: d, slot: l, ...p }) {
82
+ const { sectionPrefs: i, setSectionPref: u } = v(), { preset: t } = M(), s = i[m];
83
+ if (!s) return null;
84
+ const n = (a) => {
85
+ var c;
86
+ return ((c = s[a]) == null ? void 0 : c.offered) === !0;
87
+ }, r = s.marks, f = (r == null ? void 0 : r.decl.kind) === "choice" && Array.isArray(r.decl.options) ? r.decl.options : [];
88
+ return /* @__PURE__ */ o("div", { ...p, className: $("flex flex-col gap-4", d), "data-slot": l ?? "graph-looks", children: [
89
+ /* @__PURE__ */ e(k, { label: "Look", children: /* @__PURE__ */ e(
90
+ C,
91
+ {
92
+ onValueChange: (a) => {
93
+ const c = a.value;
94
+ c && u(m, b[c]);
95
+ },
96
+ value: t,
97
+ children: Object.keys(h).map((a) => /* @__PURE__ */ o(
98
+ S,
99
+ {
100
+ className: "flex-col gap-0 p-2",
101
+ disabled: !Object.keys(b[a]).every(n),
102
+ value: a,
103
+ children: [
104
+ /* @__PURE__ */ e(B, { channels: h[a].channels, look: j(b[a]) }),
105
+ /* @__PURE__ */ e("span", { className: "mt-1.5 font-medium text-xs", children: h[a].label }),
106
+ /* @__PURE__ */ e("span", { className: "mt-0.5 text-[10px] text-muted-foreground leading-relaxed", children: h[a].blurb })
107
+ ]
108
+ },
109
+ a
110
+ ))
111
+ }
112
+ ) }),
113
+ /* @__PURE__ */ o(w, { defaultOpen: t === null, children: [
114
+ /* @__PURE__ */ e(N, { asChild: !0, children: /* @__PURE__ */ o(E, { className: "w-full", size: "sm", variant: "ghost", children: [
115
+ "Customize",
116
+ /* @__PURE__ */ e(g, { when: t === null, children: /* @__PURE__ */ e("span", { className: "ms-auto me-1 font-normal text-muted-foreground text-xs", children: "Custom" }) }),
117
+ /* @__PURE__ */ e(O, {})
118
+ ] }) }),
119
+ /* @__PURE__ */ o(A, { className: "flex flex-col gap-4 pt-3", children: [
120
+ /* @__PURE__ */ e(g, { when: n("marks"), children: /* @__PURE__ */ e(k, { label: (r == null ? void 0 : r.decl.label) ?? "Marks", children: /* @__PURE__ */ e(
121
+ G,
122
+ {
123
+ onValueChange: (a) => a.value && u(m, { marks: a.value }),
124
+ options: f,
125
+ size: "sm",
126
+ value: (r == null ? void 0 : r.value) ?? null,
127
+ variant: "solid"
128
+ }
129
+ ) }) }),
130
+ /* @__PURE__ */ e(P, { namespace: m, only: T })
131
+ ] })
132
+ ] })
133
+ ] });
134
+ }
135
+ export {
136
+ _ as GraphLooks
137
+ };
138
+ //# sourceMappingURL=graph-looks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-looks.js","sources":["../../src/parts/graph-looks.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n Button,\n cn,\n Collapsible,\n CollapsibleContent,\n CollapsibleIndicator,\n CollapsibleTrigger,\n PreferencesFieldSet,\n PreferencesSections,\n RadioGroup,\n RadioGroupCard,\n SegmentGroup,\n Show,\n useKanzoTheme,\n} from \"@kanzo-tech/ui\";\nimport { useId } from \"react\";\nimport type { Channels } from \"../core/channels\";\nimport { useGraphPrefs } from \"../react/use-graph-prefs\";\nimport { lookFrom, PRESETS, type Look, type LookPreset } from \"../render/graph-looks\";\nimport { scaleOf } from \"../render/graph-model\";\nimport { GRAPH_SECTION } from \"../section\";\nimport { ShapeGlyph } from \"./shape-glyph\";\n\nexport type GraphLooksProps = React.ComponentProps<\"div\">;\n\nconst NAMESPACE = GRAPH_SECTION.namespace;\n\nconst CARDS: Record<LookPreset, { label: string; blurb: string; channels: Channels }> = {\n nebula: { label: \"Nebula\", blurb: \"Dense, dim points and additive links: a picture that reads as flow.\", channels: {} },\n atlas: {\n label: \"Atlas\",\n blurb: \"Map-steady points, links that just bow, generous labels.\",\n channels: { stroke: \"var(--muted-foreground)\" },\n },\n ink: {\n label: \"Ink\",\n blurb: \"Large, legible marks in one ink, identity on shape — the print-and-projector register.\",\n channels: { fill: \"var(--foreground)\", symbol: \"category\", stroke: \"var(--muted-foreground)\" },\n },\n};\n\n/** The axes under \"Customize\", after Marks; the forces are a live layout's and are not the picture. */\nconst CUSTOMIZE = [\"links\", \"labels\", \"additive-links\", \"bowed-links\", \"vignette\", \"grid\"] as const;\n\n/**\n * One small graph — the same seven vertices and eight edges on every card, so every pixel that differs\n * comes from the look. Glyphs are `ShapeGlyph` and colours `scaleOf`, the canvas's own, so a card\n * cannot promise a picture the canvas does not paint.\n */\nconst NODES = [\n { x: 22, y: 20, rank: 0, ramp: 1 },\n { x: 58, y: 8, rank: 1, ramp: 0.3 },\n { x: 58, y: 32, rank: 1, ramp: 0.3 },\n { x: 98, y: 20, rank: 2, ramp: 0.7 },\n { x: 140, y: 10, rank: 3, ramp: 0.45 },\n { x: 140, y: 31, rank: 2, ramp: 0.2 },\n { x: 184, y: 21, rank: 0, ramp: 0.85 },\n] as const;\n\nconst EDGES = [\n [0, 1],\n [0, 2],\n [1, 2],\n [0, 3],\n [3, 4],\n [3, 5],\n [4, 6],\n [5, 6],\n] as const;\n\n/** Screen pixels to the card's units, one factor for every look: Ink's larger floor is the look. */\nconst SCALE = 0.55;\n\nfunction LookPreview({ channels, look }: { channels: Channels; look: Look }) {\n const id = useId();\n const scale = scaleOf(channels);\n const radius = (ramp: number) => (look.size[0] + ramp * (look.size[1] - look.size[0])) * SCALE;\n return (\n <svg aria-hidden className=\"h-16 w-full rounded-[4px] border bg-background\" preserveAspectRatio=\"xMidYMid meet\" viewBox=\"0 0 208 40\">\n {EDGES.map(([from, to]) => {\n const a = NODES[from];\n const b = NODES[to];\n // cosmos.gl bows a link by a fraction of its length: the midpoint pushed along the perpendicular.\n const cx = (a.x + b.x) / 2 - (b.y - a.y) * look.link.curve;\n const cy = (a.y + b.y) / 2 + (b.x - a.x) * look.link.curve;\n return (\n <path\n d={`M${a.x} ${a.y}Q${cx} ${cy} ${b.x} ${b.y}`}\n fill=\"none\"\n key={`${from}-${to}`}\n stroke={channels.stroke ?? scale.color(a.rank)}\n strokeOpacity={look.link.opacity}\n strokeWidth={look.link.width}\n />\n );\n })}\n {NODES.map((node, i) => {\n const r = radius(node.ramp);\n return (\n <ShapeGlyph\n color={scale.color(node.rank)}\n height={r * 2}\n key={i}\n shape={scale.shape(node.rank)}\n width={r * 2}\n x={node.x - r}\n y={node.y - r}\n />\n );\n })}\n {look.vignette ? (\n <>\n <defs>\n <radialGradient id={id}>\n <stop offset=\"55%\" stopColor=\"var(--background)\" stopOpacity=\"0\" />\n <stop offset=\"100%\" stopColor=\"var(--background)\" stopOpacity=\"0.85\" />\n </radialGradient>\n </defs>\n <rect fill={`url(#${id})`} height=\"40\" width=\"208\" />\n </>\n ) : null}\n </svg>\n );\n}\n\n/**\n * **The graph's picture, as three looks and the axes under them** — GitHub's appearance settings:\n * pick by looking, then customise. The looks are `PRESETS` over `GRAPH_SECTION`'s axes; wearing one\n * writes every axis it names, and a look is checked exactly when the resolved axes are its own, so\n * there is no name stored beside them to go stale. \"Customize\" holds the axes themselves — Marks as\n * a segmented control, the rest as the section declares them — and opens on its own when the axes\n * are no preset.\n *\n * Under `KanzoThemeProvider` with `GRAPH_SECTION` among its `sections` — without it there is nothing\n * to write and it draws nothing, as `PreferencesSections` does for an unknown namespace. It needs no\n * `GraphRoot`, so it sits in a preferences panel as well as in a dock. A look whose axes a tenant pinned is disabled,\n * because wearing it would change nothing.\n */\nexport function GraphLooks({ className, slot, ...rest }: GraphLooksProps) {\n const { sectionPrefs, setSectionPref } = useKanzoTheme();\n const { preset } = useGraphPrefs();\n const prefs = sectionPrefs[NAMESPACE];\n if (!prefs) return null;\n const offered = (key: string) => prefs[key]?.offered === true;\n const marks = prefs.marks;\n const options = marks?.decl.kind === \"choice\" && Array.isArray(marks.decl.options) ? marks.decl.options : [];\n\n return (\n <div {...rest} className={cn(\"flex flex-col gap-4\", className)} data-slot={slot ?? \"graph-looks\"}>\n <PreferencesFieldSet label=\"Look\">\n <RadioGroup\n onValueChange={(details) => {\n const id = details.value as LookPreset | null;\n if (id) setSectionPref(NAMESPACE, PRESETS[id]);\n }}\n value={preset}\n >\n {(Object.keys(CARDS) as LookPreset[]).map((id) => (\n <RadioGroupCard\n className=\"flex-col gap-0 p-2\"\n disabled={!Object.keys(PRESETS[id]).every(offered)}\n key={id}\n value={id}\n >\n <LookPreview channels={CARDS[id].channels} look={lookFrom(PRESETS[id])} />\n <span className=\"mt-1.5 font-medium text-xs\">{CARDS[id].label}</span>\n <span className=\"mt-0.5 text-[10px] text-muted-foreground leading-relaxed\">{CARDS[id].blurb}</span>\n </RadioGroupCard>\n ))}\n </RadioGroup>\n </PreferencesFieldSet>\n\n <Collapsible defaultOpen={preset === null}>\n <CollapsibleTrigger asChild>\n <Button className=\"w-full\" size=\"sm\" variant=\"ghost\">\n Customize\n <Show when={preset === null}>\n <span className=\"ms-auto me-1 font-normal text-muted-foreground text-xs\">Custom</span>\n </Show>\n <CollapsibleIndicator />\n </Button>\n </CollapsibleTrigger>\n <CollapsibleContent className=\"flex flex-col gap-4 pt-3\">\n <Show when={offered(\"marks\")}>\n <PreferencesFieldSet label={marks?.decl.label ?? \"Marks\"}>\n <SegmentGroup\n onValueChange={(details) => details.value && setSectionPref(NAMESPACE, { marks: details.value })}\n options={options}\n size=\"sm\"\n value={marks?.value ?? null}\n variant=\"solid\"\n />\n </PreferencesFieldSet>\n </Show>\n <PreferencesSections namespace={NAMESPACE} only={CUSTOMIZE} />\n </CollapsibleContent>\n </Collapsible>\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;AA2BA;AAEwF;AAC8B;AAC7G;AACE;AACA;AACa;AAA0B;AAE3C;AACI;AACA;AAC4D;AAEvE;AAUc;AACmB;AACD;AACC;AACA;AACC;AACA;AAElC;AAEc;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AAEP;AAKA;AACE;AAGA;AAEK;AACC;AAKA;AACE;AAAC;AAAA;AAC4C;AACtC;AAEwC;AACpB;AACF;AAAA;AAHL;AAAA;AAMvB;AAEC;AACA;AACE;AAAC;AAAA;AAC6B;AAChB;AAEgB;AACjB;AACC;AACA;AAAA;AAJP;AAAA;AAOV;AAGG;AAEI;AAAiE;AACI;AAEzE;AACmD;AAEnD;AAGV;AAeO;AACL;AAGA;AACA;;AAAiC;AAAwB;AAIzD;AAEI;AACE;AAAC;AAAA;AAEG;AACA;AAA6C;AAC/C;AACO;AAGL;AAAC;AAAA;AACW;AACuC;AAE1C;AAEP;AAAwE;AACV;AAC8B;AAAA;AAAA;AALvF;AAOR;AAAA;AAEL;AAGE;AACuD;AAAA;AAInD;AACsB;AAE1B;AAEE;AAEI;AAAC;AAAA;AACgG;AAC/F;AACK;AACkB;AACf;AAAA;AAGd;AAC4D;AAC9D;AACF;AAGN;;;;"}
@@ -0,0 +1,17 @@
1
+ import { ComboboxInput } from '@kanzo-tech/ui';
2
+ export interface GraphSearchProps extends Pick<React.ComponentProps<typeof ComboboxInput>, "className" | "placeholder" | "size"> {
3
+ /** How many matches the list shows; past it, the list says to keep typing. */
4
+ limit?: number;
5
+ }
6
+ /**
7
+ * **Find a vertex by its text and go to it.** Every drawn vertex's `title` — or its table's
8
+ * `identity` — is read once, then filtered in the browser as the reader types: no query per
9
+ * keystroke. The list is ordered by the `r` ramp, so with `r="degree"` the hubs come first, and each
10
+ * match is named by the root's `categories`. Picking one is `reveal`: the canvas frames it and
11
+ * selects it with its neighbours, and `GraphInspector` reads it.
12
+ *
13
+ * A vertex the page's filter hides is not offered, because there is nothing on the canvas to go to.
14
+ * A read that fails is handed to `onFailure` whole and the input says so, rather than staying disabled.
15
+ */
16
+ export declare function GraphSearch({ className, limit, placeholder, size }: GraphSearchProps): import("react").JSX.Element;
17
+ //# sourceMappingURL=graph-search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-search.d.ts","sourceRoot":"","sources":["../../src/parts/graph-search.tsx"],"names":[],"mappings":"AAEA,OAAO,EAIL,aAAa,EAMd,MAAM,gBAAgB,CAAC;AAUxB,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,aAAa,CAAC,EAAE,WAAW,GAAG,aAAa,GAAG,MAAM,CAAC;IAC9H,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAmCD;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,EAAE,SAAS,EAAE,KAAU,EAAE,WAA4B,EAAE,IAAW,EAAE,EAAE,gBAAgB,+BAkFjH"}
@@ -0,0 +1,89 @@
1
+ "use client";
2
+ import { jsxs as h, jsx as r } from "react/jsx-runtime";
3
+ import { useFilter as z, createListCollection as A, Combobox as O, ComboboxInput as q, InputGroupAddon as K, ComboboxContent as L, ComboboxEmpty as M, ComboboxItem as Q, Show as E } from "@kanzo-tech/ui";
4
+ import { SearchIcon as R } from "lucide-react";
5
+ import { useState as T, useEffect as $, useMemo as V } from "react";
6
+ import { nameOf as B } from "../core/categories.js";
7
+ import { bindingOf as D } from "../core/channels.js";
8
+ import { readEveryTitle as H } from "../core/detail.js";
9
+ import { useGraphContext as J } from "../react/graph-root.js";
10
+ import { useGraphState as N, useGraphSnapshot as v } from "../react/use-graph-state.js";
11
+ function P(m, s, d, f, a) {
12
+ const i = [];
13
+ for (let t = 0; t < m.size; t++)
14
+ (!d || d[t]) && !Number.isNaN(m.positions[t * 2]) && i.push(t);
15
+ const { sizes: l } = s;
16
+ if (l) {
17
+ const t = (o) => Number.isNaN(l[o]) ? -1 / 0 : l[o];
18
+ i.sort((o, c) => t(c) - t(o) || o - c);
19
+ }
20
+ return i.map((t) => ({
21
+ value: String(t),
22
+ label: f[t] || `#${t}`,
23
+ kind: a ? a(s.ranks[t]) : null
24
+ }));
25
+ }
26
+ function oe({ className: m, limit: s = 50, placeholder: d = "Find a node…", size: f = "sm" }) {
27
+ const a = J(), i = N((e) => e.corpus), l = N((e) => e.options), t = v((e) => e.geometry), o = v((e) => e.encoding), c = v((e) => e.mask), w = N((e) => {
28
+ var n;
29
+ return ((n = e.drawn) == null ? void 0 : n.domain) ?? e.domain;
30
+ }), [p, S] = T(null), [b, j] = T(""), { contains: k } = z({ sensitivity: "base" }), { title: u, categories: I } = l;
31
+ $(() => {
32
+ if (!i || !t) return;
33
+ const e = new AbortController();
34
+ return H(i, t, u, e.signal).then(
35
+ (n) => !e.signal.aborted && S({ geometry: t, title: u, titles: n }),
36
+ (n) => {
37
+ e.signal.aborted || (S({ geometry: t, title: u, titles: !1 }), a.getState().options.onFailure(n));
38
+ }
39
+ ), () => e.abort();
40
+ }, [a, i, t, u]);
41
+ const g = p && p.geometry === t && p.title === u ? p.titles : null, x = g || null, G = D(l), F = G.byTable || G.category !== void 0, y = V(() => !t || !o || !x ? null : P(t, o, c, x, F ? (e) => B(w[e], I) : null), [t, o, c, x, F, w, I]), C = V(() => {
42
+ const e = [];
43
+ for (const n of y ?? []) {
44
+ if (e.length === s) break;
45
+ (!b || k(n.label, b)) && e.push(n);
46
+ }
47
+ return A({ items: e });
48
+ }, [y, b, k, s]);
49
+ return /* @__PURE__ */ h(
50
+ O,
51
+ {
52
+ collection: C,
53
+ disabled: y === null,
54
+ onInputValueChange: (e) => j(e.inputValue),
55
+ onValueChange: (e) => {
56
+ const n = e.value[0];
57
+ n !== void 0 && a.reveal(Number(n));
58
+ },
59
+ children: [
60
+ /* @__PURE__ */ r(
61
+ q,
62
+ {
63
+ "aria-invalid": g === !1 || void 0,
64
+ className: m,
65
+ placeholder: g === !1 ? "The names could not be read." : d,
66
+ size: f,
67
+ children: /* @__PURE__ */ r(K, { align: "inline-start", children: /* @__PURE__ */ r(R, {}) })
68
+ }
69
+ ),
70
+ /* @__PURE__ */ h(L, { children: [
71
+ /* @__PURE__ */ r(M, { children: "Nothing drawn by that name." }),
72
+ C.items.map((e) => /* @__PURE__ */ h(Q, { item: e, children: [
73
+ /* @__PURE__ */ r("span", { className: "min-w-0 truncate", children: e.label }),
74
+ /* @__PURE__ */ r(E, { when: e.kind !== null, children: /* @__PURE__ */ r("span", { className: "ms-auto ps-2 text-muted-foreground text-xs", children: e.kind }) })
75
+ ] }, e.value)),
76
+ /* @__PURE__ */ r(E, { when: C.items.length === s, children: /* @__PURE__ */ h("p", { className: "border-t px-2 py-1.5 text-muted-foreground text-xs", children: [
77
+ "First ",
78
+ s,
79
+ ". Keep typing to narrow it."
80
+ ] }) })
81
+ ] })
82
+ ]
83
+ }
84
+ );
85
+ }
86
+ export {
87
+ oe as GraphSearch
88
+ };
89
+ //# sourceMappingURL=graph-search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-search.js","sources":["../../src/parts/graph-search.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n Combobox,\n ComboboxContent,\n ComboboxEmpty,\n ComboboxInput,\n ComboboxItem,\n createListCollection,\n InputGroupAddon,\n Show,\n useFilter,\n} from \"@kanzo-tech/ui\";\nimport { SearchIcon } from \"lucide-react\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport { nameOf } from \"../core/categories\";\nimport { bindingOf } from \"../core/channels\";\nimport { readEveryTitle } from \"../core/detail\";\nimport type { Encoding, Geometry } from \"../core/load\";\nimport { useGraphContext } from \"../react/graph-root\";\nimport { useGraphSnapshot, useGraphState } from \"../react/use-graph-state\";\n\nexport interface GraphSearchProps extends Pick<React.ComponentProps<typeof ComboboxInput>, \"className\" | \"placeholder\" | \"size\"> {\n /** How many matches the list shows; past it, the list says to keep typing. */\n limit?: number;\n}\n\ninterface Entry {\n value: string;\n label: string;\n kind: string | null;\n}\n\n/** The titles read for one geometry and title column, or `false` when the read failed. */\ntype Titles = { geometry: Geometry; title: string | undefined; titles: string[] | false } | null;\n\n/** Every drawn vertex, biggest on the `r` ramp first — the vertices a reader most likely means. */\nfunction entriesOf(\n geometry: Geometry,\n encoding: Encoding,\n mask: Uint8Array | null,\n titles: readonly string[],\n kindOf: ((rank: number) => string) | null,\n): Entry[] {\n const ids: number[] = [];\n for (let id = 0; id < geometry.size; id++) {\n if ((!mask || mask[id]) && !Number.isNaN(geometry.positions[id * 2])) ids.push(id);\n }\n const { sizes } = encoding;\n if (sizes) {\n const ramp = (id: number) => (Number.isNaN(sizes[id]) ? -Infinity : (sizes[id] as number));\n ids.sort((a, b) => ramp(b) - ramp(a) || a - b);\n }\n return ids.map((id) => ({\n value: String(id),\n label: titles[id] || `#${id}`,\n kind: kindOf ? kindOf(encoding.ranks[id] as number) : null,\n }));\n}\n\n/**\n * **Find a vertex by its text and go to it.** Every drawn vertex's `title` — or its table's\n * `identity` — is read once, then filtered in the browser as the reader types: no query per\n * keystroke. The list is ordered by the `r` ramp, so with `r=\"degree\"` the hubs come first, and each\n * match is named by the root's `categories`. Picking one is `reveal`: the canvas frames it and\n * selects it with its neighbours, and `GraphInspector` reads it.\n *\n * A vertex the page's filter hides is not offered, because there is nothing on the canvas to go to.\n * A read that fails is handed to `onFailure` whole and the input says so, rather than staying disabled.\n */\nexport function GraphSearch({ className, limit = 50, placeholder = \"Find a node…\", size = \"sm\" }: GraphSearchProps) {\n const api = useGraphContext();\n const corpus = useGraphState((s) => s.corpus);\n const options = useGraphState((s) => s.options);\n const geometry = useGraphSnapshot((s) => s.geometry);\n const encoding = useGraphSnapshot((s) => s.encoding);\n const mask = useGraphSnapshot((s) => s.mask);\n const domain = useGraphState((s) => s.drawn?.domain ?? s.domain);\n const [read, setRead] = useState<Titles>(null);\n const [query, setQuery] = useState(\"\");\n const { contains } = useFilter({ sensitivity: \"base\" });\n const { title, categories } = options;\n\n useEffect(() => {\n if (!corpus || !geometry) return;\n const aborter = new AbortController();\n readEveryTitle(corpus, geometry, title, aborter.signal).then(\n (titles) => !aborter.signal.aborted && setRead({ geometry, title, titles }),\n (error: unknown) => {\n if (aborter.signal.aborted) return;\n setRead({ geometry, title, titles: false });\n api.getState().options.onFailure(error);\n },\n );\n return () => aborter.abort();\n }, [api, corpus, geometry, title]);\n\n const answered = read && read.geometry === geometry && read.title === title ? read.titles : null;\n const titles = answered || null;\n const binding = bindingOf(options);\n const bound = binding.byTable || binding.category !== undefined;\n\n const entries = useMemo(() => {\n if (!geometry || !encoding || !titles) return null;\n return entriesOf(geometry, encoding, mask, titles, bound ? (rank) => nameOf(domain[rank], categories) : null);\n }, [geometry, encoding, mask, titles, bound, domain, categories]);\n\n const collection = useMemo(() => {\n const matches: Entry[] = [];\n for (const entry of entries ?? []) {\n if (matches.length === limit) break;\n if (!query || contains(entry.label, query)) matches.push(entry);\n }\n return createListCollection({ items: matches });\n }, [entries, query, contains, limit]);\n\n return (\n <Combobox\n collection={collection}\n disabled={entries === null}\n onInputValueChange={(details) => setQuery(details.inputValue)}\n onValueChange={(details) => {\n const picked = details.value[0];\n if (picked !== undefined) api.reveal(Number(picked));\n }}\n >\n <ComboboxInput\n aria-invalid={answered === false || undefined}\n className={className}\n placeholder={answered === false ? \"The names could not be read.\" : placeholder}\n size={size}\n >\n <InputGroupAddon align=\"inline-start\">\n <SearchIcon />\n </InputGroupAddon>\n </ComboboxInput>\n <ComboboxContent>\n <ComboboxEmpty>Nothing drawn by that name.</ComboboxEmpty>\n {collection.items.map((item) => (\n <ComboboxItem item={item} key={item.value}>\n <span className=\"min-w-0 truncate\">{item.label}</span>\n <Show when={item.kind !== null}>\n <span className=\"ms-auto ps-2 text-muted-foreground text-xs\">{item.kind}</span>\n </Show>\n </ComboboxItem>\n ))}\n <Show when={collection.items.length === limit}>\n <p className=\"border-t px-2 py-1.5 text-muted-foreground text-xs\">First {limit}. Keep typing to narrow it.</p>\n </Show>\n </ComboboxContent>\n </Combobox>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;AAqCA;AAOE;AACA;AACE;AAEF;AACA;AACE;AACA;AAA6C;AAE/C;AAAwB;AACN;AACW;AAC2B;AAE1D;AAYO;AACL;;AAMoC;AAAqB;AAMzD;AACE;AACA;AACA;AAAwD;AACoB;AAExE;AAEsC;AACxC;AAEmB;AAGvB;AAWE;AACA;AACE;AACA;AAA8D;AAEhE;AAA8C;AAGhD;AACE;AAAC;AAAA;AACC;AACsB;AACsC;AAE1D;AACA;AAAmD;AACrD;AAEA;AAAA;AAAC;AAAA;AACqC;AACpC;AACmE;AACnE;AAIA;AAAA;AAAA;AAGA;AAA0C;AAGtC;AAA+C;AAG/C;AAEH;AAEmE;AAAA;AAAO;AAAM;AACjF;AACF;AAAA;AAAA;AAGN;;;;"}
@@ -1,4 +1,4 @@
1
- import { Look } from '../render/graph-looks';
1
+ import { Look, LookPreset } from '../render/graph-looks';
2
2
  import { Sim } from '../render/graph-sim';
3
3
  /**
4
4
  * The graph section's resolved preferences, as the two objects `useGraph` takes.
@@ -9,11 +9,15 @@ import { Sim } from '../render/graph-sim';
9
9
  * the keys, the defaults and the bounds are all `GRAPH_SECTION`'s, and the resolution chain is the
10
10
  * provider's. What reaches `lookFrom`/`simFrom` is the resolved value, never the stored one.
11
11
  *
12
+ * `preset` is the named look the axes are, for a host that pairs channels with it — Ink spends
13
+ * identity on shape — and `null` once a reader has customised past all three.
14
+ *
12
15
  * Needs `KanzoThemeProvider` with `GRAPH_SECTION` among its `sections`. Without it the namespace
13
- * resolves to nothing and both objects are the manifest's defaults.
16
+ * resolves to nothing, both objects are the manifest's defaults and the preset is Atlas.
14
17
  */
15
18
  export declare function useGraphPrefs(): {
16
19
  look: Look;
17
20
  sim: Sim;
21
+ preset: LookPreset | null;
18
22
  };
19
23
  //# sourceMappingURL=use-graph-prefs.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-graph-prefs.d.ts","sourceRoot":"","sources":["../../src/react/use-graph-prefs.ts"],"names":[],"mappings":"AAIA,OAAO,EAAY,KAAK,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAW,KAAK,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAGxD;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,IAAI;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,GAAG,CAAA;CAAE,CASxD"}
1
+ {"version":3,"file":"use-graph-prefs.d.ts","sourceRoot":"","sources":["../../src/react/use-graph-prefs.ts"],"names":[],"mappings":"AAIA,OAAO,EAAsB,KAAK,IAAI,EAAE,KAAK,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACvF,OAAO,EAAW,KAAK,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAGxD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,IAAI;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,GAAG,CAAC;IAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAAA;CAAE,CASnF"}
@@ -1,17 +1,17 @@
1
1
  "use client";
2
2
  import { useMemo as t } from "react";
3
3
  import { useKanzoTheme as s } from "@kanzo-tech/ui";
4
- import { lookFrom as i } from "../render/graph-looks.js";
5
- import { simFrom as n } from "../render/graph-sim.js";
4
+ import { presetOf as i, lookFrom as n } from "../render/graph-looks.js";
5
+ import { simFrom as p } from "../render/graph-sim.js";
6
6
  import { GRAPH_SECTION as f } from "../section.js";
7
7
  function v() {
8
- const o = s().sectionPrefs[f.namespace];
8
+ const r = s().sectionPrefs[f.namespace];
9
9
  return t(() => {
10
- const r = Object.fromEntries(
11
- Object.entries(o ?? {}).map(([e, m]) => [e, m.value])
10
+ const e = Object.fromEntries(
11
+ Object.entries(r ?? {}).map(([o, m]) => [o, m.value])
12
12
  );
13
- return { look: i(r), sim: n(r) };
14
- }, [o]);
13
+ return { look: n(e), sim: p(e), preset: i(e) };
14
+ }, [r]);
15
15
  }
16
16
  export {
17
17
  v as useGraphPrefs
@@ -1 +1 @@
1
- {"version":3,"file":"use-graph-prefs.js","sources":["../../src/react/use-graph-prefs.ts"],"sourcesContent":["\"use client\";\n\nimport { useMemo } from \"react\";\nimport { useKanzoTheme } from \"@kanzo-tech/ui\";\nimport { lookFrom, type Look } from \"../render/graph-looks\";\nimport { simFrom, type Sim } from \"../render/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
+ {"version":3,"file":"use-graph-prefs.js","sources":["../../src/react/use-graph-prefs.ts"],"sourcesContent":["\"use client\";\n\nimport { useMemo } from \"react\";\nimport { useKanzoTheme } from \"@kanzo-tech/ui\";\nimport { lookFrom, presetOf, type Look, type LookPreset } from \"../render/graph-looks\";\nimport { simFrom, type Sim } from \"../render/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 * `preset` is the named look the axes are, for a host that pairs channels with it — Ink spends\n * identity on shape — and `null` once a reader has customised past all three.\n *\n * Needs `KanzoThemeProvider` with `GRAPH_SECTION` among its `sections`. Without it the namespace\n * resolves to nothing, both objects are the manifest's defaults and the preset is Atlas.\n */\nexport function useGraphPrefs(): { look: Look; sim: Sim; preset: LookPreset | null } {\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), preset: presetOf(values) };\n }, [resolved]);\n}\n"],"names":[],"mappings":";;;;;;AAuBO;AACL;AAEA;AACE;AAAsB;AACiD;AAEvE;AAA8E;AAElF;;;;"}