@kanzo-tech/graph 0.13.1 → 0.15.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.
- package/README.md +12 -15
- package/dist/core/categories.d.ts +18 -6
- package/dist/core/categories.d.ts.map +1 -1
- package/dist/core/categories.js +41 -10
- package/dist/core/categories.js.map +1 -1
- package/dist/core/channels.d.ts +10 -5
- package/dist/core/channels.d.ts.map +1 -1
- package/dist/core/channels.js +11 -13
- package/dist/core/channels.js.map +1 -1
- package/dist/core/detail.d.ts +14 -8
- package/dist/core/detail.d.ts.map +1 -1
- package/dist/core/detail.js +33 -15
- package/dist/core/detail.js.map +1 -1
- package/dist/core/filter.d.ts +3 -1
- package/dist/core/filter.d.ts.map +1 -1
- package/dist/core/filter.js +54 -50
- package/dist/core/filter.js.map +1 -1
- package/dist/core/load.d.ts +58 -0
- package/dist/core/load.d.ts.map +1 -0
- package/dist/core/load.js +115 -0
- package/dist/core/load.js.map +1 -0
- package/dist/core/state.d.ts +31 -56
- package/dist/core/state.d.ts.map +1 -1
- package/dist/core/store.d.ts +1 -3
- package/dist/core/store.d.ts.map +1 -1
- package/dist/core/store.js +162 -172
- package/dist/core/store.js.map +1 -1
- package/dist/core/types.d.ts +21 -17
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +5 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -25
- package/dist/index.js.map +1 -1
- package/dist/parts/gesture.d.ts +1 -10
- package/dist/parts/gesture.d.ts.map +1 -1
- package/dist/parts/gesture.js +26 -28
- package/dist/parts/gesture.js.map +1 -1
- package/dist/parts/graph-canvas.d.ts.map +1 -1
- package/dist/parts/graph-canvas.js +128 -106
- package/dist/parts/graph-canvas.js.map +1 -1
- package/dist/parts/graph-inspector.d.ts +4 -4
- package/dist/parts/graph-inspector.d.ts.map +1 -1
- package/dist/parts/graph-inspector.js +55 -57
- package/dist/parts/graph-inspector.js.map +1 -1
- package/dist/parts/graph-legend.d.ts +4 -4
- package/dist/parts/graph-legend.d.ts.map +1 -1
- package/dist/parts/graph-legend.js +21 -21
- package/dist/parts/graph-legend.js.map +1 -1
- package/dist/parts/graph-toolbar.d.ts +1 -1
- package/dist/parts/graph-toolbar.d.ts.map +1 -1
- package/dist/parts/graph-toolbar.js +40 -40
- package/dist/parts/graph-toolbar.js.map +1 -1
- package/dist/parts/overlays.d.ts +6 -5
- package/dist/parts/overlays.d.ts.map +1 -1
- package/dist/parts/overlays.js +70 -68
- package/dist/parts/overlays.js.map +1 -1
- package/dist/react/use-graph-state.d.ts +4 -2
- package/dist/react/use-graph-state.d.ts.map +1 -1
- package/dist/react/use-graph-state.js +10 -6
- package/dist/react/use-graph-state.js.map +1 -1
- package/dist/react/use-graph.d.ts +1 -4
- package/dist/react/use-graph.d.ts.map +1 -1
- package/dist/react/use-graph.js +38 -49
- package/dist/react/use-graph.js.map +1 -1
- package/dist/render/graph-looks.d.ts.map +1 -1
- package/dist/render/graph-looks.js.map +1 -1
- package/dist/render/graph-model.d.ts +6 -8
- package/dist/render/graph-model.d.ts.map +1 -1
- package/dist/render/graph-model.js +51 -57
- package/dist/render/graph-model.js.map +1 -1
- package/dist/render/graph-sim.d.ts +2 -3
- package/dist/render/graph-sim.d.ts.map +1 -1
- package/dist/render/graph-sim.js.map +1 -1
- package/dist/render/renderer.d.ts +10 -10
- package/dist/render/renderer.d.ts.map +1 -1
- package/dist/render/renderer.js +121 -177
- package/dist/render/renderer.js.map +1 -1
- package/dist/section.d.ts.map +1 -1
- package/dist/section.js +3 -7
- package/dist/section.js.map +1 -1
- package/package.json +29 -10
- package/dist/core/refine.d.ts +0 -14
- package/dist/core/refine.d.ts.map +0 -1
- package/dist/core/refine.js +0 -20
- package/dist/core/refine.js.map +0 -1
- package/dist/core/resident.d.ts +0 -79
- package/dist/core/resident.d.ts.map +0 -1
- package/dist/core/resident.js +0 -44
- package/dist/core/resident.js.map +0 -1
- package/dist/core/scheduler.d.ts +0 -42
- package/dist/core/scheduler.d.ts.map +0 -1
- package/dist/core/scheduler.js +0 -77
- package/dist/core/scheduler.js.map +0 -1
- package/dist/core/tile-matrix.d.ts +0 -37
- package/dist/core/tile-matrix.d.ts.map +0 -1
- package/dist/core/tile-matrix.js +0 -49
- package/dist/core/tile-matrix.js.map +0 -1
- package/dist/core/tile.d.ts +0 -54
- package/dist/core/tile.d.ts.map +0 -1
- package/dist/core/tile.js +0 -76
- package/dist/core/tile.js.map +0 -1
- package/dist/core/tileset.d.ts +0 -59
- package/dist/core/tileset.d.ts.map +0 -1
- package/dist/core/tileset.js +0 -177
- package/dist/core/tileset.js.map +0 -1
- package/dist/render/adaptive.d.ts +0 -27
- package/dist/render/adaptive.d.ts.map +0 -1
- package/dist/render/adaptive.js +0 -25
- package/dist/render/adaptive.js.map +0 -1
- package/dist/render/compose.d.ts +0 -51
- package/dist/render/compose.d.ts.map +0 -1
- package/dist/render/compose.js +0 -107
- package/dist/render/compose.js.map +0 -1
- package/dist/render/encode.d.ts +0 -42
- package/dist/render/encode.d.ts.map +0 -1
- package/dist/render/encode.js +0 -68
- package/dist/render/encode.js.map +0 -1
- package/dist/render/when-ready.d.ts +0 -35
- package/dist/render/when-ready.d.ts.map +0 -1
- package/dist/render/when-ready.js +0 -17
- package/dist/render/when-ready.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-canvas.js","sources":["../../src/parts/graph-canvas.tsx"],"sourcesContent":["\"use client\";\n\nimport { categoricalCapacity, cn, Show, useThemeTick } from \"@kanzo-tech/ui\";\nimport { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from \"react\";\nimport { nameOf } from \"../core/categories\";\nimport { denseOf, type VertexId } from \"../core/resident\";\nimport { useGraphContext } from \"../react/graph-root\";\nimport { internalsOf } from \"../react/use-graph\";\nimport { useGraphState } from \"../react/use-graph-state\";\nimport type { Composition } from \"../render/compose\";\nimport { resolveLook } from \"../render/graph-looks\";\nimport { scaleOf } from \"../render/graph-model\";\nimport { cursorChip, useGesture } from \"./gesture\";\nimport { useOverlays } from \"./overlays\";\nimport { ShapeGlyph } from \"./shape-glyph\";\n\nexport interface GraphCanvasProps extends React.ComponentProps<\"div\"> {\n /** The chrome — a toolbar, a legend, an inspector — positioned over the surface. */\n children?: ReactNode;\n}\n\nconst WASH = \"var(--brand-a5)\";\n\ninterface Label {\n vertex: VertexId;\n text: string;\n}\n\n/** The biggest `budget` vertices by the ramp, the focused one first, each with its `title`. */\nfunction labelsOf(composition: Composition, budget: number, focus: VertexId | null): Label[] {\n const titles = composition.titles;\n if (!titles || budget <= 0) return [];\n const order = Array.from({ length: composition.vertices }, (_, i) => i);\n const ramp = composition.sizes;\n if (ramp) order.sort((a, b) => (ramp[b] ?? 0) - (ramp[a] ?? 0));\n const chosen = order.slice(0, budget);\n const focused = focus === null ? undefined : composition.resident.indexOf(focus);\n if (focused !== undefined && !chosen.includes(focused)) chosen.unshift(focused);\n return chosen.flatMap((i) => {\n const vertex = composition.resident.at(i);\n const text = titles[i];\n return vertex === undefined || !text ? [] : [{ vertex, text }];\n });\n}\n\nconst WAITING: Partial<Record<string, string>> = {\n opening: \"Opening the corpus…\",\n reading: \"Reading the corpus…\",\n};\n\n/**\n * **The element, and what the look declares over it** — the grid, the vignette, the labels and the\n * hover card are fields of `Look`, so drawing them is the canvas's; the marquee and lasso gesture is\n * too. Two layers: cosmos.gl fills the surface with a canvas of its own, so chrome parented there\n * would be a sibling inside an element the renderer resizes. The frame is `isolate`, so a host's\n * `z-index` on a child cannot escape into the page.\n *\n * The surface is never gated on the first answer: rendered behind a placeholder it deadlocks — no\n * element, no renderer, no camera, no question. Waiting is an overlay over an empty canvas.\n */\nexport function GraphCanvas({ children, className, slot, ...rest }: GraphCanvasProps) {\n const api = useGraphContext();\n const { attach, renderer } = internalsOf(api);\n const hovered = useGraphState((s) => s.hovered);\n const focus = useGraphState((s) => s.focus);\n const tool = useGraphState((s) => s.tool);\n const options = useGraphState((s) => s.options);\n const domain = useGraphState((s) => s.drawn?.domain ?? s.domain);\n const waiting = useGraphState((s) => (s.drawn === null ? WAITING[s.status] : undefined));\n const look = useMemo(() => resolveLook(options.look), [options.look]);\n const getGraph = useCallback(() => renderer()?.graph ?? null, [renderer]);\n const overlays = useOverlays({ getGraph, getResident: api.getResident });\n const { cardRef, gridRef, hostRef, labelRef, schedule, setHovered, setLabelOrder, track } = overlays;\n const surfaceRef = useRef<HTMLDivElement>(null);\n const [composition, setComposition] = useState<Composition | null>(null);\n\n useEffect(() => {\n const surface = surfaceRef.current;\n if (!surface) return;\n return attach(surface, { onFrame: schedule, onComposed: setComposition });\n }, [attach, schedule]);\n\n const themeTick = useThemeTick();\n useEffect(() => {\n renderer()?.repaint();\n }, [renderer, themeTick]);\n\n const labels = useMemo(\n () => (composition ? labelsOf(composition, look.labels, focus) : []),\n [composition, focus, look.labels],\n );\n useEffect(() => {\n setLabelOrder(labels.map((label) => label.vertex));\n setHovered(hovered);\n track();\n schedule();\n }, [hovered, labels, schedule, setHovered, setLabelOrder, track, look.grid]);\n\n const gesture = useGesture({\n getGraph,\n getResident: api.getResident,\n getSelection: useCallback(() => api.getState().selection, [api]),\n commit: (vertices, source, label) => api.select(vertices ? [...vertices] : null, source, label),\n setTool: api.setTool,\n tool,\n });\n const { active, drag, preview } = gesture;\n\n const capacity = hostRef.current ? categoricalCapacity(hostRef.current) : undefined;\n const scale = useMemo(() => scaleOf(options, capacity), [options, capacity]);\n const card = useMemo(() => {\n if (hovered === null || !composition) return null;\n const index = composition.resident.indexOf(hovered);\n if (index === undefined) return null;\n const rank = composition.categories[index] ?? 0;\n const bound = options.fill !== undefined || options.symbol !== undefined;\n return {\n rank,\n title: composition.titles?.[index] || `#${denseOf(hovered)}`,\n category: bound && rank < domain.length ? nameOf(domain[rank], options.categories) : null,\n };\n }, [composition, domain, hovered, options]);\n\n return (\n <div\n {...rest}\n className={cn(\"relative isolate size-full overflow-hidden bg-background\", className)}\n data-slot={slot ?? \"graph-canvas\"}\n ref={hostRef}\n >\n <div className=\"size-full\" data-slot=\"graph-canvas-surface\" ref={surfaceRef} />\n <Show when={look.grid}>\n <div\n className=\"pointer-events-none absolute inset-0\"\n ref={gridRef}\n style={{ backgroundImage: \"radial-gradient(var(--border) 1px, transparent 1px)\" }}\n />\n </Show>\n <Show when={look.vignette}>\n <div\n className=\"pointer-events-none absolute inset-0\"\n style={{ background: \"radial-gradient(ellipse 75% 65% at 50% 45%, transparent 30%, var(--background) 100%)\" }}\n />\n </Show>\n <div className=\"pointer-events-none absolute inset-0 overflow-hidden\">\n {labels.map((label) => (\n <span\n className=\"absolute top-0 left-0 whitespace-nowrap font-medium text-[10px] text-foreground leading-none opacity-0 transition-opacity [text-shadow:0_0_3px_var(--background),0_0_6px_var(--background)]\"\n key={String(label.vertex)}\n ref={labelRef(label.vertex)}\n >\n {label.text}\n </span>\n ))}\n {card && (\n <div\n className=\"absolute top-0 left-0 flex w-max max-w-60 items-center gap-2 rounded-lg border bg-popover px-2.5 py-2 opacity-0 shadow-lg\"\n data-slot=\"graph-canvas-card\"\n ref={cardRef}\n >\n <ShapeGlyph className=\"size-2.5 shrink-0\" color={scale.color(card.rank)} shape={scale.shape(card.rank)} />\n <span className=\"truncate font-medium text-popover-foreground text-sm leading-none\">{card.title}</span>\n {card.category !== null && <span className=\"text-muted-foreground text-xs\">{card.category}</span>}\n </div>\n )}\n </div>\n <Show when={active !== null}>\n <div className=\"absolute inset-0 cursor-crosshair\" data-slot=\"graph-canvas-gesture\" {...gesture.handlers}>\n {drag && (\n <svg className=\"pointer-events-none size-full\">\n {drag.tool === \"rect\" ? (\n <rect\n className=\"stroke-primary\"\n height={Math.abs(drag.to[1] - drag.from[1])}\n strokeDasharray=\"4 3\"\n strokeWidth={1}\n style={{ fill: WASH }}\n width={Math.abs(drag.to[0] - drag.from[0])}\n x={Math.min(drag.from[0], drag.to[0])}\n y={Math.min(drag.from[1], drag.to[1])}\n />\n ) : (\n drag.path.length > 1 && (\n <polygon\n className=\"stroke-primary\"\n points={drag.path.map(([x, y]) => `${x},${y}`).join(\" \")}\n strokeWidth={1}\n style={{ fill: WASH }}\n />\n )\n )}\n </svg>\n )}\n <Show when={preview !== null}>\n <span\n className=\"pointer-events-none absolute rounded-sm bg-primary px-1.5 py-0.5 font-medium text-[10px] text-primary-foreground tabular-nums\"\n style={cursorChip(drag)}\n >\n {preview} node{preview === 1 ? \"\" : \"s\"}\n </span>\n </Show>\n </div>\n </Show>\n <Show when={waiting !== undefined}>\n <p\n className=\"pointer-events-none absolute inset-0 grid place-items-center text-muted-foreground text-xs\"\n data-slot=\"graph-canvas-waiting\"\n >\n {waiting}\n </p>\n </Show>\n {children}\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;AAqBA;AAQA;AACE;AACA;AACA;AAEA;AACA;AAEA;AAEE;AAEA;AAA6D;AAEjE;AAEA;AAAiD;AACtC;AAEX;AAYO;AACL;;AAMoC;AAAqB;;AAGtB;AAAqB;AAMxD;AACE;AACA;AACA;AAAwE;AAG1E;AACA;;AACE;AAAY;AAGd;AAAe;AACoD;AACjC;AAElC;AACE;AAGA;AAGF;AAA2B;AACzB;AACiB;AAC8C;AAC+B;AACjF;AACb;;AAOA;AACA;AACA;AACA;AAEA;AAAO;AACL;AAC0D;AAC2B;AAAA;AAIzF;AACE;AAAC;AAAA;AACK;AAC+E;AAChE;AACd;AAEL;AAA6E;AAE3E;AAAC;AAAA;AACW;AACL;AACqB;AAAsD;AAEpF;AAEE;AAAC;AAAA;AACW;AACW;AAAuF;AAEhH;AAEG;AACC;AAAC;AAAA;AACW;AAEgB;AAEnB;AAAA;AAHiB;AAK3B;AAEC;AAAC;AAAA;AACW;AACA;AACL;AAEL;AAAwG;AACR;AACN;AAAA;AAAA;AAAA;AAGhG;AAGK;AAGK;AAAC;AAAA;AACW;AACgC;AAC1B;AACH;AACE;AAC0B;AACL;AACA;AAAA;AAIpC;AAAC;AAAA;AACW;AAC6C;AAC1C;AACE;AAAK;AAI5B;AAGA;AAAC;AAAA;AACW;AACY;AAErB;AAAA;AAAQ;AAA2B;AAAA;AAAA;AAExC;AAEJ;AAEE;AAAC;AAAA;AACW;AACA;AAET;AAAA;AAEL;AACC;AAAA;AAAA;AAGP;;;;"}
|
|
1
|
+
{"version":3,"file":"graph-canvas.js","sources":["../../src/parts/graph-canvas.tsx"],"sourcesContent":["\"use client\";\n\nimport { categoricalCapacity, cn, Show, useThemeTick } from \"@kanzo-tech/ui\";\nimport { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from \"react\";\nimport { nameOf } from \"../core/categories\";\nimport { readTitles } from \"../core/detail\";\nimport type { Encoding, Geometry } from \"../core/load\";\nimport type { GraphOptions } from \"../core/state\";\nimport type { VertexId } from \"../core/types\";\nimport { useGraphContext } from \"../react/graph-root\";\nimport { internalsOf } from \"../react/use-graph\";\nimport { useGraphSnapshot, useGraphState } from \"../react/use-graph-state\";\nimport { resolveLook } from \"../render/graph-looks\";\nimport { scaleOf } from \"../render/graph-model\";\nimport { cursorChip, useGesture } from \"./gesture\";\nimport { useOverlays } from \"./overlays\";\nimport { ShapeGlyph } from \"./shape-glyph\";\n\nexport interface GraphCanvasProps extends React.ComponentProps<\"div\"> {\n /** The chrome — a toolbar, a legend, an inspector — positioned over the surface. */\n children?: ReactNode;\n}\n\nconst WASH = \"color-mix(in oklab, var(--primary) 17%, transparent)\";\n\n/**\n * The biggest `budget` surviving vertices by the ramp, the focused one first. One pass, keeping the\n * best few in order: a sort of every vertex to name twenty-six of them is the cost this avoids.\n */\nfunction labelled(geometry: Geometry, encoding: Encoding, mask: Uint8Array | null, budget: number, focus: VertexId | null): VertexId[] {\n const ramp = encoding.sizes;\n const best: VertexId[] = [];\n if (ramp && budget > 0) {\n for (let id = 0; id < geometry.size; id++) {\n const value = ramp[id] as number;\n if ((mask && !mask[id]) || Number.isNaN(value)) continue;\n if (best.length === budget && value <= (ramp[best[budget - 1] as number] as number)) continue;\n let at = best.length;\n while (at > 0 && (ramp[best[at - 1] as number] as number) < value) at--;\n best.splice(at, 0, id);\n if (best.length > budget) best.pop();\n }\n }\n return focus === null || best.includes(focus) ? best : [focus, ...best];\n}\n\n/** A label's text is read for the vertices that carry one, never carried for every vertex. */\nfunction useTitles(vertices: readonly VertexId[]): ReadonlyMap<VertexId, string> {\n const api = useGraphContext();\n const corpus = useGraphState((s) => s.corpus);\n const title = useGraphState((s) => s.options.title);\n const geometry = useGraphSnapshot((s) => s.geometry);\n const [titles, setTitles] = useState<ReadonlyMap<VertexId, string>>(() => new Map());\n const key = vertices.join(\",\");\n useEffect(() => {\n if (!corpus || !geometry || key === \"\") return;\n const aborter = new AbortController();\n readTitles(corpus, geometry, key.split(\",\").map(Number), title, aborter.signal).then(\n (found) => !aborter.signal.aborted && setTitles(found),\n (error: unknown) => !aborter.signal.aborted && api.getState().options.onFailure(error instanceof Error ? error.message : String(error)),\n );\n return () => aborter.abort();\n }, [api, corpus, geometry, key, title]);\n return titles;\n}\n\nconst WAITING: Partial<Record<string, string>> = {\n opening: \"Opening the corpus…\",\n loading: \"Loading the graph…\",\n};\n\n/**\n * **The element, and what the look declares over it** — the grid, the vignette, the labels and the\n * hover card are fields of `Look`, so drawing them is the canvas's; the marquee and lasso gesture is\n * too. Two layers: cosmos.gl fills the surface with a canvas of its own, so chrome parented there\n * would be a sibling inside an element the renderer resizes. The frame is `isolate`, so a host's\n * `z-index` on a child cannot escape into the page.\n *\n * The surface is never gated on the first answer: rendered behind a placeholder it deadlocks — no\n * element, no renderer, no camera, no question. Waiting is an overlay over an empty canvas.\n */\nexport function GraphCanvas({ children, className, slot, ...rest }: GraphCanvasProps) {\n const api = useGraphContext();\n const { attach, renderer } = internalsOf(api);\n const focus = useGraphState((s) => s.focus);\n const tool = useGraphState((s) => s.tool);\n const options = useGraphState((s) => s.options);\n const domain = useGraphState((s) => s.drawn?.domain ?? s.domain);\n const waiting = useGraphState((s) => (s.drawn === null ? WAITING[s.status] : undefined));\n const look = useMemo(() => resolveLook(options.look), [options.look]);\n const getGraph = useCallback(() => renderer()?.graph ?? null, [renderer]);\n const overlays = useOverlays({ getGraph });\n const { cardRef, gridRef, hostRef, hoverAt, labelRef, schedule, setHovered, setLabelOrder, track } = overlays;\n const surfaceRef = useRef<HTMLDivElement>(null);\n const geometry = useGraphSnapshot((s) => s.geometry);\n const encoding = useGraphSnapshot((s) => s.encoding);\n const mask = useGraphSnapshot((s) => s.mask);\n\n useEffect(() => {\n const surface = surfaceRef.current;\n if (!surface) return;\n return attach(surface, { onFrame: schedule, onHover: hoverAt });\n }, [attach, hoverAt, schedule]);\n\n const themeTick = useThemeTick();\n useEffect(() => {\n renderer()?.repaint();\n }, [renderer, themeTick]);\n\n const labelledIds = useMemo(\n () => (geometry && encoding ? labelled(geometry, encoding, mask, look.labels, focus) : []),\n [geometry, encoding, mask, focus, look.labels],\n );\n const titles = useTitles(labelledIds);\n const labels = useMemo(\n () => labelledIds.flatMap((vertex) => (titles.get(vertex) ? [{ vertex, text: titles.get(vertex) as string }] : [])),\n [labelledIds, titles],\n );\n useEffect(() => {\n setLabelOrder(labels.map((label) => label.vertex));\n track();\n schedule();\n }, [labels, schedule, setLabelOrder, track, look.grid]);\n\n const gesture = useGesture({\n getGraph,\n getSelection: useCallback(() => api.getState().selection, [api]),\n commit: (vertices, source, label) => api.select(vertices ? [...vertices] : null, source, label),\n setTool: api.setTool,\n tool,\n });\n const { active, drag, preview } = gesture;\n\n const capacity = hostRef.current ? categoricalCapacity(hostRef.current) : undefined;\n const scale = useMemo(() => scaleOf(options, capacity), [options, capacity]);\n\n return (\n <div\n {...rest}\n className={cn(\"relative isolate size-full overflow-hidden bg-background\", className)}\n data-slot={slot ?? \"graph-canvas\"}\n ref={hostRef}\n >\n <div className=\"size-full\" data-slot=\"graph-canvas-surface\" ref={surfaceRef} />\n <Show when={look.grid}>\n <div\n className=\"pointer-events-none absolute inset-0\"\n ref={gridRef}\n style={{ backgroundImage: \"radial-gradient(var(--border) 1px, transparent 1px)\" }}\n />\n </Show>\n <Show when={look.vignette}>\n <div\n className=\"pointer-events-none absolute inset-0\"\n style={{ background: \"radial-gradient(ellipse 75% 65% at 50% 45%, transparent 30%, var(--background) 100%)\" }}\n />\n </Show>\n <div className=\"pointer-events-none absolute inset-0 overflow-hidden\">\n {labels.map((label) => (\n <span\n className=\"absolute top-0 left-0 whitespace-nowrap font-medium text-[10px] text-foreground leading-none opacity-0 transition-opacity [text-shadow:0_0_3px_var(--background),0_0_6px_var(--background)]\"\n key={String(label.vertex)}\n ref={labelRef(label.vertex)}\n >\n {label.text}\n </span>\n ))}\n <HoverCard\n cardRef={cardRef}\n domain={domain}\n encoding={encoding}\n options={options}\n scale={scale}\n schedule={schedule}\n setHovered={setHovered}\n />\n </div>\n <Show when={active !== null}>\n <div className=\"absolute inset-0 cursor-crosshair\" data-slot=\"graph-canvas-gesture\" {...gesture.handlers}>\n {drag && (\n <svg className=\"pointer-events-none size-full\">\n {drag.tool === \"rect\" ? (\n <rect\n className=\"stroke-primary\"\n height={Math.abs(drag.to[1] - drag.from[1])}\n strokeDasharray=\"4 3\"\n strokeWidth={1}\n style={{ fill: WASH }}\n width={Math.abs(drag.to[0] - drag.from[0])}\n x={Math.min(drag.from[0], drag.to[0])}\n y={Math.min(drag.from[1], drag.to[1])}\n />\n ) : (\n drag.path.length > 1 && (\n <polygon\n className=\"stroke-primary\"\n points={drag.path.map(([x, y]) => `${x},${y}`).join(\" \")}\n strokeWidth={1}\n style={{ fill: WASH }}\n />\n )\n )}\n </svg>\n )}\n <Show when={preview !== null}>\n <span\n className=\"pointer-events-none absolute rounded-sm bg-primary px-1.5 py-0.5 font-medium text-[10px] text-primary-foreground tabular-nums\"\n style={cursorChip(drag)}\n >\n {preview} node{preview === 1 ? \"\" : \"s\"}\n </span>\n </Show>\n </div>\n </Show>\n <Show when={waiting !== undefined}>\n <p\n className=\"pointer-events-none absolute inset-0 grid place-items-center text-muted-foreground text-xs\"\n data-slot=\"graph-canvas-waiting\"\n >\n {waiting}\n </p>\n </Show>\n {children}\n </div>\n );\n}\n\ninterface HoverCardProps {\n cardRef: React.RefObject<HTMLDivElement | null>;\n domain: readonly unknown[];\n encoding: Encoding | null;\n options: GraphOptions;\n scale: ReturnType<typeof scaleOf>;\n schedule: () => void;\n setHovered: (vertex: VertexId | null) => void;\n}\n\n/** The only part that re-renders on a hover: the canvas and its labels do not. */\nfunction HoverCard({ cardRef, domain, encoding, options, scale, schedule, setHovered }: HoverCardProps) {\n const hovered = useGraphState((s) => s.hovered);\n const titles = useTitles(useMemo(() => (hovered === null ? [] : [hovered]), [hovered]));\n useEffect(() => {\n setHovered(hovered);\n schedule();\n }, [hovered, schedule, setHovered, titles]);\n\n if (hovered === null || !encoding || hovered >= encoding.ranks.length) return null;\n const rank = encoding.ranks[hovered] ?? 0;\n const category = rank < domain.length ? nameOf(domain[rank], options.categories) : null;\n return (\n <div\n className=\"absolute top-0 left-0 flex w-max max-w-60 items-center gap-2 rounded-lg border bg-popover px-2.5 py-2 opacity-0 shadow-lg\"\n data-slot=\"graph-canvas-card\"\n ref={cardRef}\n >\n <ShapeGlyph className=\"size-2.5 shrink-0\" color={scale.color(rank)} shape={scale.shape(rank)} />\n <span className=\"truncate font-medium text-popover-foreground text-sm leading-none\">\n {titles.get(hovered) || `#${hovered}`}\n </span>\n <Show when={category !== null}>\n <span className=\"text-muted-foreground text-xs\">{category}</span>\n </Show>\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;AAuBA;AAMA;AACE;AAEA;AACE;AACE;AAEA;AACA;AACA;AACA;AAC+B;AAGnC;AACF;AAGA;AACE;AAMA;AACE;AACA;AACA;AAAgF;AACzB;AACiF;AAEnH;AAGzB;AAEA;AAAiD;AACtC;AAEX;AAYO;AACL;;AAKoC;AAAqB;;AAGtB;AAAqB;AAQxD;AACE;AACA;AACA;AAA8D;AAGhE;AACA;;AACE;AAAY;AAGd;AAAoB;AACqE;AAC1C;AAGhC;AACqG;AAC9F;AAEtB;AACE;AAEA;AAGF;AAA2B;AACzB;AAC+D;AAC+B;AACjF;AACb;AAOF;AACE;AAAC;AAAA;AACK;AAC+E;AAChE;AACd;AAEL;AAA6E;AAE3E;AAAC;AAAA;AACW;AACL;AACqB;AAAsD;AAEpF;AAEE;AAAC;AAAA;AACW;AACW;AAAuF;AAEhH;AAEG;AACC;AAAC;AAAA;AACW;AAEgB;AAEnB;AAAA;AAHiB;AAK3B;AACD;AAAC;AAAA;AACC;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAEJ;AAGK;AAGK;AAAC;AAAA;AACW;AACgC;AAC1B;AACH;AACE;AAC0B;AACL;AACA;AAAA;AAIpC;AAAC;AAAA;AACW;AAC6C;AAC1C;AACE;AAAK;AAI5B;AAGA;AAAC;AAAA;AACW;AACY;AAErB;AAAA;AAAQ;AAA2B;AAAA;AAAA;AAExC;AAEJ;AAEE;AAAC;AAAA;AACW;AACA;AAET;AAAA;AAEL;AACC;AAAA;AAAA;AAGP;AAaA;AACE;AAOA;AAJE;AACA;AAIF;AAEA;AACE;AAAC;AAAA;AACW;AACA;AACL;AAEL;AAA8F;AAG9F;AAGA;AAAA;AAAA;AAGN;;;;"}
|
|
@@ -9,10 +9,10 @@ export interface GraphInspectorProps extends Omit<React.ComponentProps<"div">, "
|
|
|
9
9
|
children?: (detail: VertexDetail) => ReactNode;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
* **The focused vertex, fetched and laid out by the corpus's own
|
|
13
|
-
* channels project and nothing else, so the rest of the row is read when a reader focuses
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* **The focused vertex, fetched and laid out by the corpus's own tables.** The loaded graph carries
|
|
13
|
+
* what the channels project and nothing else, so the rest of the row is read when a reader focuses
|
|
14
|
+
* it — a scan of its table, filtered to its key, cancelled when the focus moves on. The fields are the
|
|
15
|
+
* table's, in the order the manifest declares them; the key and the position are the canvas's.
|
|
16
16
|
*/
|
|
17
17
|
export declare function GraphInspector({ children, className, slot, ...rest }: GraphInspectorProps): import("react").JSX.Element;
|
|
18
18
|
//# sourceMappingURL=graph-inspector.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-inspector.d.ts","sourceRoot":"","sources":["../../src/parts/graph-inspector.tsx"],"names":[],"mappings":"AAeA,OAAO,EAAgC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGrE,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"graph-inspector.d.ts","sourceRoot":"","sources":["../../src/parts/graph-inspector.tsx"],"names":[],"mappings":"AAeA,OAAO,EAAgC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGrE,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAO/D,MAAM,WAAW,mBAAoB,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC;IACxF;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,SAAS,CAAC;CAChD;AAWD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,EAAE,mBAAmB,+BAuFzF"}
|
|
@@ -1,84 +1,82 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsxs as
|
|
3
|
-
import { useChartCapacity as
|
|
4
|
-
import { CrosshairIcon as
|
|
5
|
-
import { useMemo as
|
|
6
|
-
import { nameOf as
|
|
7
|
-
import { bindingOf as
|
|
8
|
-
import { readVertex as
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const t =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
(i) => {
|
|
28
|
-
r.signal.aborted || (g({ vertex: o, detail: null }), c.getState().options.onFailure(i instanceof Error ? i.message : String(i)));
|
|
2
|
+
import { jsxs as i, jsx as n, Fragment as O } from "react/jsx-runtime";
|
|
3
|
+
import { useChartCapacity as B, Show as m, Skeleton as F, Badge as M, Button as T, DataList as j, DataListItem as A, DataListItemLabel as E, DataListItemValue as V, cn as $ } from "@kanzo-tech/ui";
|
|
4
|
+
import { CrosshairIcon as q } from "lucide-react";
|
|
5
|
+
import { useMemo as H, useState as J, useEffect as K } from "react";
|
|
6
|
+
import { nameOf as P } from "../core/categories.js";
|
|
7
|
+
import { bindingOf as Q } from "../core/channels.js";
|
|
8
|
+
import { readVertex as R } from "../core/detail.js";
|
|
9
|
+
import { useGraphContext as U } from "../react/graph-root.js";
|
|
10
|
+
import { useGraphState as d, useGraphSnapshot as W } from "../react/use-graph-state.js";
|
|
11
|
+
import { scaleOf as X } from "../render/graph-model.js";
|
|
12
|
+
import { ShapeGlyph as Y } from "./shape-glyph.js";
|
|
13
|
+
const w = (s) => s instanceof Date ? s.toISOString().slice(0, 10) : s == null || s === "" ? "—" : String(s);
|
|
14
|
+
function ct({ children: s, className: C, slot: I, ...k }) {
|
|
15
|
+
var y;
|
|
16
|
+
const p = U(), o = d((t) => t.focus), h = d((t) => t.corpus), r = W((t) => t.geometry), l = d((t) => t.options), D = d((t) => {
|
|
17
|
+
var a;
|
|
18
|
+
return ((a = t.drawn) == null ? void 0 : a.domain) ?? t.domain;
|
|
19
|
+
}), g = B(), v = H(() => X(l, g), [l, g]), [f, b] = J(null);
|
|
20
|
+
K(() => {
|
|
21
|
+
if (o === null || !h || !r) return;
|
|
22
|
+
const t = new AbortController();
|
|
23
|
+
return R(h, r, o, t.signal).then(
|
|
24
|
+
(a) => !t.signal.aborted && b({ vertex: o, detail: a }),
|
|
25
|
+
(a) => {
|
|
26
|
+
t.signal.aborted || (b({ vertex: o, detail: null }), p.getState().options.onFailure(a instanceof Error ? a.message : String(a)));
|
|
29
27
|
}
|
|
30
|
-
), () =>
|
|
31
|
-
}, [
|
|
32
|
-
const
|
|
33
|
-
var
|
|
34
|
-
return (
|
|
35
|
-
},
|
|
36
|
-
return /* @__PURE__ */
|
|
37
|
-
/* @__PURE__ */
|
|
38
|
-
/* @__PURE__ */
|
|
39
|
-
/* @__PURE__ */
|
|
40
|
-
|
|
41
|
-
/* @__PURE__ */
|
|
42
|
-
/* @__PURE__ */
|
|
43
|
-
/* @__PURE__ */
|
|
44
|
-
/* @__PURE__ */
|
|
45
|
-
/* @__PURE__ */
|
|
46
|
-
|
|
28
|
+
), () => t.abort();
|
|
29
|
+
}, [p, h, o, r]);
|
|
30
|
+
const e = f !== null && f.vertex === o ? f.detail : void 0, u = (t) => {
|
|
31
|
+
var a;
|
|
32
|
+
return (a = e == null ? void 0 : e.fields.find((z) => z.name === t)) == null ? void 0 : a.value;
|
|
33
|
+
}, c = Q(l), N = c.byTable ? e == null ? void 0 : e.table : u(c.category), S = D.findIndex((t) => String(t) === String(N)), G = c.byTable || c.category !== void 0, L = (y = r == null ? void 0 : r.tables.find((t) => t.name === (e == null ? void 0 : e.table))) == null ? void 0 : y.identity, x = u(l.title) ?? u(L);
|
|
34
|
+
return /* @__PURE__ */ i("div", { ...k, className: $("space-y-3 text-sm", C), "data-slot": I ?? "graph-inspector", children: [
|
|
35
|
+
/* @__PURE__ */ n(m, { when: o === null, children: /* @__PURE__ */ n("p", { className: "text-muted-foreground text-xs", children: "Click a vertex on the canvas to inspect it." }) }),
|
|
36
|
+
/* @__PURE__ */ n(m, { when: o !== null && e === void 0, children: /* @__PURE__ */ n(F, { className: "h-24 w-full" }) }),
|
|
37
|
+
/* @__PURE__ */ n(m, { when: o !== null && e === null, children: /* @__PURE__ */ n("p", { className: "text-muted-foreground text-xs", children: "This vertex could not be read." }) }),
|
|
38
|
+
e && /* @__PURE__ */ i(O, { children: [
|
|
39
|
+
/* @__PURE__ */ i("div", { children: [
|
|
40
|
+
/* @__PURE__ */ n("p", { className: "truncate font-medium", "data-slot": "graph-inspector-title", children: x == null ? `#${e.vertex}` : w(x) }),
|
|
41
|
+
/* @__PURE__ */ i("div", { className: "mt-1 flex items-center gap-1.5", children: [
|
|
42
|
+
/* @__PURE__ */ n(m, { when: G, children: /* @__PURE__ */ i(M, { className: "gap-1 text-[10px]", size: "xs", variant: "outline", children: [
|
|
43
|
+
/* @__PURE__ */ n(
|
|
44
|
+
Y,
|
|
47
45
|
{
|
|
48
46
|
className: "size-2 shrink-0",
|
|
49
|
-
color:
|
|
50
|
-
shape:
|
|
47
|
+
color: v.color(Math.max(0, S)),
|
|
48
|
+
shape: v.shape(Math.max(0, S))
|
|
51
49
|
}
|
|
52
50
|
),
|
|
53
|
-
|
|
51
|
+
P(N, l.categories)
|
|
54
52
|
] }) }),
|
|
55
|
-
/* @__PURE__ */
|
|
56
|
-
|
|
53
|
+
/* @__PURE__ */ i(
|
|
54
|
+
T,
|
|
57
55
|
{
|
|
58
56
|
className: "h-5 gap-1 text-[10px]",
|
|
59
|
-
onClick: () =>
|
|
57
|
+
onClick: () => p.reveal(e.vertex),
|
|
60
58
|
size: "sm",
|
|
61
59
|
title: "Bring this vertex into view on the canvas",
|
|
62
60
|
variant: "ghost",
|
|
63
61
|
children: [
|
|
64
|
-
/* @__PURE__ */
|
|
62
|
+
/* @__PURE__ */ n(q, { className: "size-3" }),
|
|
65
63
|
"Find on canvas"
|
|
66
64
|
]
|
|
67
65
|
}
|
|
68
66
|
)
|
|
69
67
|
] })
|
|
70
68
|
] }),
|
|
71
|
-
/* @__PURE__ */
|
|
72
|
-
|
|
73
|
-
/* @__PURE__ */
|
|
74
|
-
/* @__PURE__ */
|
|
69
|
+
/* @__PURE__ */ i(j, { orientation: "vertical", children: [
|
|
70
|
+
e.fields.map((t) => /* @__PURE__ */ i(A, { className: "gap-0.5 py-0", children: [
|
|
71
|
+
/* @__PURE__ */ n(E, { className: "text-xs", children: t.name }),
|
|
72
|
+
/* @__PURE__ */ n(V, { className: "break-all", children: w(t.value) })
|
|
75
73
|
] }, t.name)),
|
|
76
|
-
|
|
74
|
+
s == null ? void 0 : s(e)
|
|
77
75
|
] })
|
|
78
76
|
] })
|
|
79
77
|
] });
|
|
80
78
|
}
|
|
81
79
|
export {
|
|
82
|
-
|
|
80
|
+
ct as GraphInspector
|
|
83
81
|
};
|
|
84
82
|
//# sourceMappingURL=graph-inspector.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-inspector.js","sources":["../../src/parts/graph-inspector.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n Badge,\n Button,\n cn,\n DataList,\n DataListItem,\n DataListItemLabel,\n DataListItemValue,\n Show,\n Skeleton,\n useChartCapacity,\n} from \"@kanzo-tech/ui\";\nimport { CrosshairIcon } from \"lucide-react\";\nimport { useEffect, useMemo, useState, type ReactNode } from \"react\";\nimport { nameOf } from \"../core/categories\";\nimport { bindingOf } from \"../core/channels\";\nimport { readVertex, type VertexDetail } from \"../core/detail\";\nimport {
|
|
1
|
+
{"version":3,"file":"graph-inspector.js","sources":["../../src/parts/graph-inspector.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n Badge,\n Button,\n cn,\n DataList,\n DataListItem,\n DataListItemLabel,\n DataListItemValue,\n Show,\n Skeleton,\n useChartCapacity,\n} from \"@kanzo-tech/ui\";\nimport { CrosshairIcon } from \"lucide-react\";\nimport { useEffect, useMemo, useState, type ReactNode } from \"react\";\nimport { nameOf } from \"../core/categories\";\nimport { bindingOf } from \"../core/channels\";\nimport { readVertex, type VertexDetail } from \"../core/detail\";\nimport type { VertexId } from \"../core/types\";\nimport { useGraphContext } from \"../react/graph-root\";\nimport { useGraphSnapshot, useGraphState } from \"../react/use-graph-state\";\nimport { scaleOf } from \"../render/graph-model\";\nimport { ShapeGlyph } from \"./shape-glyph\";\n\nexport interface GraphInspectorProps extends Omit<React.ComponentProps<\"div\">, \"children\"> {\n /**\n * More fields for the focused vertex, drawn inside its list after the corpus's own — a\n * `DataListItem` each. A render prop rather than a table of formatters: what a product adds is\n * markup, and the part never learns what any of it means.\n */\n children?: (detail: VertexDetail) => ReactNode;\n}\n\n/** A cell, as text — a `DATE` arrives as a `Date`, and a 64-bit column as a `bigint`. */\nconst text = (value: unknown): string => {\n if (value instanceof Date) return value.toISOString().slice(0, 10);\n if (value === null || value === undefined || value === \"\") return \"—\";\n return String(value);\n};\n\ntype Answer = { vertex: VertexId; detail: VertexDetail | null } | null;\n\n/**\n * **The focused vertex, fetched and laid out by the corpus's own tables.** The loaded graph carries\n * what the channels project and nothing else, so the rest of the row is read when a reader focuses\n * it — a scan of its table, filtered to its key, cancelled when the focus moves on. The fields are the\n * table's, in the order the manifest declares them; the key and the position are the canvas's.\n */\nexport function GraphInspector({ children, className, slot, ...rest }: GraphInspectorProps) {\n const api = useGraphContext();\n const focus = useGraphState((s) => s.focus);\n const corpus = useGraphState((s) => s.corpus);\n const geometry = useGraphSnapshot((s) => s.geometry);\n const options = useGraphState((s) => s.options);\n const domain = useGraphState((s) => s.drawn?.domain ?? s.domain);\n const capacity = useChartCapacity();\n const scale = useMemo(() => scaleOf(options, capacity), [options, capacity]);\n const [answer, setAnswer] = useState<Answer>(null);\n\n useEffect(() => {\n if (focus === null || !corpus || !geometry) return;\n const aborter = new AbortController();\n readVertex(corpus, geometry, focus, aborter.signal).then(\n (detail) => !aborter.signal.aborted && setAnswer({ vertex: focus, detail }),\n (error: unknown) => {\n if (aborter.signal.aborted) return;\n setAnswer({ vertex: focus, detail: null });\n api.getState().options.onFailure(error instanceof Error ? error.message : String(error));\n },\n );\n return () => aborter.abort();\n }, [api, corpus, focus, geometry]);\n\n const current = answer !== null && answer.vertex === focus ? answer.detail : undefined;\n const field = (name: string | undefined) => current?.fields.find((f) => f.name === name)?.value;\n const binding = bindingOf(options);\n const category = binding.byTable ? current?.table : field(binding.category);\n const rank = domain.findIndex((value) => String(value) === String(category));\n const bound = binding.byTable || binding.category !== undefined;\n const identity = geometry?.tables.find((table) => table.name === current?.table)?.identity;\n const heading = field(options.title) ?? field(identity);\n\n return (\n <div {...rest} className={cn(\"space-y-3 text-sm\", className)} data-slot={slot ?? \"graph-inspector\"}>\n <Show when={focus === null}>\n <p className=\"text-muted-foreground text-xs\">Click a vertex on the canvas to inspect it.</p>\n </Show>\n <Show when={focus !== null && current === undefined}>\n <Skeleton className=\"h-24 w-full\" />\n </Show>\n <Show when={focus !== null && current === null}>\n <p className=\"text-muted-foreground text-xs\">This vertex could not be read.</p>\n </Show>\n {current && (\n <>\n <div>\n <p className=\"truncate font-medium\" data-slot=\"graph-inspector-title\">\n {heading === undefined || heading === null ? `#${current.vertex}` : text(heading)}\n </p>\n <div className=\"mt-1 flex items-center gap-1.5\">\n <Show when={bound}>\n <Badge className=\"gap-1 text-[10px]\" size=\"xs\" variant=\"outline\">\n <ShapeGlyph\n className=\"size-2 shrink-0\"\n color={scale.color(Math.max(0, rank))}\n shape={scale.shape(Math.max(0, rank))}\n />\n {nameOf(category, options.categories)}\n </Badge>\n </Show>\n <Button\n className=\"h-5 gap-1 text-[10px]\"\n onClick={() => api.reveal(current.vertex)}\n size=\"sm\"\n title=\"Bring this vertex into view on the canvas\"\n variant=\"ghost\"\n >\n <CrosshairIcon className=\"size-3\" />\n Find on canvas\n </Button>\n </div>\n </div>\n <DataList orientation=\"vertical\">\n {current.fields.map((f) => (\n <DataListItem className=\"gap-0.5 py-0\" key={f.name}>\n <DataListItemLabel className=\"text-xs\">{f.name}</DataListItemLabel>\n <DataListItemValue className=\"break-all\">{text(f.value)}</DataListItemValue>\n </DataListItem>\n ))}\n {children?.(current)}\n </DataList>\n </>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;AAmCA;AAcO;;AACL;;AAKoC;AAAqB;AAKzD;AACE;AACA;AACA;AAAoD;AACwB;AAExE;AAEuF;AACzF;AAEmB;AAGvB;;AAC4C;AAA8C;AAQ1F;AAEI;AAEA;AAGA;AAGA;AAGI;AACE;AAEA;AAEE;AAEI;AAAA;AAAC;AAAA;AACW;AAC0B;AACA;AAAA;AAAA;AAEF;AAExC;AACA;AAAC;AAAA;AACW;AAC8B;AACnC;AACC;AACE;AAER;AAAkC;AAAE;AAAA;AAAA;AAAA;AAGxC;AACF;AAEG;AAEG;AAA+C;AACS;AAE3D;AACW;AACd;AACF;AAIR;;;;"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export type GraphLegendProps = React.ComponentProps<"div">;
|
|
2
2
|
/**
|
|
3
|
-
* **The categorical scale the canvas draws, as rows** — glyph, name and how many vertices
|
|
4
|
-
*
|
|
5
|
-
* before
|
|
6
|
-
* palette's capacity is Other, as it is on the canvas.
|
|
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.
|
|
5
|
+
* The rows are the domain fixed before the graph loaded, so a row never moves and a colour never
|
|
6
|
+
* changes hands; a category past the palette's capacity is Other, as it is on the canvas.
|
|
7
7
|
*
|
|
8
8
|
* Names are the root's `categories`, never this part's: a legend that renamed a category would be
|
|
9
9
|
* encoding one product's policy, which is the parts rule's reversal.
|
|
@@ -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,+
|
|
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,52 +1,52 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsxs as s, jsx as a } from "react/jsx-runtime";
|
|
3
|
-
import { useChartCapacity as S, Show as
|
|
3
|
+
import { useChartCapacity as S, Show as x, cn as b } from "@kanzo-tech/ui";
|
|
4
4
|
import { useMemo as O } from "react";
|
|
5
|
-
import { nameOf as
|
|
6
|
-
import { bindingOf as
|
|
5
|
+
import { nameOf as G } from "../core/categories.js";
|
|
6
|
+
import { bindingOf as j } from "../core/channels.js";
|
|
7
7
|
import { useGraphState as r } from "../react/use-graph-state.js";
|
|
8
|
-
import { scaleOf as
|
|
9
|
-
import { ShapeGlyph as
|
|
10
|
-
const
|
|
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
11
|
function E({ className: l, slot: c, ...p }) {
|
|
12
12
|
const n = r((e) => e.options), u = r((e) => {
|
|
13
13
|
var t;
|
|
14
14
|
return ((t = e.drawn) == null ? void 0 : t.domain) ?? e.domain;
|
|
15
|
-
},
|
|
15
|
+
}, C), i = r((e) => {
|
|
16
16
|
var t;
|
|
17
17
|
return ((t = e.drawn) == null ? void 0 : t.tally) ?? null;
|
|
18
|
-
}),
|
|
18
|
+
}), N = r((e) => {
|
|
19
19
|
var t;
|
|
20
|
-
return ((t = e.drawn) == null ? void 0 : t.
|
|
21
|
-
}),
|
|
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
22
|
return /* @__PURE__ */ s(
|
|
23
23
|
"div",
|
|
24
24
|
{
|
|
25
25
|
...p,
|
|
26
|
-
className:
|
|
26
|
+
className: b("rounded-lg border bg-card px-2.5 py-1.5 text-xs", l),
|
|
27
27
|
"data-slot": c ?? "graph-legend",
|
|
28
28
|
children: [
|
|
29
|
-
/* @__PURE__ */ a(
|
|
29
|
+
/* @__PURE__ */ a(x, { when: f && h.length > 0, children: /* @__PURE__ */ s("ul", { className: "space-y-1", "data-slot": "graph-legend-items", children: [
|
|
30
30
|
h.map((e, t) => /* @__PURE__ */ s("li", { className: "flex items-center gap-2", "data-slot": "graph-legend-item", children: [
|
|
31
|
-
/* @__PURE__ */ a(
|
|
32
|
-
/* @__PURE__ */ a("span", { className: "truncate", children:
|
|
33
|
-
/* @__PURE__ */ a("span", { className: "ms-auto ps-4 text-muted-foreground tabular-nums", children: m(
|
|
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
34
|
] }, String(e))),
|
|
35
|
-
/* @__PURE__ */ a(
|
|
36
|
-
/* @__PURE__ */ a(
|
|
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
37
|
/* @__PURE__ */ a("span", { children: "Other" }),
|
|
38
|
-
/* @__PURE__ */ a("span", { className: "ms-auto ps-4 text-muted-foreground tabular-nums", children: m(
|
|
38
|
+
/* @__PURE__ */ a("span", { className: "ms-auto ps-4 text-muted-foreground tabular-nums", children: m(v) })
|
|
39
39
|
] }) })
|
|
40
40
|
] }) }),
|
|
41
41
|
/* @__PURE__ */ s(
|
|
42
42
|
"p",
|
|
43
43
|
{
|
|
44
|
-
className:
|
|
44
|
+
className: b("text-muted-foreground tabular-nums", f && h.length > 0 && "mt-1 border-t pt-1"),
|
|
45
45
|
"data-slot": "graph-legend-count",
|
|
46
46
|
children: [
|
|
47
|
-
m(
|
|
47
|
+
m(N),
|
|
48
48
|
" of ",
|
|
49
|
-
m(
|
|
49
|
+
m(w),
|
|
50
50
|
" drawn"
|
|
51
51
|
]
|
|
52
52
|
}
|
|
@@ -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
|
|
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;;;;"}
|
|
@@ -4,7 +4,7 @@ export interface GraphToolbarProps extends React.ComponentProps<"div"> {
|
|
|
4
4
|
}
|
|
5
5
|
/**
|
|
6
6
|
* **The commands, drawn** — the selection tools, the selection and what to do with it, the camera, and
|
|
7
|
-
* the live layout's transport
|
|
7
|
+
* the live layout's transport, which starts a layout from the points where they are. Each cluster is a `ButtonGroup`, because a cluster
|
|
8
8
|
* is a claim that its buttons do one job; the part declares no `toolbar` role, since a `role` of that
|
|
9
9
|
* kind promises roving focus and each button here is its own tab stop.
|
|
10
10
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-toolbar.d.ts","sourceRoot":"","sources":["../../src/parts/graph-toolbar.tsx"],"names":[],"mappings":"AAoBA,MAAM,WAAW,iBAAkB,SAAQ,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC;IACpE,uFAAuF;IACvF,WAAW,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC;CACzC;AAmBD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,EAAE,SAAS,EAAE,WAA0B,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,EAAE,iBAAiB,+
|
|
1
|
+
{"version":3,"file":"graph-toolbar.d.ts","sourceRoot":"","sources":["../../src/parts/graph-toolbar.tsx"],"names":[],"mappings":"AAoBA,MAAM,WAAW,iBAAkB,SAAQ,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC;IACpE,uFAAuF;IACvF,WAAW,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC;CACzC;AAmBD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,EAAE,SAAS,EAAE,WAA0B,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,EAAE,iBAAiB,+BAkGvG"}
|
|
@@ -1,94 +1,94 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsxs as s, jsx as e } from "react/jsx-runtime";
|
|
3
|
-
import { ButtonGroup as h, Button as
|
|
4
|
-
import { SquareDashedIcon as
|
|
5
|
-
import { useGraphContext as
|
|
6
|
-
import { useGraphState as
|
|
7
|
-
const m = "rounded-lg border bg-card shadow-sm", v = 1.4,
|
|
3
|
+
import { ButtonGroup as h, Button as o, ButtonGroupSeparator as i, Show as C, ButtonGroupText as I, cn as k } from "@kanzo-tech/ui";
|
|
4
|
+
import { SquareDashedIcon as y, LassoIcon as R, ScanIcon as N, XIcon as w, PlusIcon as T, MinusIcon as B, MaximizeIcon as G, PauseIcon as L, PlayIcon as P, RotateCcwIcon as M, PinOffIcon as O } from "lucide-react";
|
|
5
|
+
import { useGraphContext as Z } from "../react/graph-root.js";
|
|
6
|
+
import { useGraphState as r } from "../react/use-graph-state.js";
|
|
7
|
+
const m = "rounded-lg border bg-card shadow-sm", v = 1.4, j = {
|
|
8
8
|
running: { pause: !0, label: "Pause the layout" },
|
|
9
9
|
paused: { pause: !1, label: "Resume the layout" },
|
|
10
|
-
settled: { pause: !1, label: "
|
|
10
|
+
settled: { pause: !1, label: "Run the layout" }
|
|
11
11
|
};
|
|
12
|
-
function
|
|
13
|
-
const
|
|
12
|
+
function E({ className: z, orientation: c = "horizontal", slot: x, ...S }) {
|
|
13
|
+
const a = Z(), t = r((l) => l.corpus !== null), n = r((l) => l.tool), d = r((l) => l.selection), g = r((l) => l.total), u = r((l) => l.motion), p = r((l) => l.pinned.length), b = j[u], f = `Release ${p} pinned ${p === 1 ? "node" : "nodes"}`;
|
|
14
14
|
return /* @__PURE__ */ s(
|
|
15
15
|
"div",
|
|
16
16
|
{
|
|
17
17
|
...S,
|
|
18
|
-
className:
|
|
18
|
+
className: k("flex items-start gap-1.5", c === "vertical" && "flex-col items-end", z),
|
|
19
19
|
"data-slot": x ?? "graph-toolbar",
|
|
20
20
|
children: [
|
|
21
21
|
/* @__PURE__ */ s(h, { "aria-label": "Selection tool", className: m, orientation: c, children: [
|
|
22
22
|
/* @__PURE__ */ e(
|
|
23
|
-
|
|
23
|
+
o,
|
|
24
24
|
{
|
|
25
25
|
"aria-label": "Marquee",
|
|
26
26
|
"aria-pressed": n === "rect",
|
|
27
|
-
disabled: !
|
|
28
|
-
onClick: () =>
|
|
27
|
+
disabled: !t,
|
|
28
|
+
onClick: () => a.setTool(n === "rect" ? null : "rect"),
|
|
29
29
|
size: "icon-sm",
|
|
30
30
|
title: "Drag a box — or just hold Shift",
|
|
31
31
|
variant: n === "rect" ? "default" : "ghost",
|
|
32
|
-
children: /* @__PURE__ */ e(
|
|
32
|
+
children: /* @__PURE__ */ e(y, {})
|
|
33
33
|
}
|
|
34
34
|
),
|
|
35
|
-
/* @__PURE__ */ e(
|
|
35
|
+
/* @__PURE__ */ e(i, {}),
|
|
36
36
|
/* @__PURE__ */ e(
|
|
37
|
-
|
|
37
|
+
o,
|
|
38
38
|
{
|
|
39
39
|
"aria-label": "Lasso",
|
|
40
40
|
"aria-pressed": n === "lasso",
|
|
41
|
-
disabled: !
|
|
42
|
-
onClick: () =>
|
|
41
|
+
disabled: !t,
|
|
42
|
+
onClick: () => a.setTool(n === "lasso" ? null : "lasso"),
|
|
43
43
|
size: "icon-sm",
|
|
44
44
|
title: "Draw a loop around a cluster",
|
|
45
45
|
variant: n === "lasso" ? "default" : "ghost",
|
|
46
|
-
children: /* @__PURE__ */ e(
|
|
46
|
+
children: /* @__PURE__ */ e(R, {})
|
|
47
47
|
}
|
|
48
48
|
)
|
|
49
49
|
] }),
|
|
50
|
-
/* @__PURE__ */ e(
|
|
50
|
+
/* @__PURE__ */ e(C, { when: d !== null, children: /* @__PURE__ */ s(h, { "aria-label": "Current selection", className: m, "data-slot": "graph-toolbar-selection", children: [
|
|
51
51
|
/* @__PURE__ */ s(I, { className: "gap-1 ps-2 pe-2 text-xs tabular-nums", children: [
|
|
52
52
|
/* @__PURE__ */ e("span", { className: "font-medium text-foreground", children: d == null ? void 0 : d.vertices.length.toLocaleString() }),
|
|
53
|
-
|
|
53
|
+
g === void 0 ? " selected" : ` of ${g.toLocaleString()} selected`
|
|
54
54
|
] }),
|
|
55
|
-
/* @__PURE__ */ e(
|
|
56
|
-
/* @__PURE__ */ e(
|
|
57
|
-
/* @__PURE__ */ e(
|
|
55
|
+
/* @__PURE__ */ e(i, {}),
|
|
56
|
+
/* @__PURE__ */ e(o, { "aria-label": "Frame the selection", onClick: () => a.frameSelection(), size: "icon-sm", title: "Frame the selection", variant: "ghost", children: /* @__PURE__ */ e(N, {}) }),
|
|
57
|
+
/* @__PURE__ */ e(o, { "aria-label": "Clear the selection", onClick: () => a.clear(), size: "icon-sm", title: "Clear the selection", variant: "ghost", children: /* @__PURE__ */ e(w, {}) })
|
|
58
58
|
] }) }),
|
|
59
59
|
/* @__PURE__ */ s(h, { "aria-label": "Zoom and fit", className: m, orientation: c, children: [
|
|
60
|
-
/* @__PURE__ */ e(
|
|
61
|
-
/* @__PURE__ */ e(
|
|
62
|
-
/* @__PURE__ */ e(
|
|
63
|
-
/* @__PURE__ */ e(
|
|
64
|
-
/* @__PURE__ */ e(
|
|
60
|
+
/* @__PURE__ */ e(o, { "aria-label": "Zoom in", disabled: !t, onClick: () => a.zoomBy(v), size: "icon-sm", variant: "ghost", children: /* @__PURE__ */ e(T, {}) }),
|
|
61
|
+
/* @__PURE__ */ e(i, {}),
|
|
62
|
+
/* @__PURE__ */ e(o, { "aria-label": "Zoom out", disabled: !t, onClick: () => a.zoomBy(1 / v), size: "icon-sm", variant: "ghost", children: /* @__PURE__ */ e(B, {}) }),
|
|
63
|
+
/* @__PURE__ */ e(i, {}),
|
|
64
|
+
/* @__PURE__ */ e(o, { "aria-label": "Fit to view", disabled: !t, onClick: () => a.fit(), size: "icon-sm", variant: "ghost", children: /* @__PURE__ */ e(G, {}) })
|
|
65
65
|
] }),
|
|
66
|
-
/* @__PURE__ */
|
|
66
|
+
/* @__PURE__ */ s(h, { "aria-label": "Layout", className: m, orientation: c, children: [
|
|
67
67
|
/* @__PURE__ */ e(
|
|
68
|
-
|
|
68
|
+
o,
|
|
69
69
|
{
|
|
70
70
|
"aria-label": b.label,
|
|
71
71
|
"aria-pressed": u === "paused",
|
|
72
|
-
disabled: !
|
|
73
|
-
onClick: () => b.pause ?
|
|
72
|
+
disabled: !t,
|
|
73
|
+
onClick: () => b.pause ? a.pause() : a.resume(),
|
|
74
74
|
size: "icon-sm",
|
|
75
75
|
title: b.label,
|
|
76
76
|
variant: u === "paused" ? "default" : "ghost",
|
|
77
|
-
children: u === "running" ? /* @__PURE__ */ e(
|
|
77
|
+
children: u === "running" ? /* @__PURE__ */ e(L, {}) : /* @__PURE__ */ e(P, {})
|
|
78
78
|
}
|
|
79
79
|
),
|
|
80
|
-
/* @__PURE__ */ e(
|
|
81
|
-
/* @__PURE__ */ e(
|
|
82
|
-
/* @__PURE__ */ s(
|
|
83
|
-
/* @__PURE__ */ e(
|
|
84
|
-
/* @__PURE__ */ e(
|
|
80
|
+
/* @__PURE__ */ e(i, {}),
|
|
81
|
+
/* @__PURE__ */ e(o, { "aria-label": "Re-run the layout", disabled: !t, onClick: () => a.restart(), size: "icon-sm", title: "Re-run the layout", variant: "ghost", children: /* @__PURE__ */ e(M, {}) }),
|
|
82
|
+
/* @__PURE__ */ s(C, { when: p > 0, children: [
|
|
83
|
+
/* @__PURE__ */ e(i, {}),
|
|
84
|
+
/* @__PURE__ */ e(o, { "aria-label": f, onClick: () => a.unpin(), size: "icon-sm", title: f, variant: "ghost", children: /* @__PURE__ */ e(O, {}) })
|
|
85
85
|
] })
|
|
86
|
-
] })
|
|
86
|
+
] })
|
|
87
87
|
]
|
|
88
88
|
}
|
|
89
89
|
);
|
|
90
90
|
}
|
|
91
91
|
export {
|
|
92
|
-
|
|
92
|
+
E as GraphToolbar
|
|
93
93
|
};
|
|
94
94
|
//# sourceMappingURL=graph-toolbar.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-toolbar.js","sources":["../../src/parts/graph-toolbar.tsx"],"sourcesContent":["\"use client\";\n\nimport { Button, ButtonGroup, ButtonGroupSeparator, ButtonGroupText, cn, Show } from \"@kanzo-tech/ui\";\nimport {\n LassoIcon,\n MaximizeIcon,\n MinusIcon,\n PauseIcon,\n PinOffIcon,\n PlayIcon,\n PlusIcon,\n RotateCcwIcon,\n ScanIcon,\n SquareDashedIcon,\n XIcon,\n} from \"lucide-react\";\nimport type { Motion } from \"../core/types\";\nimport { useGraphContext } from \"../react/graph-root\";\nimport { useGraphState } from \"../react/use-graph-state\";\n\nexport interface GraphToolbarProps extends React.ComponentProps<\"div\"> {\n /** How the clusters stack. Each cluster is a `ButtonGroup` in the same orientation. */\n orientation?: \"horizontal\" | \"vertical\";\n}\n\n/**\n * Solid, not `bg-card/85 backdrop-blur-md`: a diluted surface is a function of whatever the layout\n * put behind it — measured over the plane and the eight slots, `card/85` spans ΔE 9.4–11.1, against\n * the ramp's own `interchangeable` bound of 4. The buttons are `ghost` because the group carries the\n * surface, so the divisions come back as explicit separators.\n */\nconst CLUSTER = \"rounded-lg border bg-card shadow-sm\";\n\nconst ZOOM = 1.4;\n\n/** One action per state, named for what it does: a settled layout is not paused, so it is woken. */\nconst TRANSPORT: Record<Motion, { pause: boolean; label: string }> = {\n running: { pause: true, label: \"Pause the layout\" },\n paused: { pause: false, label: \"Resume the layout\" },\n settled: { pause: false, label: \"
|
|
1
|
+
{"version":3,"file":"graph-toolbar.js","sources":["../../src/parts/graph-toolbar.tsx"],"sourcesContent":["\"use client\";\n\nimport { Button, ButtonGroup, ButtonGroupSeparator, ButtonGroupText, cn, Show } from \"@kanzo-tech/ui\";\nimport {\n LassoIcon,\n MaximizeIcon,\n MinusIcon,\n PauseIcon,\n PinOffIcon,\n PlayIcon,\n PlusIcon,\n RotateCcwIcon,\n ScanIcon,\n SquareDashedIcon,\n XIcon,\n} from \"lucide-react\";\nimport type { Motion } from \"../core/types\";\nimport { useGraphContext } from \"../react/graph-root\";\nimport { useGraphState } from \"../react/use-graph-state\";\n\nexport interface GraphToolbarProps extends React.ComponentProps<\"div\"> {\n /** How the clusters stack. Each cluster is a `ButtonGroup` in the same orientation. */\n orientation?: \"horizontal\" | \"vertical\";\n}\n\n/**\n * Solid, not `bg-card/85 backdrop-blur-md`: a diluted surface is a function of whatever the layout\n * put behind it — measured over the plane and the eight slots, `card/85` spans ΔE 9.4–11.1, against\n * the ramp's own `interchangeable` bound of 4. The buttons are `ghost` because the group carries the\n * surface, so the divisions come back as explicit separators.\n */\nconst CLUSTER = \"rounded-lg border bg-card shadow-sm\";\n\nconst ZOOM = 1.4;\n\n/** One action per state, named for what it does: a settled layout is not paused, so it is woken. */\nconst TRANSPORT: Record<Motion, { pause: boolean; label: string }> = {\n running: { pause: true, label: \"Pause the layout\" },\n paused: { pause: false, label: \"Resume the layout\" },\n settled: { pause: false, label: \"Run the layout\" },\n};\n\n/**\n * **The commands, drawn** — the selection tools, the selection and what to do with it, the camera, and\n * the live layout's transport, which starts a layout from the points where they are. Each cluster is a `ButtonGroup`, because a cluster\n * is a claim that its buttons do one job; the part declares no `toolbar` role, since a `role` of that\n * kind promises roving focus and each button here is its own tab stop.\n *\n * The tools are not a `SegmentGroup`: that always has one member selected, and no tool — drag pans —\n * is the posture a reader spends most of their time in.\n */\nexport function GraphToolbar({ className, orientation = \"horizontal\", slot, ...rest }: GraphToolbarProps) {\n const api = useGraphContext();\n const ready = useGraphState((s) => s.corpus !== null);\n const tool = useGraphState((s) => s.tool);\n const selection = useGraphState((s) => s.selection);\n const total = useGraphState((s) => s.total);\n const motion = useGraphState((s) => s.motion);\n const pinned = useGraphState((s) => s.pinned.length);\n const transport = TRANSPORT[motion];\n const release = `Release ${pinned} pinned ${pinned === 1 ? \"node\" : \"nodes\"}`;\n\n return (\n <div\n {...rest}\n className={cn(\"flex items-start gap-1.5\", orientation === \"vertical\" && \"flex-col items-end\", className)}\n data-slot={slot ?? \"graph-toolbar\"}\n >\n <ButtonGroup aria-label=\"Selection tool\" className={CLUSTER} orientation={orientation}>\n <Button\n aria-label=\"Marquee\"\n aria-pressed={tool === \"rect\"}\n disabled={!ready}\n onClick={() => api.setTool(tool === \"rect\" ? null : \"rect\")}\n size=\"icon-sm\"\n title=\"Drag a box — or just hold Shift\"\n variant={tool === \"rect\" ? \"default\" : \"ghost\"}\n >\n <SquareDashedIcon />\n </Button>\n <ButtonGroupSeparator />\n <Button\n aria-label=\"Lasso\"\n aria-pressed={tool === \"lasso\"}\n disabled={!ready}\n onClick={() => api.setTool(tool === \"lasso\" ? null : \"lasso\")}\n size=\"icon-sm\"\n title=\"Draw a loop around a cluster\"\n variant={tool === \"lasso\" ? \"default\" : \"ghost\"}\n >\n <LassoIcon />\n </Button>\n </ButtonGroup>\n\n <Show when={selection !== null}>\n <ButtonGroup aria-label=\"Current selection\" className={CLUSTER} data-slot=\"graph-toolbar-selection\">\n <ButtonGroupText className=\"gap-1 ps-2 pe-2 text-xs tabular-nums\">\n <span className=\"font-medium text-foreground\">{selection?.vertices.length.toLocaleString()}</span>\n {total === undefined ? \" selected\" : ` of ${total.toLocaleString()} selected`}\n </ButtonGroupText>\n <ButtonGroupSeparator />\n <Button aria-label=\"Frame the selection\" onClick={() => api.frameSelection()} size=\"icon-sm\" title=\"Frame the selection\" variant=\"ghost\">\n <ScanIcon />\n </Button>\n <Button aria-label=\"Clear the selection\" onClick={() => api.clear()} size=\"icon-sm\" title=\"Clear the selection\" variant=\"ghost\">\n <XIcon />\n </Button>\n </ButtonGroup>\n </Show>\n\n <ButtonGroup aria-label=\"Zoom and fit\" className={CLUSTER} orientation={orientation}>\n <Button aria-label=\"Zoom in\" disabled={!ready} onClick={() => api.zoomBy(ZOOM)} size=\"icon-sm\" variant=\"ghost\">\n <PlusIcon />\n </Button>\n <ButtonGroupSeparator />\n <Button aria-label=\"Zoom out\" disabled={!ready} onClick={() => api.zoomBy(1 / ZOOM)} size=\"icon-sm\" variant=\"ghost\">\n <MinusIcon />\n </Button>\n <ButtonGroupSeparator />\n <Button aria-label=\"Fit to view\" disabled={!ready} onClick={() => api.fit()} size=\"icon-sm\" variant=\"ghost\">\n <MaximizeIcon />\n </Button>\n </ButtonGroup>\n\n <ButtonGroup aria-label=\"Layout\" className={CLUSTER} orientation={orientation}>\n <Button\n aria-label={transport.label}\n aria-pressed={motion === \"paused\"}\n disabled={!ready}\n onClick={() => (transport.pause ? api.pause() : api.resume())}\n size=\"icon-sm\"\n title={transport.label}\n variant={motion === \"paused\" ? \"default\" : \"ghost\"}\n >\n {motion === \"running\" ? <PauseIcon /> : <PlayIcon />}\n </Button>\n <ButtonGroupSeparator />\n <Button aria-label=\"Re-run the layout\" disabled={!ready} onClick={() => api.restart()} size=\"icon-sm\" title=\"Re-run the layout\" variant=\"ghost\">\n <RotateCcwIcon />\n </Button>\n <Show when={pinned > 0}>\n <ButtonGroupSeparator />\n <Button aria-label={release} onClick={() => api.unpin()} size=\"icon-sm\" title={release} variant=\"ghost\">\n <PinOffIcon />\n </Button>\n </Show>\n </ButtonGroup>\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;AA+BA;AAKqE;AACpC;AACA;AAEjC;AAWO;AACL;AAUA;AACE;AAAC;AAAA;AACK;AACmG;AACpF;AAEnB;AACE;AAAA;AAAC;AAAA;AACY;AACY;AACZ;AAC+C;AACrD;AACC;AACiC;AAErB;AAAA;AAAA;AAEE;AACtB;AAAC;AAAA;AACY;AACY;AACZ;AACiD;AACvD;AACC;AACkC;AAE7B;AAAA;AAAA;AAEf;AAII;AACE;AAA2F;AACzB;AACpE;AACsB;AAGtB;AAGA;AAEJ;AAGE;AAEA;AACsB;AAGtB;AACsB;AAGtB;AACF;AAGE;AAAA;AAAC;AAAA;AACuB;AACG;AACd;AACyC;AAC/C;AACY;AAC0B;AAEO;AAAA;AAAA;AAE9B;AAGtB;AAEE;AAAsB;AAGtB;AACF;AACF;AAAA;AAAA;AAGN;;;;"}
|