@poodle64/ui 2026.10.16 → 2026.10.17
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 +41 -0
- package/dist/components/ui/graph-canvas/graph-canvas.svelte +922 -0
- package/dist/components/ui/graph-canvas/graph-canvas.svelte.d.ts +73 -0
- package/dist/components/ui/graph-canvas/index.d.ts +3 -0
- package/dist/components/ui/graph-canvas/index.js +2 -0
- package/dist/components/ui/graph-canvas/scene.d.ts +107 -0
- package/dist/components/ui/graph-canvas/scene.js +562 -0
- package/dist/components/ui/graph-canvas/types.d.ts +33 -0
- package/dist/components/ui/graph-canvas/types.js +1 -0
- package/package.json +4 -2
- package/registry/component-map.json +17 -3
- package/registry/component-map.md +9 -1
package/README.md
CHANGED
|
@@ -119,6 +119,7 @@ rather than rebuilding it:
|
|
|
119
119
|
| `search-results` | A ranked retrieval answer: title, the matched passage with accent-tinted highlights, source chip, mapped state and a mono relevance figure per hit; plus the before-any-search and matched-nothing empties. |
|
|
120
120
|
| `ledger` | The one ledger every module draws: dated rows of money, coins or units, two lines a row, grouped by the viewer's period, the viewer's columns and running balance. See [The ledger](#the-ledger). |
|
|
121
121
|
| `resize-handle` | The drag edge of a side panel: pointer, arrow keys, double-click reset, remembered width, ceiling measured against the content beside it. AppShell's rail uses it; an app with its own `<aside>` renders `<ResizeHandle bind:width storageKey="…" />` as the aside's last child (positioned) and sets the aside's width from `width`. |
|
|
122
|
+
| `graph-canvas` | A live force-directed graph of entities and their relations: pan, zoom, pinch, find, a legend that filters, a selected node's neighbourhood lit and framed, its card. See [A graph of relations](#a-graph-of-relations). |
|
|
122
123
|
|
|
123
124
|
**The application shell** (`app-shell`, `command-palette`). Page chrome is not
|
|
124
125
|
what makes an app feel like an app either — the shell is. Five household
|
|
@@ -1051,6 +1052,46 @@ the page opened; that item's document stands beside it (`?surface=record`).
|
|
|
1051
1052
|
into the same pane opens on its first page, fitted; so does one the column
|
|
1052
1053
|
moves beside itself as the row crosses 1250px.
|
|
1053
1054
|
|
|
1055
|
+
## A graph of relations
|
|
1056
|
+
|
|
1057
|
+
`GraphCanvas` (`@poodle64/ui/graph-canvas`) draws people, organisations, sources, any entities, as nodes and their relations as edges, laid out live by d3-force and painted on one canvas. It fills its container, so the page gives it a height.
|
|
1058
|
+
|
|
1059
|
+
```svelte
|
|
1060
|
+
<script lang="ts">
|
|
1061
|
+
import GraphCanvas, { type GraphKind } from '@poodle64/ui/graph-canvas';
|
|
1062
|
+
const kinds: GraphKind[] = [
|
|
1063
|
+
{ id: 'person', label: 'People', color: 'var(--chart-1)' },
|
|
1064
|
+
{ id: 'organisation', label: 'Organisations', color: 'var(--chart-2)', shape: 'square' },
|
|
1065
|
+
{ id: 'source', label: 'Sources', color: 'var(--chart-3)', shape: 'diamond' }
|
|
1066
|
+
];
|
|
1067
|
+
let graph = $state.raw({ nodes: [], edges: [] });
|
|
1068
|
+
let selected = $state<string | null>(null);
|
|
1069
|
+
</script>
|
|
1070
|
+
|
|
1071
|
+
<div class="h-[70vh]">
|
|
1072
|
+
<GraphCanvas nodes={graph.nodes} edges={graph.edges} {kinds} directed bind:selected
|
|
1073
|
+
onactivate={(n) => goto(`/entities/${n.id}`)} />
|
|
1074
|
+
</div>
|
|
1075
|
+
```
|
|
1076
|
+
|
|
1077
|
+
| Prop | |
|
|
1078
|
+
| --- | --- |
|
|
1079
|
+
| `nodes` | `{ id, label, kind, description?, emphasis?, size? }[]`. Extra fields ride along to the callbacks and the `detail` snippet, typed. |
|
|
1080
|
+
| `edges` | `{ source, target, label? }[]` by node id; an edge naming an unknown id is dropped. Parallel edges fan out. |
|
|
1081
|
+
| `kinds` | Legend order, `label`, `color` (any CSS colour; default the primary) and `shape` (`circle`, `square`, `diamond`, `triangle`). The package owns no categorical palette, as with `Sparkline`. |
|
|
1082
|
+
| `selected`, `hiddenKinds` | Bindable: the selected id, and the kinds the legend has switched off. |
|
|
1083
|
+
| `directed` | Arrowheads at each target, and the card names a relation pointing here as "Director of this". |
|
|
1084
|
+
| `onselect(node \| null)`, `onactivate(node)` | A click, find or card pick; a double-click, Enter or the card's `activateLabel` button. |
|
|
1085
|
+
| `detail` | `Snippet<[node, neighbours]>` under the card's title, for the app's own actions. |
|
|
1086
|
+
| `search`, `label` | The find field (default on); the accessible name. |
|
|
1087
|
+
|
|
1088
|
+
Exports on the instance (`bind:this`): `fit()`, `focus(id)` (select and frame the neighbourhood), `locate(id)` (where it is drawn, in px) and `relayout()`.
|
|
1089
|
+
|
|
1090
|
+
- **Live data.** Pass `$state.raw` and assign a new array to change it. Every surviving node keeps its place, a newcomer starts beside the nodes it joins, and the layout warms only as much as the change needs. A deep `$state` proxy works, but every paint then reads through it.
|
|
1091
|
+
- **Themed where it is placed.** Each colour is resolved against the component's own element, so inside an element's shadow root it takes the host page's palette, and it follows the mode class, an element host's `data-dark`, a palette swap and the OS scheme without a reload. Nothing portals out.
|
|
1092
|
+
- **Size.** The layout ticks within a time budget per frame, edges and nodes are culled and stroked as a few batched paths, and names are placed in screen space against what is already drawn, a quota by area at rest and every neighbour under a focus. A few thousand nodes is the design point; well past ten thousand, a WebGL renderer is the better tool.
|
|
1093
|
+
- **Load it lazily.** It brings d3-force and a canvas engine, so a route imports it with a dynamic `import()` (`sveltekit-quality.md`).
|
|
1094
|
+
|
|
1054
1095
|
## Hand-written forms
|
|
1055
1096
|
|
|
1056
1097
|
The shadcn-svelte Formsnap wrapper set, over `sveltekit-superforms`:
|