@kanzo-tech/graph 0.19.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 (56) hide show
  1. package/README.md +25 -7
  2. package/dist/core/detail.d.ts +2 -0
  3. package/dist/core/detail.d.ts.map +1 -1
  4. package/dist/core/detail.js +51 -30
  5. package/dist/core/detail.js.map +1 -1
  6. package/dist/core/state.d.ts +7 -0
  7. package/dist/core/state.d.ts.map +1 -1
  8. package/dist/core/store.d.ts.map +1 -1
  9. package/dist/core/store.js +112 -108
  10. package/dist/core/store.js.map +1 -1
  11. package/dist/core/types.d.ts +4 -4
  12. package/dist/core/types.d.ts.map +1 -1
  13. package/dist/index.d.ts +7 -3
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +24 -18
  16. package/dist/index.js.map +1 -1
  17. package/dist/parts/graph-counts.d.ts +15 -0
  18. package/dist/parts/graph-counts.d.ts.map +1 -0
  19. package/dist/parts/graph-counts.js +41 -0
  20. package/dist/parts/graph-counts.js.map +1 -0
  21. package/dist/parts/graph-legend.d.ts +3 -2
  22. package/dist/parts/graph-legend.d.ts.map +1 -1
  23. package/dist/parts/graph-legend.js +30 -48
  24. package/dist/parts/graph-legend.js.map +1 -1
  25. package/dist/parts/graph-looks.d.ts +16 -0
  26. package/dist/parts/graph-looks.d.ts.map +1 -0
  27. package/dist/parts/graph-looks.js +138 -0
  28. package/dist/parts/graph-looks.js.map +1 -0
  29. package/dist/parts/graph-search.d.ts +17 -0
  30. package/dist/parts/graph-search.d.ts.map +1 -0
  31. package/dist/parts/graph-search.js +89 -0
  32. package/dist/parts/graph-search.js.map +1 -0
  33. package/dist/react/use-graph-prefs.d.ts +6 -2
  34. package/dist/react/use-graph-prefs.d.ts.map +1 -1
  35. package/dist/react/use-graph-prefs.js +7 -7
  36. package/dist/react/use-graph-prefs.js.map +1 -1
  37. package/dist/render/camera.d.ts +26 -0
  38. package/dist/render/camera.d.ts.map +1 -0
  39. package/dist/render/camera.js +40 -0
  40. package/dist/render/camera.js.map +1 -0
  41. package/dist/render/graph-looks.d.ts +10 -0
  42. package/dist/render/graph-looks.d.ts.map +1 -1
  43. package/dist/render/graph-looks.js +37 -20
  44. package/dist/render/graph-looks.js.map +1 -1
  45. package/dist/render/placement.d.ts +23 -0
  46. package/dist/render/placement.d.ts.map +1 -0
  47. package/dist/render/placement.js +23 -0
  48. package/dist/render/placement.js.map +1 -0
  49. package/dist/render/renderer.d.ts.map +1 -1
  50. package/dist/render/renderer.js +109 -100
  51. package/dist/render/renderer.js.map +1 -1
  52. package/dist/render/webgl.d.ts +5 -4
  53. package/dist/render/webgl.d.ts.map +1 -1
  54. package/dist/render/webgl.js +14 -11
  55. package/dist/render/webgl.js.map +1 -1
  56. package/package.json +7 -6
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","sources":["../../src/core/store.ts"],"sourcesContent":["import { GraphError } from \"./error\";\nimport type { MosaicClient } from \"@kanzo-tech/mosaic\";\nimport { domainOf } from \"./categories\";\nimport { bindingOf } from \"./channels\";\nimport type { Corpus, Filter, VertexTable } from \"@fossil-lang/corpus\";\nimport { filterFor, graphClient, publish } from \"./filter\";\nimport { drawnTables, loadEncoding, loadGraph, maskOf, readKept, type Encoding, type Geometry, type Kept } from \"./load\";\nimport type { Drawn, GraphOptions, GraphSnapshot, GraphStatus, GraphStore } from \"./state\";\n\nexport type { Drawn, GraphOptions, GraphSnapshot, GraphState, GraphStatus, GraphStore } from \"./state\";\n\nconst sameFilter = (a: Filter | undefined, b: Filter | undefined) =>\n JSON.stringify(a, (_, v: unknown) => (typeof v === \"bigint\" ? `${v}n` : v)) ===\n JSON.stringify(b, (_, v: unknown) => (typeof v === \"bigint\" ? `${v}n` : v));\n\nconst isPromise = (value: unknown): value is PromiseLike<Corpus> =>\n typeof (value as PromiseLike<Corpus> | null)?.then === \"function\";\n\nfunction drawnOf(geometry: Geometry, encoding: Encoding, mask: Uint8Array | null): Drawn {\n const tally = encoding.domain.map(() => 0);\n let vertices = 0;\n for (let id = 0; id < geometry.size; id++) {\n if ((mask && !mask[id]) || Number.isNaN(geometry.positions[id * 2])) continue;\n vertices++;\n const rank = encoding.ranks[id] as number;\n tally[rank] = (tally[rank] ?? 0) + 1;\n }\n return { vertices, domain: encoding.domain, tally };\n}\n\nexport function createGraph(initial: GraphOptions): GraphStore {\n let options = initial;\n const listeners = new Set<() => void>();\n const self: MosaicClient = graphClient();\n\n let corpus: Corpus | null = null;\n let opening: PromiseLike<Corpus> | null = null;\n let failed = false;\n let unrenderable = false;\n let tables: readonly VertexTable[] = [];\n let binding = bindingOf(initial);\n let filter: Filter | undefined;\n let loading: AbortController | null = null;\n let filtering: AbortController | null = null;\n let geometry: Geometry | null = null;\n let encoding: Encoding | null = null;\n let kept: Kept | null = null;\n let mask: Uint8Array | null = null;\n let uploaded: readonly unknown[] = [];\n let unlisten: (() => void) | null = null;\n let active = false;\n\n // A failure found while the store is being built is found while React is still rendering —\n // `useGraph` builds it in `useState` — and a host's `onFailure` is usually a `setState`. It is held\n // and reported to the first subscriber, which arrives in the commit phase.\n let building = true;\n let held: { error: unknown } | null = null;\n const fail = (error: unknown) => {\n if (building) held = { error };\n else options.onFailure(error);\n };\n\n let snapshot: GraphSnapshot = {\n status: \"none\",\n total: undefined,\n drawn: null,\n domain: [],\n selection: null,\n focus: null,\n hovered: null,\n pinned: [],\n tool: null,\n motion: \"settled\",\n progress: 1,\n options,\n corpus: null,\n binding,\n geometry: null,\n encoding: null,\n mask: null,\n };\n\n let domain: { key: unknown[]; value: readonly unknown[] } = { key: [], value: [] };\n function domainNow(): readonly unknown[] {\n const key = [tables, binding.byTable, binding.category, options.categories];\n if (key.some((part, i) => part !== domain.key[i])) domain = { key, value: domainOf(tables, binding, options.categories) };\n return domain.value;\n }\n\n let drawn: { key: unknown[]; value: Drawn | null } = { key: [], value: null };\n function drawnNow(): Drawn | null {\n const key = [geometry, encoding, mask];\n if (key.some((part, i) => part !== drawn.key[i])) {\n drawn = { key, value: geometry && encoding ? drawnOf(geometry, encoding, mask) : null };\n }\n return drawn.value;\n }\n\n function statusOf(): GraphStatus {\n if (failed || unrenderable) return \"failed\";\n if (opening) return \"opening\";\n if (!corpus) return \"none\";\n const current = [geometry, encoding, mask];\n const shown = geometry !== null && current.every((part, i) => part === uploaded[i]);\n return loading || filtering || !shown ? \"loading\" : \"idle\";\n }\n\n function notify(fields: Partial<GraphSnapshot> = {}): void {\n snapshot = {\n ...snapshot,\n total: corpus ? tables.reduce((sum, table) => sum + table.record_count, 0) : undefined,\n drawn: drawnNow(),\n domain: domainNow(),\n options,\n corpus,\n binding,\n geometry,\n encoding,\n mask,\n ...fields,\n };\n snapshot = { ...snapshot, status: statusOf() };\n emit();\n }\n\n /** A change of state only — hover, focus, selection, tool, motion — derives nothing. */\n function patch(fields: Partial<GraphSnapshot>): void {\n snapshot = { ...snapshot, ...fields };\n emit();\n }\n\n function emit(): void {\n for (const listener of listeners) listener();\n }\n\n /**\n * One read in flight per kind, and a newer ask aborts the older one's statements. A load that\n * fails leaves nothing to draw; a filter that fails leaves the last picture.\n */\n function run<T>(\n current: () => AbortController | null,\n set: (aborter: AbortController | null) => void,\n fatal: boolean,\n read: (signal: AbortSignal) => Promise<T>,\n done: (value: T) => void,\n ): void {\n current()?.abort();\n const aborter = new AbortController();\n set(aborter);\n read(aborter.signal).then(\n (value) => {\n if (current() !== aborter) return;\n set(null);\n done(value);\n notify();\n },\n (error: unknown) => {\n // An aborted read is one this store cancelled, not a failure of the corpus.\n if (current() !== aborter || aborter.signal.aborted) return;\n set(null);\n failed ||= fatal;\n fail(error);\n notify();\n },\n );\n }\n\n const asLoad = [() => loading, (a: AbortController | null) => (loading = a), true] as const;\n const asFilter = [() => filtering, (a: AbortController | null) => (filtering = a), false] as const;\n\n function load(): void {\n const open = corpus;\n if (!open) return;\n const seed = domainNow();\n const given = geometry;\n if (given) {\n return run(...asLoad, (signal) => loadEncoding(open, given, binding, seed, signal), (next) => {\n encoding = next;\n });\n }\n run(...asLoad, (signal) => loadGraph(open, binding, seed, signal), (next) => {\n geometry = next.geometry;\n encoding = next.encoding;\n mask = geometry && kept ? maskOf(geometry, kept) : null;\n });\n }\n\n function refilter(): void {\n filtering?.abort();\n filtering = null;\n const open = corpus;\n if (!open || tables.length === 0) return;\n if (filter === undefined) {\n kept = null;\n mask = null;\n return;\n }\n const given = filter;\n run(...asFilter, (signal) => readKept(open, tables, given, signal), (next) => {\n kept = next;\n mask = geometry ? maskOf(geometry, next) : null;\n });\n }\n\n function reopen(): void {\n loading?.abort();\n filtering?.abort();\n loading = filtering = null;\n failed = false;\n geometry = encoding = mask = kept = null;\n uploaded = [];\n tables = [];\n const cleared = { selection: null, focus: null, hovered: null, pinned: [] };\n if (!corpus) return notify(cleared);\n try {\n tables = drawnTables(corpus);\n if (tables.length === 0) throw new GraphError(\"graph/nothing-to-draw\", \"the corpus has no vertex type with a position to draw\");\n } catch (error) {\n failed = true;\n fail(error);\n return notify(cleared);\n }\n load();\n refilter();\n notify(cleared);\n }\n\n /** A promise is adopted when it settles, and only if it is still the corpus the host means. */\n function adopt(given: GraphOptions[\"corpus\"]): void {\n corpus = null;\n opening = isPromise(given) ? given : null;\n if (!opening) {\n corpus = given as Corpus | null;\n return reopen();\n }\n const promised = opening;\n reopen();\n promised.then(\n (opened) => {\n if (opening !== promised) return;\n opening = null;\n corpus = opened;\n reopen();\n },\n (error: unknown) => {\n if (opening !== promised) return;\n opening = null;\n failed = true;\n fail(error);\n notify();\n },\n );\n }\n\n function listen(): void {\n unlisten?.();\n unlisten = null;\n const crossfilter = options.filterBy;\n const changed = () => {\n let next: Filter | undefined;\n try {\n next = crossfilter ? filterFor(crossfilter, self) : undefined;\n } catch (error) {\n fail(error);\n return;\n }\n if (sameFilter(next, filter)) return;\n filter = next;\n refilter();\n notify();\n };\n if (crossfilter) {\n crossfilter.addEventListener(\"value\", changed);\n unlisten = () => crossfilter.removeEventListener(\"value\", changed);\n }\n changed();\n }\n\n const store: GraphStore = {\n /**\n * The first subscriber starts listening to the crossfilter and the last one stops it and lets the\n * graph go — `QueryObserver`'s `onSubscribe`/`onUnsubscribe`, which is what survives StrictMode\n * mounting everything twice.\n */\n subscribe(listener) {\n listeners.add(listener);\n if (!active) {\n active = true;\n listen();\n // StrictMode unsubscribes and subscribes again: what the unsubscribe cancelled starts over.\n if (corpus && !failed && tables.length > 0) {\n if (!loading && (!geometry || !encoding)) load();\n if (!filtering && filter !== undefined && !kept) refilter();\n }\n if (held !== null) {\n const { error } = held;\n held = null;\n options.onFailure(error);\n }\n }\n return () => {\n listeners.delete(listener);\n if (listeners.size === 0) store.destroy();\n };\n },\n getSnapshot: () => snapshot,\n getOptions: () => options,\n setOptions(next) {\n const previous = options;\n options = next;\n const nextBinding = bindingOf(next);\n const rebound =\n nextBinding.category !== binding.category ||\n nextBinding.byTable !== binding.byTable ||\n nextBinding.size !== binding.size ||\n next.categories !== previous.categories;\n binding = nextBinding;\n if (next.corpus !== previous.corpus) {\n adopt(next.corpus);\n if (active && next.filterBy !== previous.filterBy) listen();\n return;\n }\n if (active && next.filterBy !== previous.filterBy) listen();\n if (rebound) load();\n notify();\n },\n destroy() {\n active = false;\n unlisten?.();\n unlisten = null;\n loading?.abort();\n filtering?.abort();\n loading = filtering = null;\n listeners.clear();\n },\n select(vertices, source = \"node\", label = \"\") {\n const selection = vertices && vertices.length > 0 ? { vertices: [...vertices], source, label } : null;\n patch({ selection });\n options.onSelect?.(selection);\n const key = tables[0]?.key;\n if (options.filterBy && key) publish(options.filterBy, self, key, selection ? selection.vertices : null);\n },\n focus(vertex) {\n if (vertex === snapshot.focus) return;\n patch({ focus: vertex });\n options.onFocus?.(vertex);\n },\n hover(vertex) {\n if (vertex !== snapshot.hovered) patch({ hovered: vertex });\n },\n pin(vertices) {\n patch({ pinned: [...vertices] });\n },\n setTool(tool) {\n if (tool !== snapshot.tool) patch({ tool });\n },\n report(motion) {\n if (motion !== snapshot.motion) patch({ motion });\n },\n reportProgress(value) {\n if (value !== snapshot.progress) patch({ progress: value });\n },\n renderable() {\n if (!unrenderable) return;\n unrenderable = false;\n notify();\n },\n unrenderable(error) {\n unrenderable = true;\n fail(error);\n notify();\n },\n reportDrawn(drawnSnapshot) {\n const next = [drawnSnapshot.geometry, drawnSnapshot.encoding, drawnSnapshot.mask];\n if (next.every((part, i) => part === uploaded[i])) return;\n uploaded = next;\n notify();\n },\n };\n\n adopt(initial.corpus);\n building = false;\n return store;\n}\n"],"names":["sameFilter","b","_","v","isPromise","value","drawnOf","geometry","encoding","mask","tally","vertices","id","rank","createGraph","initial","options","listeners","self","graphClient","corpus","opening","failed","unrenderable","tables","binding","bindingOf","filter","loading","filtering","kept","uploaded","unlisten","active","building","held","fail","error","snapshot","domain","domainNow","key","part","i","domainOf","drawn","drawnNow","statusOf","shown","notify","fields","sum","table","emit","patch","listener","run","current","set","fatal","read","done","_a","aborter","asLoad","a","asFilter","load","open","seed","given","signal","loadEncoding","next","loadGraph","maskOf","refilter","readKept","reopen","cleared","drawnTables","GraphError","adopt","promised","opened","listen","crossfilter","changed","filterFor","store","previous","nextBinding","rebound","source","label","selection","_b","publish","vertex","tool","motion","drawnSnapshot"],"mappings":"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"}
1
+ {"version":3,"file":"store.js","sources":["../../src/core/store.ts"],"sourcesContent":["import { GraphError } from \"./error\";\nimport type { MosaicClient } from \"@kanzo-tech/mosaic\";\nimport { domainOf } from \"./categories\";\nimport { bindingOf } from \"./channels\";\nimport type { Corpus, Filter, VertexTable } from \"@fossil-lang/corpus\";\nimport { filterFor, graphClient, publish } from \"./filter\";\nimport { drawnTables, loadEncoding, loadGraph, maskOf, readKept, type Encoding, type Geometry, type Kept } from \"./load\";\nimport type { Drawn, GraphOptions, GraphSnapshot, GraphStatus, GraphStore } from \"./state\";\n\nexport type { Drawn, GraphOptions, GraphSnapshot, GraphState, GraphStatus, GraphStore } from \"./state\";\n\nconst sameFilter = (a: Filter | undefined, b: Filter | undefined) =>\n JSON.stringify(a, (_, v: unknown) => (typeof v === \"bigint\" ? `${v}n` : v)) ===\n JSON.stringify(b, (_, v: unknown) => (typeof v === \"bigint\" ? `${v}n` : v));\n\nconst isPromise = (value: unknown): value is PromiseLike<Corpus> =>\n typeof (value as PromiseLike<Corpus> | null)?.then === \"function\";\n\nfunction drawnOf(geometry: Geometry, encoding: Encoding, mask: Uint8Array | null): Drawn {\n const tally = encoding.domain.map(() => 0);\n const shown = new Uint8Array(geometry.size);\n let vertices = 0;\n for (let id = 0; id < geometry.size; id++) {\n if ((mask && !mask[id]) || Number.isNaN(geometry.positions[id * 2])) continue;\n shown[id] = 1;\n vertices++;\n const rank = encoding.ranks[id] as number;\n tally[rank] = (tally[rank] ?? 0) + 1;\n }\n let edges = 0;\n const { links } = geometry;\n for (let i = 0; i < links.length; i += 2) {\n if (shown[links[i] as number] && shown[links[i + 1] as number]) edges++;\n }\n return { vertices, edges, domain: encoding.domain, tally };\n}\n\nexport function createGraph(initial: GraphOptions): GraphStore {\n let options = initial;\n const listeners = new Set<() => void>();\n const self: MosaicClient = graphClient();\n\n let corpus: Corpus | null = null;\n let opening: PromiseLike<Corpus> | null = null;\n let failed = false;\n let unrenderable = false;\n let tables: readonly VertexTable[] = [];\n let binding = bindingOf(initial);\n let filter: Filter | undefined;\n let loading: AbortController | null = null;\n let filtering: AbortController | null = null;\n let geometry: Geometry | null = null;\n let encoding: Encoding | null = null;\n let kept: Kept | null = null;\n let mask: Uint8Array | null = null;\n let uploaded: readonly unknown[] = [];\n let unlisten: (() => void) | null = null;\n let active = false;\n\n // A failure found while the store is being built is found while React is still rendering —\n // `useGraph` builds it in `useState` — and a host's `onFailure` is usually a `setState`. It is held\n // and reported to the first subscriber, which arrives in the commit phase.\n let building = true;\n let held: { error: unknown } | null = null;\n const fail = (error: unknown) => {\n if (building) held = { error };\n else options.onFailure(error);\n };\n\n let snapshot: GraphSnapshot = {\n status: \"none\",\n total: undefined,\n drawn: null,\n domain: [],\n selection: null,\n focus: null,\n hovered: null,\n pinned: [],\n tool: null,\n motion: \"settled\",\n progress: 1,\n options,\n corpus: null,\n binding,\n geometry: null,\n encoding: null,\n mask: null,\n };\n\n let domain: { key: unknown[]; value: readonly unknown[] } = { key: [], value: [] };\n function domainNow(): readonly unknown[] {\n const key = [tables, binding.byTable, binding.category, options.categories];\n if (key.some((part, i) => part !== domain.key[i])) domain = { key, value: domainOf(tables, binding, options.categories) };\n return domain.value;\n }\n\n let drawn: { key: unknown[]; value: Drawn | null } = { key: [], value: null };\n function drawnNow(): Drawn | null {\n const key = [geometry, encoding, mask];\n if (key.some((part, i) => part !== drawn.key[i])) {\n drawn = { key, value: geometry && encoding ? drawnOf(geometry, encoding, mask) : null };\n }\n return drawn.value;\n }\n\n function statusOf(): GraphStatus {\n if (failed || unrenderable) return \"failed\";\n if (opening) return \"opening\";\n if (!corpus) return \"none\";\n const current = [geometry, encoding, mask];\n const shown = geometry !== null && current.every((part, i) => part === uploaded[i]);\n return loading || filtering || !shown ? \"loading\" : \"idle\";\n }\n\n function notify(fields: Partial<GraphSnapshot> = {}): void {\n snapshot = {\n ...snapshot,\n total: corpus ? tables.reduce((sum, table) => sum + table.record_count, 0) : undefined,\n drawn: drawnNow(),\n domain: domainNow(),\n options,\n corpus,\n binding,\n geometry,\n encoding,\n mask,\n ...fields,\n };\n snapshot = { ...snapshot, status: statusOf() };\n emit();\n }\n\n /** A change of state only — hover, focus, selection, tool, motion — derives nothing. */\n function patch(fields: Partial<GraphSnapshot>): void {\n snapshot = { ...snapshot, ...fields };\n emit();\n }\n\n function emit(): void {\n for (const listener of listeners) listener();\n }\n\n /**\n * One read in flight per kind, and a newer ask aborts the older one's statements. A load that\n * fails leaves nothing to draw; a filter that fails leaves the last picture.\n */\n function run<T>(\n current: () => AbortController | null,\n set: (aborter: AbortController | null) => void,\n fatal: boolean,\n read: (signal: AbortSignal) => Promise<T>,\n done: (value: T) => void,\n ): void {\n current()?.abort();\n const aborter = new AbortController();\n set(aborter);\n read(aborter.signal).then(\n (value) => {\n if (current() !== aborter) return;\n set(null);\n done(value);\n notify();\n },\n (error: unknown) => {\n // An aborted read is one this store cancelled, not a failure of the corpus.\n if (current() !== aborter || aborter.signal.aborted) return;\n set(null);\n failed ||= fatal;\n fail(error);\n notify();\n },\n );\n }\n\n const asLoad = [() => loading, (a: AbortController | null) => (loading = a), true] as const;\n const asFilter = [() => filtering, (a: AbortController | null) => (filtering = a), false] as const;\n\n function load(): void {\n const open = corpus;\n if (!open) return;\n const seed = domainNow();\n const given = geometry;\n if (given) {\n return run(...asLoad, (signal) => loadEncoding(open, given, binding, seed, signal), (next) => {\n encoding = next;\n });\n }\n run(...asLoad, (signal) => loadGraph(open, binding, seed, signal), (next) => {\n geometry = next.geometry;\n encoding = next.encoding;\n mask = geometry && kept ? maskOf(geometry, kept) : null;\n });\n }\n\n function refilter(): void {\n filtering?.abort();\n filtering = null;\n const open = corpus;\n if (!open || tables.length === 0) return;\n if (filter === undefined) {\n kept = null;\n mask = null;\n return;\n }\n const given = filter;\n run(...asFilter, (signal) => readKept(open, tables, given, signal), (next) => {\n kept = next;\n mask = geometry ? maskOf(geometry, next) : null;\n });\n }\n\n function reopen(): void {\n loading?.abort();\n filtering?.abort();\n loading = filtering = null;\n failed = false;\n geometry = encoding = mask = kept = null;\n uploaded = [];\n tables = [];\n const cleared = { selection: null, focus: null, hovered: null, pinned: [] };\n if (!corpus) return notify(cleared);\n try {\n tables = drawnTables(corpus);\n if (tables.length === 0) throw new GraphError(\"graph/nothing-to-draw\", \"the corpus has no vertex type with a position to draw\");\n } catch (error) {\n failed = true;\n fail(error);\n return notify(cleared);\n }\n load();\n refilter();\n notify(cleared);\n }\n\n /** A promise is adopted when it settles, and only if it is still the corpus the host means. */\n function adopt(given: GraphOptions[\"corpus\"]): void {\n corpus = null;\n opening = isPromise(given) ? given : null;\n if (!opening) {\n corpus = given as Corpus | null;\n return reopen();\n }\n const promised = opening;\n reopen();\n promised.then(\n (opened) => {\n if (opening !== promised) return;\n opening = null;\n corpus = opened;\n reopen();\n },\n (error: unknown) => {\n if (opening !== promised) return;\n opening = null;\n failed = true;\n fail(error);\n notify();\n },\n );\n }\n\n function listen(): void {\n unlisten?.();\n unlisten = null;\n const crossfilter = options.filterBy;\n const changed = () => {\n let next: Filter | undefined;\n try {\n next = crossfilter ? filterFor(crossfilter, self) : undefined;\n } catch (error) {\n fail(error);\n return;\n }\n if (sameFilter(next, filter)) return;\n filter = next;\n refilter();\n notify();\n };\n if (crossfilter) {\n crossfilter.addEventListener(\"value\", changed);\n unlisten = () => crossfilter.removeEventListener(\"value\", changed);\n }\n changed();\n }\n\n const store: GraphStore = {\n /**\n * The first subscriber starts listening to the crossfilter and the last one stops it and lets the\n * graph go — `QueryObserver`'s `onSubscribe`/`onUnsubscribe`, which is what survives StrictMode\n * mounting everything twice.\n */\n subscribe(listener) {\n listeners.add(listener);\n if (!active) {\n active = true;\n listen();\n // StrictMode unsubscribes and subscribes again: what the unsubscribe cancelled starts over.\n if (corpus && !failed && tables.length > 0) {\n if (!loading && (!geometry || !encoding)) load();\n if (!filtering && filter !== undefined && !kept) refilter();\n }\n if (held !== null) {\n const { error } = held;\n held = null;\n options.onFailure(error);\n }\n }\n return () => {\n listeners.delete(listener);\n if (listeners.size === 0) store.destroy();\n };\n },\n getSnapshot: () => snapshot,\n getOptions: () => options,\n setOptions(next) {\n const previous = options;\n options = next;\n const nextBinding = bindingOf(next);\n const rebound =\n nextBinding.category !== binding.category ||\n nextBinding.byTable !== binding.byTable ||\n nextBinding.size !== binding.size ||\n next.categories !== previous.categories;\n binding = nextBinding;\n if (next.corpus !== previous.corpus) {\n adopt(next.corpus);\n if (active && next.filterBy !== previous.filterBy) listen();\n return;\n }\n if (active && next.filterBy !== previous.filterBy) listen();\n if (rebound) load();\n notify();\n },\n destroy() {\n active = false;\n unlisten?.();\n unlisten = null;\n loading?.abort();\n filtering?.abort();\n loading = filtering = null;\n listeners.clear();\n },\n select(vertices, source = \"node\", label = \"\") {\n const selection = vertices && vertices.length > 0 ? { vertices: [...vertices], source, label } : null;\n patch({ selection });\n options.onSelect?.(selection);\n const key = tables[0]?.key;\n if (options.filterBy && key) publish(options.filterBy, self, key, selection ? selection.vertices : null);\n },\n focus(vertex) {\n if (vertex === snapshot.focus) return;\n patch({ focus: vertex });\n options.onFocus?.(vertex);\n },\n hover(vertex) {\n if (vertex !== snapshot.hovered) patch({ hovered: vertex });\n },\n pin(vertices) {\n patch({ pinned: [...vertices] });\n },\n setTool(tool) {\n if (tool !== snapshot.tool) patch({ tool });\n },\n report(motion) {\n if (motion !== snapshot.motion) patch({ motion });\n },\n reportProgress(value) {\n if (value !== snapshot.progress) patch({ progress: value });\n },\n renderable() {\n if (!unrenderable) return;\n unrenderable = false;\n notify();\n },\n unrenderable(error) {\n unrenderable = true;\n fail(error);\n notify();\n },\n reportDrawn(drawnSnapshot) {\n const next = [drawnSnapshot.geometry, drawnSnapshot.encoding, drawnSnapshot.mask];\n if (next.every((part, i) => part === uploaded[i])) return;\n uploaded = next;\n notify();\n },\n };\n\n adopt(initial.corpus);\n building = false;\n return store;\n}\n"],"names":["sameFilter","a","b","_","v","isPromise","value","drawnOf","geometry","encoding","mask","tally","shown","vertices","id","rank","edges","links","i","createGraph","initial","options","listeners","self","graphClient","corpus","opening","failed","unrenderable","tables","binding","bindingOf","filter","loading","filtering","kept","uploaded","unlisten","active","building","held","fail","error","snapshot","domain","domainNow","key","part","domainOf","drawn","drawnNow","statusOf","notify","fields","sum","table","emit","patch","listener","run","current","set","fatal","read","done","_a","aborter","asLoad","asFilter","load","open","seed","given","signal","loadEncoding","next","loadGraph","maskOf","refilter","readKept","reopen","cleared","drawnTables","GraphError","adopt","promised","opened","listen","crossfilter","changed","filterFor","store","previous","nextBinding","rebound","source","label","selection","_b","publish","vertex","tool","motion","drawnSnapshot"],"mappings":"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"}
@@ -22,9 +22,9 @@ export type VertexId = number;
22
22
  /**
23
23
  * The selection tools, and the gesture that reaches them without a mode.
24
24
  *
25
- * `null` is the reading posture: drag pans, drag on a node pins it. Picking a tool swaps the drag
26
- * for a selection gesture — and holding Shift borrows the marquee for one drag without picking
27
- * anything, which is how most selections actually get made.
25
+ * `null` is the reading posture: drag pans, and drag on a node moves it — and pins it while a layout
26
+ * runs. Picking a tool swaps the drag for a selection gesture — and holding Shift borrows the marquee
27
+ * for one drag without picking anything, which is how most selections actually get made.
28
28
  */
