@zvk/graphs 0.1.1 → 0.1.3
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/CHANGELOG.md +14 -0
- package/README.md +380 -4
- package/dist/algorithms.d.ts +73 -1
- package/dist/algorithms.js +287 -0
- package/dist/diagnostics.d.ts +74 -2
- package/dist/diagnostics.js +577 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/layout/dag.d.ts +5 -2
- package/dist/layout/dag.js +38 -12
- package/dist/layout/manual.d.ts +3 -2
- package/dist/layout/manual.js +11 -8
- package/dist/layout/tree.d.ts +3 -2
- package/dist/layout/tree.js +15 -8
- package/dist/layout/types.d.ts +76 -2
- package/dist/layout/types.js +336 -9
- package/dist/model.d.ts +47 -0
- package/dist/serialization.d.ts +14 -0
- package/dist/serialization.js +68 -0
- package/dist/styles.css +209 -7
- package/dist/svg.js +234 -19
- package/package.json +23 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @zvk/graphs Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.3](https://github.com/brandon-schabel/zvk/compare/graphs-v0.1.2...graphs-v0.1.3) (2026-06-20)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* improve package release workflow and repo maintainability ([#22](https://github.com/brandon-schabel/zvk/issues/22)) ([a41cb66](https://github.com/brandon-schabel/zvk/commit/a41cb66554496f241c5a8e30b29a76c8b8ca92b3))
|
|
9
|
+
|
|
10
|
+
## [0.1.2](https://github.com/brandon-schabel/zvk/compare/graphs-v0.1.1...graphs-v0.1.2) (2026-06-19)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* add theme presets and package improvements ([#19](https://github.com/brandon-schabel/zvk/issues/19)) ([867f955](https://github.com/brandon-schabel/zvk/commit/867f9556f7a60144cad4e747f2c032f2d5ede353))
|
|
16
|
+
|
|
3
17
|
## [0.1.1](https://github.com/brandon-schabel/zvk/compare/graphs-v0.1.0...graphs-v0.1.1) (2026-06-18)
|
|
4
18
|
|
|
5
19
|
|
package/README.md
CHANGED
|
@@ -15,13 +15,22 @@ import "@zvk/graphs/styles.css";
|
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
Public subpaths are `@zvk/graphs`, `@zvk/graphs/model`,
|
|
18
|
-
`@zvk/graphs/diagnostics`, `@zvk/graphs/algorithms`,
|
|
19
|
-
`@zvk/graphs/
|
|
18
|
+
`@zvk/graphs/diagnostics`, `@zvk/graphs/algorithms`,
|
|
19
|
+
`@zvk/graphs/serialization`, `@zvk/graphs/layout`, `@zvk/graphs/svg`,
|
|
20
|
+
`@zvk/graphs/styles.css`, and
|
|
20
21
|
`@zvk/graphs/package.json`.
|
|
21
22
|
|
|
22
23
|
Do not import from `src`, `dist`, package internals, or private relative paths.
|
|
23
24
|
Treat `packages/graphs/package.json` `exports` as the public API contract.
|
|
24
25
|
|
|
26
|
+
## Package Docs And Examples
|
|
27
|
+
|
|
28
|
+
Package-local guides live in `packages/graphs/docs/README.md`. They cover
|
|
29
|
+
modeling, algorithms, diagnostics, serialization, deterministic layout, static
|
|
30
|
+
SVG rendering, accessibility, app composition boundaries, and anti-goals.
|
|
31
|
+
Runnable examples live in `packages/graphs/examples/` and must use only public
|
|
32
|
+
`@zvk/graphs` imports.
|
|
33
|
+
|
|
25
34
|
## Styles
|
|
26
35
|
|
|
27
36
|
Import graph styles once from the public stylesheet:
|
|
@@ -43,6 +52,11 @@ and graph-specific `data-*` attributes. It may fall back to `--zvk-ui-*` tokens
|
|
|
43
52
|
when present, but package source must not import `@zvk/ui` JavaScript or
|
|
44
53
|
component internals for styling.
|
|
45
54
|
|
|
55
|
+
Graph typography uses `--zvk-graphs-font-primary`, `--zvk-graphs-font-secondary`,
|
|
56
|
+
and `--zvk-graphs-font-tertiary`. These inherit `--zvk-ui-font-family-primary`,
|
|
57
|
+
`--zvk-ui-font-family-secondary`, and `--zvk-ui-font-family-tertiary` when UI
|
|
58
|
+
styles are present.
|
|
59
|
+
|
|
46
60
|
## Accessibility And SSR
|
|
47
61
|
|
|
48
62
|
- Require a graph title, node labels, and meaningful edge labels or derivable
|
|
@@ -57,6 +71,281 @@ component internals for styling.
|
|
|
57
71
|
- Use deterministic IDs and layout output. Do not use random IDs, timestamps, or
|
|
58
72
|
DOM measurement for default layout.
|
|
59
73
|
|
|
74
|
+
## Fallback Details
|
|
75
|
+
|
|
76
|
+
`GraphFigure` renders structured fallback content by default. Node fallback
|
|
77
|
+
details include documented summary fields such as `summary`, `accessibility`,
|
|
78
|
+
`ariaDescription`, `kind`, `groupId`, `status`, `presentation`, structured
|
|
79
|
+
primitive `metadata`, and primitive string/number/boolean `data` values. Edge
|
|
80
|
+
fallback details include source and target labels, route kind, accessibility
|
|
81
|
+
text, status, summary, structured primitive metadata, and primitive edge data.
|
|
82
|
+
Arbitrary object data is not dumped into the DOM by default.
|
|
83
|
+
|
|
84
|
+
Use `fallbackMode="tree"` when a graph is a one-root tree and should expose a
|
|
85
|
+
nested fallback. Invalid or multi-root trees degrade to the list fallback. Use
|
|
86
|
+
`fallbackMode="none"` only when the host application renders equivalent graph
|
|
87
|
+
details elsewhere.
|
|
88
|
+
|
|
89
|
+
## Static Metadata And Presentation
|
|
90
|
+
|
|
91
|
+
Nodes and edges can expose app-owned details through serializable metadata and
|
|
92
|
+
accessibility fields:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
const graph = {
|
|
96
|
+
title: "Deploy graph",
|
|
97
|
+
description: "Release approval flow.",
|
|
98
|
+
nodes: [
|
|
99
|
+
{
|
|
100
|
+
id: "gate",
|
|
101
|
+
label: "Deploy gate",
|
|
102
|
+
labelLines: ["Deploy", "gate"],
|
|
103
|
+
metadata: [
|
|
104
|
+
{ key: "owner", label: "Owner", value: "Platform", visibility: "summary", tone: "info" },
|
|
105
|
+
{ key: "risk", label: "Risk", value: "Medium", visibility: "detail", tone: "warning" }
|
|
106
|
+
],
|
|
107
|
+
accessibility: {
|
|
108
|
+
label: "Deploy gate decision",
|
|
109
|
+
description: "Decision node for deployment approval."
|
|
110
|
+
},
|
|
111
|
+
presentation: {
|
|
112
|
+
shape: "diamond",
|
|
113
|
+
density: "compact",
|
|
114
|
+
tone: "warning",
|
|
115
|
+
badge: "Gate",
|
|
116
|
+
glyph: "?"
|
|
117
|
+
},
|
|
118
|
+
status: "warning",
|
|
119
|
+
position: { x: 0, y: 0 }
|
|
120
|
+
}
|
|
121
|
+
],
|
|
122
|
+
edges: []
|
|
123
|
+
};
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The SVG renderer only displays metadata marked `visibility: "summary"` so
|
|
127
|
+
callers have to opt into compact visible details. Fallback content renders all
|
|
128
|
+
primitive metadata values. Use `renderNode` for advanced custom SVG output, but
|
|
129
|
+
prefer built-in `shape`, `badge`, `glyph`, `labelLines`, and metadata summaries
|
|
130
|
+
for common static graph details.
|
|
131
|
+
|
|
132
|
+
## Static Groups
|
|
133
|
+
|
|
134
|
+
Graphs can define static groups and attach nodes through `groupId`:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
const graph = {
|
|
138
|
+
title: "Service map",
|
|
139
|
+
groups: [
|
|
140
|
+
{
|
|
141
|
+
id: "backend",
|
|
142
|
+
label: "Backend services",
|
|
143
|
+
summary: "Platform-owned services.",
|
|
144
|
+
tone: "info",
|
|
145
|
+
status: "active"
|
|
146
|
+
}
|
|
147
|
+
],
|
|
148
|
+
nodes: [
|
|
149
|
+
{ id: "api", label: "API", groupId: "backend", position: { x: 0, y: 0 } },
|
|
150
|
+
{ id: "worker", label: "Worker", groupId: "backend", position: { x: 140, y: 0 } }
|
|
151
|
+
],
|
|
152
|
+
edges: []
|
|
153
|
+
};
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Static groups render as non-interactive SVG containers beneath edges and nodes.
|
|
157
|
+
When a group has explicit `position` and `size`, those bounds are used;
|
|
158
|
+
otherwise `deriveGraphGroupBounds` derives padded bounds from positioned member
|
|
159
|
+
nodes. Group fallback details include summaries, metadata, status, tone, and
|
|
160
|
+
member labels.
|
|
161
|
+
|
|
162
|
+
Groups are visual and fallback organization only. They do not provide compound
|
|
163
|
+
layout, nested group layout, port constraints, edge routing around group
|
|
164
|
+
boundaries, collapse/expand behavior, or editor workflows.
|
|
165
|
+
|
|
166
|
+
## Edge Labels And Routes
|
|
167
|
+
|
|
168
|
+
Static SVG edges include accessible titles and descriptions derived from
|
|
169
|
+
`accessibility.label`, `ariaLabel`, `label`, source label, target label, status,
|
|
170
|
+
`accessibility.description`, and summary where available. Visible edge labels
|
|
171
|
+
render at deterministic label anchors and include a tokenized background
|
|
172
|
+
rectangle for contrast. Use `deriveGraphEdgeLabelAnchor` when applications need
|
|
173
|
+
the same estimated label bounds and anchor metadata before rendering.
|
|
174
|
+
Use `getGraphEdgeLabelOverlapDiagnostics` on positioned graphs, or set
|
|
175
|
+
`warnEdgeLabelOverlaps` on layout helpers, when applications need opt-in
|
|
176
|
+
warnings for estimated label bounds that overlap. This diagnostic is a
|
|
177
|
+
deterministic risk signal; it does not measure rendered text, relocate labels,
|
|
178
|
+
or solve collisions.
|
|
179
|
+
|
|
180
|
+
The layout helpers expose deterministic straight, curved, elbow, self-loop, and
|
|
181
|
+
parallel-offset route primitives. These are simple SVG path primitives, not
|
|
182
|
+
full orthogonal routing, port routing, obstacle avoidance, edge bundling, or
|
|
183
|
+
Graphviz/ELK-style label placement.
|
|
184
|
+
|
|
185
|
+
Use `edgeRouteKind` for a layout-wide default route kind and `routeByEdgeId`
|
|
186
|
+
when a specific edge needs a different route:
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
const layout = layoutManualGraph(graph, {
|
|
190
|
+
edgeRouteKind: "straight",
|
|
191
|
+
routeByEdgeId: {
|
|
192
|
+
"audit-edge": "curve",
|
|
193
|
+
"fallback-edge": "elbow"
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Per-edge route hints take precedence over the layout default. Self-loop edges
|
|
199
|
+
still render with the `self-loop` route kind so loops remain recognizable.
|
|
200
|
+
|
|
201
|
+
Label anchors are deterministic estimates based on the label text length and
|
|
202
|
+
route label position. They do not use DOM measurement, `getBBox`, label
|
|
203
|
+
collision solving, obstacle avoidance, or route-aware label placement.
|
|
204
|
+
|
|
205
|
+
## Layout Diagnostics And Bounds
|
|
206
|
+
|
|
207
|
+
Manual, tree, and narrow DAG layouts return diagnostics rather than throwing for
|
|
208
|
+
expected graph-shape limits. Layout options can warn about large graphs, dense
|
|
209
|
+
graphs, missing explicit node sizes, duplicate manual positions, self-loops, and
|
|
210
|
+
parallel edges. `estimateGraphRenderCost` returns deterministic counts for
|
|
211
|
+
nodes, edges, groups, fallback rows, visible labels, summary metadata, route
|
|
212
|
+
points, complex routes, and a rough SVG element estimate. Use threshold options
|
|
213
|
+
such as `warnFallbackRowsAt`, `warnVisibleLabelsAt`, `warnRoutePointsAt`, and
|
|
214
|
+
`warnMetadataSummariesAt` to surface opt-in performance warnings when a static
|
|
215
|
+
graph should be filtered, collapsed, summarized, or delegated to a specialized
|
|
216
|
+
graph engine.
|
|
217
|
+
|
|
218
|
+
DAG layout supports `rankStrategy: "longest-path"` and `"source-depth"` for
|
|
219
|
+
small acyclic graphs. Apps can also provide
|
|
220
|
+
`rankByNodeId` and `orderByNodeId` hints when a workflow or dependency view has
|
|
221
|
+
known phases or stable author-defined ordering:
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
import { layoutDagGraph } from "@zvk/graphs";
|
|
225
|
+
|
|
226
|
+
const layout = layoutDagGraph(workflowGraph, {
|
|
227
|
+
direction: "left-right",
|
|
228
|
+
rankByNodeId: {
|
|
229
|
+
change: 0,
|
|
230
|
+
preflight: 1,
|
|
231
|
+
review: 1,
|
|
232
|
+
release: 2
|
|
233
|
+
},
|
|
234
|
+
orderByNodeId: {
|
|
235
|
+
review: 0,
|
|
236
|
+
preflight: 1
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Rank hints override computed DAG ranks for matching node IDs. Order hints sort
|
|
242
|
+
nodes within their resolved rank; missing order hints fall back to graph input
|
|
243
|
+
order. Unknown hint keys are ignored. These hints are deterministic layout
|
|
244
|
+
inputs, not a crossing-minimization engine, compound layout system, or
|
|
245
|
+
Graphviz/ELK replacement.
|
|
246
|
+
|
|
247
|
+
Use `validateGraph(graph, { auditAccessibility: true })` for opt-in
|
|
248
|
+
accessibility audit info entries about missing graph descriptions, node
|
|
249
|
+
descriptions, and edge descriptions. These diagnostics are guidance for complex
|
|
250
|
+
static diagrams; they do not turn ordinary validation failures into throws.
|
|
251
|
+
|
|
252
|
+
Use `getGraphBounds` as a pure static helper for `viewBox` sizing with padding
|
|
253
|
+
and minimum dimensions. It does not own pan/zoom state, fit-to-screen behavior,
|
|
254
|
+
viewport persistence, or responsive layout state.
|
|
255
|
+
|
|
256
|
+
## Pure Graph Transforms
|
|
257
|
+
|
|
258
|
+
Use algorithm transform helpers when an application needs focused graph views
|
|
259
|
+
without moving search, filters, routing, or detail panels into the package:
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
import { getGraphNeighborhood, mapGraphNodes, pickGraphSubgraph } from "@zvk/graphs";
|
|
263
|
+
|
|
264
|
+
const neighborhood = getGraphNeighborhood(graph, {
|
|
265
|
+
rootId: "model",
|
|
266
|
+
direction: "outgoing",
|
|
267
|
+
maxDepth: 2
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
const selectedPath = pickGraphSubgraph(graph, {
|
|
271
|
+
nodeIds: ["model", "diagnostics", "layout"]
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
const highlighted = mapGraphNodes(neighborhood, (node) => ({
|
|
275
|
+
...node,
|
|
276
|
+
status: node.id === "model" ? "success" : node.status
|
|
277
|
+
}));
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
`pickGraphSubgraph` preserves node and edge input order while pruning edges to
|
|
281
|
+
selected nodes by default. `includeConnectedEdges: false` returns node-only
|
|
282
|
+
projections, and `includeDanglingEdges: true` keeps edges touching selected
|
|
283
|
+
nodes for app-owned inspection flows. `getGraphNeighborhood` returns a
|
|
284
|
+
depth-limited incoming, outgoing, or bidirectional projection. `mapGraphNodes`
|
|
285
|
+
and `mapGraphEdges` preserve graph metadata while letting applications annotate
|
|
286
|
+
nodes or edges for a derived view.
|
|
287
|
+
|
|
288
|
+
## Edge And Adjacency Interop
|
|
289
|
+
|
|
290
|
+
Use edge-list and adjacency-list helpers when an app imports relationship data
|
|
291
|
+
from a database query, config file, or debug fixture and needs a graph model
|
|
292
|
+
without adopting a graph language parser:
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
import { graphFromAdjacencyList, graphFromEdgeList, graphToAdjacencyList } from "@zvk/graphs";
|
|
296
|
+
|
|
297
|
+
const importedGraph = graphFromEdgeList(
|
|
298
|
+
[
|
|
299
|
+
{ id: "api-db", source: "api", target: "db", label: "queries" },
|
|
300
|
+
{ source: "api", target: "cache", label: "reads" }
|
|
301
|
+
],
|
|
302
|
+
{
|
|
303
|
+
title: "Service dependencies",
|
|
304
|
+
getNodeLabel: (nodeId) => nodeId.toUpperCase()
|
|
305
|
+
}
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
const adjacencyList = graphToAdjacencyList(importedGraph);
|
|
309
|
+
const debugGraph = graphFromAdjacencyList(adjacencyList, {
|
|
310
|
+
title: "Debug dependency graph"
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
`graphFromEdgeList` preserves explicit edge fields and appends missing nodes in
|
|
315
|
+
first-seen edge order. Missing edge IDs are generated deterministically from the
|
|
316
|
+
source and target IDs, with numeric suffixes for repeated pairs. Provide stable
|
|
317
|
+
edge IDs when IDs are persisted, linked, or shown to users.
|
|
318
|
+
|
|
319
|
+
`graphToAdjacencyList` returns ordered `{ source, targets }` items in graph node
|
|
320
|
+
order, including isolated nodes. This format is intentionally structural and
|
|
321
|
+
lossy: it preserves source IDs, ordered target IDs, and duplicate targets, but
|
|
322
|
+
not edge labels, metadata, data, kind, status, or IDs.
|
|
323
|
+
|
|
324
|
+
## Stable JSON Serialization
|
|
325
|
+
|
|
326
|
+
Use serialization helpers when an app needs deterministic graph JSON for
|
|
327
|
+
storage, debug exports, snapshots, or support bundles:
|
|
328
|
+
|
|
329
|
+
```ts
|
|
330
|
+
import { normalizeGraphForJson, stringifyGraphForJson } from "@zvk/graphs/serialization";
|
|
331
|
+
|
|
332
|
+
const portableGraph = normalizeGraphForJson(graph);
|
|
333
|
+
const stableJson = stringifyGraphForJson(graph, {
|
|
334
|
+
sortItems: true,
|
|
335
|
+
space: 2
|
|
336
|
+
});
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
`normalizeGraphForJson` removes unsupported object properties such as
|
|
340
|
+
`undefined`, functions, symbols, and bigints. Object keys are sorted for stable
|
|
341
|
+
diffs. Graph groups, nodes, and edges preserve input order by default; set
|
|
342
|
+
`sortItems: true` to sort those arrays by `id` for persisted output.
|
|
343
|
+
|
|
344
|
+
`stringifyGraphForJson` serializes the normalized graph. It is not a runtime
|
|
345
|
+
validator and does not parse or emit DOT, Mermaid, Graphviz, or layout-engine
|
|
346
|
+
formats. Use `validateGraph` and `validateGraphLayout` for imported data that
|
|
347
|
+
must be checked before rendering.
|
|
348
|
+
|
|
60
349
|
## Package Boundaries
|
|
61
350
|
|
|
62
351
|
`@zvk/graphs` owns:
|
|
@@ -64,8 +353,9 @@ component internals for styling.
|
|
|
64
353
|
- serializable graph model types;
|
|
65
354
|
- graph diagnostics and validation reports;
|
|
66
355
|
- pure graph algorithms such as adjacency, degree, cycle, topological,
|
|
67
|
-
reachability, component, and
|
|
68
|
-
- deterministic manual, tree,
|
|
356
|
+
reachability, component, tree, subgraph, neighborhood, and mapping helpers;
|
|
357
|
+
- deterministic manual, tree, narrow DAG, bounds, and static group-bound
|
|
358
|
+
utilities;
|
|
69
359
|
- static React/SVG graph rendering;
|
|
70
360
|
- graph CSS and token inheritance contracts.
|
|
71
361
|
|
|
@@ -74,6 +364,75 @@ domain entities, and any editor or viewport state. Use `@zvk/charts` for axes,
|
|
|
74
364
|
scales, numeric series, legends, bars, lines, areas, scatterplots, sparklines,
|
|
75
365
|
pie/donut charts, and other quantitative visualization.
|
|
76
366
|
|
|
367
|
+
App-owned detail panel composition should keep state in the application:
|
|
368
|
+
|
|
369
|
+
```tsx
|
|
370
|
+
import { GraphFigure, layoutManualGraph } from "@zvk/graphs";
|
|
371
|
+
import "@zvk/graphs/styles.css";
|
|
372
|
+
|
|
373
|
+
const layout = layoutManualGraph(graph);
|
|
374
|
+
const selectedNode = layout.graph.nodes.find((node) => node.id === selectedNodeId);
|
|
375
|
+
|
|
376
|
+
return (
|
|
377
|
+
<>
|
|
378
|
+
<GraphFigure graph={layout.graph} selectedNodeIds={selectedNodeId ? [selectedNodeId] : []} />
|
|
379
|
+
<aside>{selectedNode ? selectedNode.label : "No node selected"}</aside>
|
|
380
|
+
</>
|
|
381
|
+
);
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
This pattern is application composition. `@zvk/graphs` does not own product
|
|
385
|
+
detail fields, routes, filtering, search, keyboard selection, or client state.
|
|
386
|
+
|
|
387
|
+
## Client Boundary Decision
|
|
388
|
+
|
|
389
|
+
There is no `@zvk/graphs/client` public subpath yet. Keep client behavior
|
|
390
|
+
app-owned until the static core needs shared helpers that cannot be expressed
|
|
391
|
+
with public model, layout, SVG, and CSS contracts.
|
|
392
|
+
|
|
393
|
+
A future client subpath must stay narrow: focusable nodes and edges, roving
|
|
394
|
+
focus or tab-order helpers, controlled selection helpers, keyboard shortcuts,
|
|
395
|
+
persistent inspector helpers, and live-region guidance. Product detail panels,
|
|
396
|
+
filters, search, routing, editing, drag-to-connect, pan/zoom, minimaps, and
|
|
397
|
+
viewport persistence remain application responsibilities.
|
|
398
|
+
|
|
399
|
+
## CSS Contract
|
|
400
|
+
|
|
401
|
+
Graph styles use `.zvk-graphs*` classes, `data-zvk-graphs`, `data-node-id`,
|
|
402
|
+
`data-edge-id`, `data-kind`, `data-node-shape`, `data-edge-kind`,
|
|
403
|
+
`data-edge-label-anchor`, `data-edge-label-side`, `data-edge-route-kind`,
|
|
404
|
+
`data-density`, `data-tone`, `data-status`, and `data-selected` hooks. Treat
|
|
405
|
+
these as styling and diagnostics hooks, not a replacement for accessible labels
|
|
406
|
+
or public imports.
|
|
407
|
+
|
|
408
|
+
Important variables include:
|
|
409
|
+
|
|
410
|
+
- `--zvk-graphs-canvas-bg`
|
|
411
|
+
- `--zvk-graphs-group-bg`
|
|
412
|
+
- `--zvk-graphs-group-border`
|
|
413
|
+
- `--zvk-graphs-group-label-text`
|
|
414
|
+
- `--zvk-graphs-node-bg`
|
|
415
|
+
- `--zvk-graphs-node-border`
|
|
416
|
+
- `--zvk-graphs-node-text`
|
|
417
|
+
- `--zvk-graphs-node-badge-text`
|
|
418
|
+
- `--zvk-graphs-muted-text`
|
|
419
|
+
- `--zvk-graphs-edge-stroke`
|
|
420
|
+
- `--zvk-graphs-edge-label-bg`
|
|
421
|
+
- `--zvk-graphs-edge-label-border`
|
|
422
|
+
- `--zvk-graphs-selected-bg`
|
|
423
|
+
- `--zvk-graphs-selected-stroke`
|
|
424
|
+
- `--zvk-graphs-success`
|
|
425
|
+
- `--zvk-graphs-warning`
|
|
426
|
+
- `--zvk-graphs-destructive`
|
|
427
|
+
- `--zvk-graphs-info`
|
|
428
|
+
- `--zvk-graphs-font-primary`
|
|
429
|
+
- `--zvk-graphs-font-secondary`
|
|
430
|
+
- `--zvk-graphs-font-tertiary`
|
|
431
|
+
|
|
432
|
+
The default CSS adds non-color cues for several statuses with dash patterns,
|
|
433
|
+
stroke width changes, and fallback/status text. Host applications should still
|
|
434
|
+
verify contrast and forced-colors behavior in their own theme context.
|
|
435
|
+
|
|
77
436
|
## Anti-Goals
|
|
78
437
|
|
|
79
438
|
- No runtime dependencies.
|
|
@@ -84,3 +443,20 @@ pie/donut charts, and other quantitative visualization.
|
|
|
84
443
|
node rendering in the core package.
|
|
85
444
|
- No unlabeled graph nodes, inaccessible edge relationships, color-only states,
|
|
86
445
|
or browser-only behavior leaking into SSR-safe imports.
|
|
446
|
+
|
|
447
|
+
## Source Policy Validation
|
|
448
|
+
|
|
449
|
+
`bun run --filter @zvk/graphs validate:source-policy` checks the static package
|
|
450
|
+
boundary. It fails on browser globals or forbidden graph/runtime dependency
|
|
451
|
+
imports in production graph source, nondeterministic time/random APIs, graph CSS
|
|
452
|
+
that escapes the token contract, and private `src` or `dist` graph imports in
|
|
453
|
+
the README, package docs, or examples. The package `preflight` runs this check
|
|
454
|
+
after export validation.
|
|
455
|
+
|
|
456
|
+
`bun run --filter @zvk/graphs verify:style-contract` checks the positive style
|
|
457
|
+
surface: graph layers, public `.zvk-graphs*` selectors, graph token definitions,
|
|
458
|
+
UI-token inheritance, state selectors, and forced-colors coverage.
|
|
459
|
+
|
|
460
|
+
## Repo Skill
|
|
461
|
+
|
|
462
|
+
Use `.codex/skills/use-zvk-graphs/SKILL.md` when maintaining this package.
|
package/dist/algorithms.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { GraphDiagnostic } from "./diagnostics.js";
|
|
2
|
-
import type { GraphEdge, GraphId, GraphModel, GraphNode } from "./model.js";
|
|
2
|
+
import type { GraphDirection, GraphEdge, GraphGroup, GraphId, GraphModel, GraphNode } from "./model.js";
|
|
3
3
|
export interface GraphIndex {
|
|
4
4
|
readonly nodeById: ReadonlyMap<GraphId, GraphNode>;
|
|
5
5
|
readonly edgeById: ReadonlyMap<GraphId, GraphEdge>;
|
|
@@ -11,8 +11,71 @@ export interface GraphDegree {
|
|
|
11
11
|
readonly out: number;
|
|
12
12
|
readonly total: number;
|
|
13
13
|
}
|
|
14
|
+
export type GraphAdjacencyList = readonly GraphAdjacencyListItem[];
|
|
15
|
+
export interface GraphAdjacencyListItem {
|
|
16
|
+
readonly source: GraphId;
|
|
17
|
+
readonly targets: readonly GraphId[];
|
|
18
|
+
}
|
|
19
|
+
export interface GraphEdgeListItem<TEdgeData = unknown, TEdgeKind extends string = string> extends Omit<GraphEdge<TEdgeData, TEdgeKind>, "id"> {
|
|
20
|
+
readonly id?: GraphId;
|
|
21
|
+
}
|
|
22
|
+
export interface GraphNodeLabelContext {
|
|
23
|
+
readonly nodeId: GraphId;
|
|
24
|
+
readonly index: number;
|
|
25
|
+
}
|
|
26
|
+
export interface GraphEdgeIdContext<TEdgeData = unknown, TEdgeKind extends string = string> {
|
|
27
|
+
readonly entry: GraphEdgeListItem<TEdgeData, TEdgeKind>;
|
|
28
|
+
readonly index: number;
|
|
29
|
+
readonly proposedId: GraphId;
|
|
30
|
+
}
|
|
31
|
+
export interface GraphFromEdgeListOptions<TNodeData = unknown, TEdgeData = unknown, TNodeKind extends string = string, TEdgeKind extends string = string> {
|
|
32
|
+
readonly id?: GraphId;
|
|
33
|
+
readonly title: string;
|
|
34
|
+
readonly description?: string;
|
|
35
|
+
readonly direction?: GraphDirection;
|
|
36
|
+
readonly groups?: readonly GraphGroup[];
|
|
37
|
+
readonly nodes?: readonly GraphNode<TNodeData, TNodeKind>[];
|
|
38
|
+
readonly getNodeLabel?: (nodeId: GraphId, context: GraphNodeLabelContext) => string;
|
|
39
|
+
readonly getEdgeId?: (entry: GraphEdgeListItem<TEdgeData, TEdgeKind>, context: GraphEdgeIdContext<TEdgeData, TEdgeKind>) => GraphId;
|
|
40
|
+
}
|
|
41
|
+
export interface GraphFromAdjacencyListOptions<TNodeData = unknown, TNodeKind extends string = string> {
|
|
42
|
+
readonly id?: GraphId;
|
|
43
|
+
readonly title: string;
|
|
44
|
+
readonly description?: string;
|
|
45
|
+
readonly direction?: GraphDirection;
|
|
46
|
+
readonly groups?: readonly GraphGroup[];
|
|
47
|
+
readonly nodes?: readonly GraphNode<TNodeData, TNodeKind>[];
|
|
48
|
+
readonly getNodeLabel?: (nodeId: GraphId, context: GraphNodeLabelContext) => string;
|
|
49
|
+
}
|
|
50
|
+
export interface GraphSubgraphOptions {
|
|
51
|
+
readonly nodeIds: readonly GraphId[];
|
|
52
|
+
readonly includeConnectedEdges?: boolean;
|
|
53
|
+
readonly includeDanglingEdges?: boolean;
|
|
54
|
+
}
|
|
55
|
+
export type GraphNeighborhoodDirection = "outgoing" | "incoming" | "both";
|
|
56
|
+
export interface GraphNeighborhoodOptions {
|
|
57
|
+
readonly rootId: GraphId;
|
|
58
|
+
readonly direction?: GraphNeighborhoodDirection;
|
|
59
|
+
readonly maxDepth: number;
|
|
60
|
+
}
|
|
61
|
+
export interface GraphNodeMapperContext<TNodeData = unknown, TEdgeData = unknown, TNodeKind extends string = string, TEdgeKind extends string = string> {
|
|
62
|
+
readonly graph: GraphModel<TNodeData, TEdgeData, TNodeKind, TEdgeKind>;
|
|
63
|
+
readonly index: number;
|
|
64
|
+
}
|
|
65
|
+
export interface GraphEdgeMapperContext<TNodeData = unknown, TEdgeData = unknown, TNodeKind extends string = string, TEdgeKind extends string = string> {
|
|
66
|
+
readonly graph: GraphModel<TNodeData, TEdgeData, TNodeKind, TEdgeKind>;
|
|
67
|
+
readonly index: number;
|
|
68
|
+
}
|
|
14
69
|
export declare function createGraphIndex(graph: GraphModel): GraphIndex;
|
|
15
70
|
export declare function getGraphDegrees(graph: GraphModel): Record<string, GraphDegree>;
|
|
71
|
+
export declare function graphFromEdgeList<TNodeData = unknown, TEdgeData = unknown, TNodeKind extends string = string, TEdgeKind extends string = string>(edgeList: readonly GraphEdgeListItem<TEdgeData, TEdgeKind>[], options: GraphFromEdgeListOptions<TNodeData, TEdgeData, TNodeKind, TEdgeKind>): GraphModel<TNodeData, TEdgeData, TNodeKind, TEdgeKind>;
|
|
72
|
+
export declare function graphToEdgeList<TNodeData = unknown, TEdgeData = unknown, TNodeKind extends string = string, TEdgeKind extends string = string>(graph: GraphModel<TNodeData, TEdgeData, TNodeKind, TEdgeKind>): readonly GraphEdge<TEdgeData, TEdgeKind>[];
|
|
73
|
+
export declare function graphToAdjacencyList(graph: GraphModel): GraphAdjacencyList;
|
|
74
|
+
export declare function graphFromAdjacencyList<TNodeData = unknown, TNodeKind extends string = string>(adjacencyList: GraphAdjacencyList, options: GraphFromAdjacencyListOptions<TNodeData, TNodeKind>): GraphModel<TNodeData, unknown, TNodeKind, string>;
|
|
75
|
+
export declare function pickGraphSubgraph<TNodeData = unknown, TEdgeData = unknown, TNodeKind extends string = string, TEdgeKind extends string = string>(graph: GraphModel<TNodeData, TEdgeData, TNodeKind, TEdgeKind>, options: GraphSubgraphOptions): GraphModel<TNodeData, TEdgeData, TNodeKind, TEdgeKind>;
|
|
76
|
+
export declare function getGraphNeighborhood<TNodeData = unknown, TEdgeData = unknown, TNodeKind extends string = string, TEdgeKind extends string = string>(graph: GraphModel<TNodeData, TEdgeData, TNodeKind, TEdgeKind>, options: GraphNeighborhoodOptions): GraphModel<TNodeData, TEdgeData, TNodeKind, TEdgeKind>;
|
|
77
|
+
export declare function mapGraphNodes<TNodeData = unknown, TEdgeData = unknown, TNodeKind extends string = string, TEdgeKind extends string = string, TNextNodeData = TNodeData, TNextNodeKind extends string = TNodeKind>(graph: GraphModel<TNodeData, TEdgeData, TNodeKind, TEdgeKind>, mapper: (node: GraphNode<TNodeData, TNodeKind>, context: GraphNodeMapperContext<TNodeData, TEdgeData, TNodeKind, TEdgeKind>) => GraphNode<TNextNodeData, TNextNodeKind>): GraphModel<TNextNodeData, TEdgeData, TNextNodeKind, TEdgeKind>;
|
|
78
|
+
export declare function mapGraphEdges<TNodeData = unknown, TEdgeData = unknown, TNodeKind extends string = string, TEdgeKind extends string = string, TNextEdgeData = TEdgeData, TNextEdgeKind extends string = TEdgeKind>(graph: GraphModel<TNodeData, TEdgeData, TNodeKind, TEdgeKind>, mapper: (edge: GraphEdge<TEdgeData, TEdgeKind>, context: GraphEdgeMapperContext<TNodeData, TEdgeData, TNodeKind, TEdgeKind>) => GraphEdge<TNextEdgeData, TNextEdgeKind>): GraphModel<TNodeData, TNextEdgeData, TNodeKind, TNextEdgeKind>;
|
|
16
79
|
export declare function detectGraphCycle(graph: GraphModel): readonly GraphId[] | null;
|
|
17
80
|
export interface TopologicalSortResult {
|
|
18
81
|
readonly nodeIds: readonly GraphId[];
|
|
@@ -20,6 +83,15 @@ export interface TopologicalSortResult {
|
|
|
20
83
|
}
|
|
21
84
|
export declare function topologicalSortGraph(graph: GraphModel): TopologicalSortResult;
|
|
22
85
|
export declare function getReachableNodeIds(graph: GraphModel, startId: GraphId): readonly GraphId[];
|
|
86
|
+
export declare function getUnreachableNodeIds(graph: GraphModel, startId: GraphId): readonly GraphId[];
|
|
87
|
+
export declare function getIsolatedNodeIds(graph: GraphModel): readonly GraphId[];
|
|
88
|
+
export declare function getShortestPath(graph: GraphModel, sourceId: GraphId, targetId: GraphId): readonly GraphId[] | null;
|
|
89
|
+
export declare function getStronglyConnectedComponents(graph: GraphModel): readonly (readonly GraphId[])[];
|
|
90
|
+
export interface TransitiveReductionResult {
|
|
91
|
+
readonly edges: readonly GraphEdge[];
|
|
92
|
+
readonly diagnostics: readonly GraphDiagnostic[];
|
|
93
|
+
}
|
|
94
|
+
export declare function getTransitiveReductionEdges(graph: GraphModel): TransitiveReductionResult;
|
|
23
95
|
export declare function getWeaklyConnectedComponents(graph: GraphModel): readonly (readonly GraphId[])[];
|
|
24
96
|
export interface ValidateTreeGraphOptions {
|
|
25
97
|
readonly rootId?: GraphId;
|