29
29
  export type Tool = "rect" | "lasso" | null;
30
30
  /** `settled` converged on its own; `paused` is waiting for you. */
@@ -59,7 +59,7 @@ export interface GraphCommands {
59
59
  pause(): void;
60
60
  resume(): void;
61
61
  restart(): void;
62
- /** Let go of every pinned node, so the simulation gets the whole layout back. */
62
+ /** Let go of every pinned node and reheat the layout, so the released nodes flow back into place. */
63
63
  unpin(): void;
64
64
  /** Centre and select one vertex. */
65
65
  reveal(vertex: VertexId): void;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC;AAE9B;;;;;;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,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
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC;AAE9B;;;;;;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,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,qGAAqG;IACrG,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"}
package/dist/index.d.ts CHANGED
@@ -12,8 +12,9 @@
12
12
  * - `react/` — `useGraph`, `GraphRoot`, `GraphRootProvider`, `useGraphContext`: Ark's four; and
13
13
  * `useGraphState`, TanStack Store's selector, because the api is commands and the state moves at
14
14
  * frame rate.
15
- * - `parts/` — `GraphCanvas`, `GraphLegend`, `GraphToolbar`, `GraphInspector`: flat parts over the
16
- * one context. They own the vocabulary; the root's callbacks are the host's policy.
15
+ * - `parts/` — `GraphCanvas`, `GraphLegend`, `GraphCounts`, `GraphToolbar`, `GraphInspector`,
16
+ * `GraphSearch`: flat parts over the one context; and `GraphLooks`, the picture's preferences,
17
+ * which needs only the theme provider. They own the vocabulary; the root's callbacks are the host's policy.
17
18
  *
18
19
  * **Why a package and not `@kanzo-tech/ui`.** The first admission rule is *domain-free — nothing
19
20
  * about RDF / SHACL / fossil / graphs / auth*. Graphs are excluded by name, and a sibling package is
@@ -25,10 +26,13 @@ export { useGraphState } from './react/use-graph-state';
25
26
  export { useGraphPrefs } from './react/use-graph-prefs';
26
27
  export { GraphCanvas, type GraphCanvasProps } from './parts/graph-canvas';
27
28
  export { GraphLegend, type GraphLegendProps } from './parts/graph-legend';
29
+ export { GraphCounts, type GraphCountsProps } from './parts/graph-counts';
28
30
  export { GraphToolbar, type GraphToolbarProps } from './parts/graph-toolbar';
29
31
  export { GraphInspector, type GraphInspectorProps } from './parts/graph-inspector';
32
+ export { GraphSearch, type GraphSearchProps } from './parts/graph-search';
33
+ export { GraphLooks, type GraphLooksProps } from './parts/graph-looks';
30
34
  export { ShapeGlyph, type ShapeGlyphProps } from './parts/shape-glyph';
31
- export { lookFrom, type Look, type LookPatch, type Shape } from './render/graph-looks';
35
+ export { lookFrom, type Look, type LookPatch, type LookPreset, type Shape } from './render/graph-looks';
32
36
  export { simFrom, type Sim } from './render/graph-sim';
33
37
  export { scaleOf } from './render/graph-model';
34
38
  export type { Channels } from './core/channels';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EACL,SAAS,EACT,iBAAiB,EACjB,eAAe,EACf,KAAK,cAAc,EACnB,KAAK,sBAAsB,GAC5B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,QAAQ,EAAE,KAAK,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAChF,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAExD,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC7E,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AACnF,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAIvE,OAAO,EAAE,QAAQ,EAAE,KAAK,IAAI,EAAE,KAAK,SAAS,EAAE,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AACvF,OAAO,EAAE,OAAO,EAAE,KAAK,GAAG,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,YAAY,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAEhD,YAAY,EAAE,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,eAAe,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACtG,YAAY,EAAE,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AACnE,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EACL,SAAS,EACT,iBAAiB,EACjB,eAAe,EACf,KAAK,cAAc,EACnB,KAAK,sBAAsB,GAC5B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,QAAQ,EAAE,KAAK,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAChF,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAExD,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC7E,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AACnF,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAIvE,OAAO,EAAE,QAAQ,EAAE,KAAK,IAAI,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAE,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AACxG,OAAO,EAAE,OAAO,EAAE,KAAK,GAAG,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,YAAY,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAEhD,YAAY,EAAE,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,eAAe,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACtG,YAAY,EAAE,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AACnE,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js CHANGED
@@ -1,31 +1,37 @@
1
1
  import { GraphRoot as p, GraphRootProvider as e, useGraphContext as t } from "./react/graph-root.js";
2
2
  import { useGraph as m } from "./react/use-graph.js";
3
- import { useGraphState as x } from "./react/use-graph-state.js";
3
+ import { useGraphState as h } from "./react/use-graph-state.js";
4
4
  import { useGraphPrefs as G } from "./react/use-graph-prefs.js";
5
- import { GraphCanvas as l } from "./parts/graph-canvas.js";
6
- import { GraphLegend as u } from "./parts/graph-legend.js";
5
+ import { GraphCanvas as n } from "./parts/graph-canvas.js";
6
+ import { GraphLegend as l } from "./parts/graph-legend.js";
7
+ import { GraphCounts as C } from "./parts/graph-counts.js";
7
8
  import { GraphToolbar as d } from "./parts/graph-toolbar.js";
8
- import { GraphInspector as v } from "./parts/graph-inspector.js";
9
- import { ShapeGlyph as F } from "./parts/shape-glyph.js";
10
- import { lookFrom as R } from "./render/graph-looks.js";
11
- import { simFrom as b } from "./render/graph-sim.js";
12
- import { scaleOf as k } from "./render/graph-model.js";
13
- import { GraphError as E } from "./core/error.js";
9
+ import { GraphInspector as k } from "./parts/graph-inspector.js";
10
+ import { GraphSearch as F } from "./parts/graph-search.js";
11
+ import { GraphLooks as P } from "./parts/graph-looks.js";
12
+ import { ShapeGlyph as b } from "./parts/shape-glyph.js";
13
+ import { lookFrom as y } from "./render/graph-looks.js";
14
+ import { simFrom as I } from "./render/graph-sim.js";
15
+ import { scaleOf as T } from "./render/graph-model.js";
16
+ import { GraphError as q } from "./core/error.js";
14
17
  export {
15
- l as GraphCanvas,
16
- E as GraphError,
17
- v as GraphInspector,
18
- u as GraphLegend,
18
+ n as GraphCanvas,
19
+ C as GraphCounts,
20
+ q as GraphError,
21
+ k as GraphInspector,
22
+ l as GraphLegend,
23
+ P as GraphLooks,
19
24
  p as GraphRoot,
20
25
  e as GraphRootProvider,
26
+ F as GraphSearch,
21
27
  d as GraphToolbar,
22
- F as ShapeGlyph,
23
- R as lookFrom,
24
- k as scaleOf,
25
- b as simFrom,
28
+ b as ShapeGlyph,
29
+ y as lookFrom,
30
+ T as scaleOf,
31
+ I as simFrom,
26
32
  m as useGraph,
27
33
  t as useGraphContext,
28
34
  G as useGraphPrefs,
29
- x as useGraphState
35
+ h as useGraphState
30
36
  };
31
37
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;"}
@@ -0,0 +1,15 @@
1
+ export interface GraphCountsProps extends React.ComponentProps<"p"> {
2
+ /** Draws a spinner while the corpus opens, the graph loads or a layout runs — for a host with no status of its own beside it. */
3
+ spinner?: boolean;
4
+ }
5
+ /**
6
+ * **What is drawn of the whole, in one sentence** — "1.2K of 5K nodes drawn · 8K edges". `drawn`
7
+ * shrinks with the page's filter and `total` is the manifest's, so the sentence is the filter's
8
+ * effect; an edge counts when both its ends are drawn. A figure not yet known is "—", and the
9
+ * numbers are compact in the nearest `LocaleProvider`'s locale.
10
+ *
11
+ * `aria-busy` follows `status`, because only a load changes these figures; a running layout moves
12
+ * the points and not the counts, so it reaches the spinner alone, named "Laying out".
13
+ */
14
+ export declare function GraphCounts({ className, slot, spinner, ...rest }: GraphCountsProps): import("react").JSX.Element;
15
+ //# sourceMappingURL=graph-counts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-counts.d.ts","sourceRoot":"","sources":["../../src/parts/graph-counts.tsx"],"names":[],"mappings":"AAKA,MAAM,WAAW,gBAAiB,SAAQ,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC;IACjE,iIAAiI;IACjI,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAQD;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,OAAe,EAAE,GAAG,IAAI,EAAE,EAAE,gBAAgB,+BAsB1F"}
@@ -0,0 +1,41 @@
1
+ "use client";
2
+ import { jsxs as i, jsx as n } from "react/jsx-runtime";
3
+ import { Show as f, Spinner as h, cn as x, FormatNumber as b } from "@kanzo-tech/ui";
4
+ import { useGraphState as e } from "../react/use-graph-state.js";
5
+ const v = { notation: "compact", maximumFractionDigits: 1 };
6
+ function r({ value: o }) {
7
+ return o == null ? "—" : /* @__PURE__ */ n(b, { value: o, ...v });
8
+ }
9
+ function N({ className: o, slot: u, spinner: c = !1, ...l }) {
10
+ const m = e((t) => {
11
+ var a;
12
+ return (a = t.drawn) == null ? void 0 : a.vertices;
13
+ }), d = e((t) => {
14
+ var a;
15
+ return (a = t.drawn) == null ? void 0 : a.edges;
16
+ }), g = e((t) => t.total), s = e((t) => t.status === "opening" || t.status === "loading"), p = e((t) => t.motion === "running");
17
+ return /* @__PURE__ */ i(
18
+ "p",
19
+ {
20
+ "aria-busy": s || void 0,
21
+ ...l,
22
+ className: x("flex items-center gap-1.5 text-muted-foreground text-xs tabular-nums", o),
23
+ "data-slot": u ?? "graph-counts",
24
+ children: [
25
+ /* @__PURE__ */ n(f, { when: c && (s || p), children: /* @__PURE__ */ n(h, { "aria-label": s ? "Loading" : "Laying out", className: "size-3" }) }),
26
+ /* @__PURE__ */ i("span", { children: [
27
+ /* @__PURE__ */ n(r, { value: m }),
28
+ " of ",
29
+ /* @__PURE__ */ n(r, { value: g }),
30
+ " nodes drawn · ",
31
+ /* @__PURE__ */ n(r, { value: d }),
32
+ " edges"
33
+ ] })
34
+ ]
35
+ }
36
+ );
37
+ }
38
+ export {
39
+ N as GraphCounts
40
+ };
41
+ //# sourceMappingURL=graph-counts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-counts.js","sources":["../../src/parts/graph-counts.tsx"],"sourcesContent":["\"use client\";\n\nimport { cn, FormatNumber, Show, Spinner } from \"@kanzo-tech/ui\";\nimport { useGraphState } from \"../react/use-graph-state\";\n\nexport interface GraphCountsProps extends React.ComponentProps<\"p\"> {\n /** Draws a spinner while the corpus opens, the graph loads or a layout runs — for a host with no status of its own beside it. */\n spinner?: boolean;\n}\n\nconst COMPACT = { notation: \"compact\", maximumFractionDigits: 1 } as const;\n\nfunction Count({ value }: { value: number | null | undefined }) {\n return value === null || value === undefined ? \"—\" : <FormatNumber value={value} {...COMPACT} />;\n}\n\n/**\n * **What is drawn of the whole, in one sentence** — \"1.2K of 5K nodes drawn · 8K edges\". `drawn`\n * shrinks with the page's filter and `total` is the manifest's, so the sentence is the filter's\n * effect; an edge counts when both its ends are drawn. A figure not yet known is \"—\", and the\n * numbers are compact in the nearest `LocaleProvider`'s locale.\n *\n * `aria-busy` follows `status`, because only a load changes these figures; a running layout moves\n * the points and not the counts, so it reaches the spinner alone, named \"Laying out\".\n */\nexport function GraphCounts({ className, slot, spinner = false, ...rest }: GraphCountsProps) {\n const vertices = useGraphState((s) => s.drawn?.vertices);\n const edges = useGraphState((s) => s.drawn?.edges);\n const total = useGraphState((s) => s.total);\n const busy = useGraphState((s) => s.status === \"opening\" || s.status === \"loading\");\n const laying = useGraphState((s) => s.motion === \"running\");\n\n return (\n <p\n aria-busy={busy || undefined}\n {...rest}\n className={cn(\"flex items-center gap-1.5 text-muted-foreground text-xs tabular-nums\", className)}\n data-slot={slot ?? \"graph-counts\"}\n >\n <Show when={spinner && (busy || laying)}>\n <Spinner aria-label={busy ? \"Loading\" : \"Laying out\"} className=\"size-3\" />\n </Show>\n <span>\n <Count value={vertices} /> of <Count value={total} /> nodes drawn · <Count value={edges} /> edges\n </span>\n </p>\n );\n}\n"],"names":[],"mappings":";;;;AAUA;AAEA;AACE;AACF;AAWO;AACL;;AAAsC;AAAS;;AACZ;AAAS;AAK5C;AACE;AAAC;AAAA;AACoB;AACf;AAC2F;AAC5E;AAEnB;AAEA;AAEE;AAAwB;AAAE;AAAyB;AAAE;AAAoC;AAAE;AAC7F;AAAA;AAAA;AAGN;;;;"}
@@ -1,12 +1,13 @@
1
1
  export type GraphLegendProps = React.ComponentProps<"div">;
2
2
  /**
3
3
  * **The categorical scale the canvas draws, as rows** — glyph, name and how many vertices of that
4
- * category are drawn, then what is drawn of the whole. Unbound, the categories are the vertex types.
4
+ * category are drawn; what is drawn of the whole is `GraphCounts`. Unbound, the categories are the
5
+ * vertex types, and with no category at all there is nothing to key, so nothing is drawn.
5
6
  * The rows are the domain fixed before the graph loaded, so a row never moves and a colour never
6
7
  * changes hands; a category past the palette's capacity is Other, as it is on the canvas.
7
8
  *
8
9
  * Names are the root's `categories`, never this part's: a legend that renamed a category would be
9
10
  * encoding one product's policy, which is the parts rule's reversal.
10
11
  */
11
- export declare function GraphLegend({ className, slot, ...rest }: GraphLegendProps): import("react").JSX.Element;
12
+ export declare function GraphLegend({ className, slot, ...rest }: GraphLegendProps): import("react").JSX.Element | null;
12
13
  //# sourceMappingURL=graph-legend.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"graph-legend.d.ts","sourceRoot":"","sources":["../../src/parts/graph-legend.tsx"],"names":[],"mappings":"AAUA,MAAM,MAAM,gBAAgB,GAAG,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;AAI3D;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,EAAE,gBAAgB,+BA8CzE"}
1
+ {"version":3,"file":"graph-legend.d.ts","sourceRoot":"","sources":["../../src/parts/graph-legend.tsx"],"names":[],"mappings":"AAUA,MAAM,MAAM,gBAAgB,GAAG,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;AAI3D;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,EAAE,gBAAgB,sCAqCzE"}
@@ -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