@zvk/graphs 0.1.1 → 0.1.2

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/dist/model.d.ts CHANGED
@@ -8,11 +8,54 @@ export interface GraphSize {
8
8
  readonly width: number;
9
9
  readonly height: number;
10
10
  }
11
+ export type GraphMetadataValue = string | number | boolean | null;
12
+ export type GraphMetadataVisibility = "summary" | "detail";
13
+ export type GraphTone = "neutral" | "info" | "success" | "warning" | "danger" | "destructive";
14
+ export interface GraphAccessibilityText {
15
+ readonly label?: string;
16
+ readonly description?: string;
17
+ }
18
+ export interface GraphMetadataItem {
19
+ readonly key: string;
20
+ readonly label: string;
21
+ readonly value: GraphMetadataValue;
22
+ readonly visibility?: GraphMetadataVisibility;
23
+ readonly tone?: GraphTone;
24
+ }
25
+ export type GraphNodeShape = "rect" | "rounded" | "pill" | "circle" | "diamond";
26
+ export type GraphNodeDensity = "comfortable" | "compact" | "dense";
27
+ export interface GraphNodePresentation {
28
+ readonly shape?: GraphNodeShape;
29
+ readonly tone?: GraphTone;
30
+ readonly density?: GraphNodeDensity;
31
+ readonly glyph?: string;
32
+ readonly badge?: string;
33
+ }
34
+ export interface GraphStatusPresentation {
35
+ readonly statusLabel?: string;
36
+ readonly statusGlyph?: string;
37
+ }
38
+ export interface GraphGroup {
39
+ readonly id: GraphId;
40
+ readonly label: string;
41
+ readonly summary?: string;
42
+ readonly accessibility?: GraphAccessibilityText;
43
+ readonly metadata?: readonly GraphMetadataItem[];
44
+ readonly tone?: GraphTone;
45
+ readonly status?: string;
46
+ readonly position?: GraphPoint;
47
+ readonly size?: GraphSize;
48
+ }
11
49
  export interface GraphNode<TData = unknown, TKind extends string = string> {
12
50
  readonly id: GraphId;
13
51
  readonly label: string;
14
52
  readonly kind?: TKind;
15
53
  readonly data?: TData;
54
+ readonly metadata?: readonly GraphMetadataItem[];
55
+ readonly summary?: string;
56
+ readonly accessibility?: GraphAccessibilityText;
57
+ readonly presentation?: GraphNodePresentation & GraphStatusPresentation;
58
+ readonly labelLines?: readonly string[];
16
59
  readonly position?: GraphPoint;
17
60
  readonly size?: GraphSize;
18
61
  readonly status?: string;
@@ -25,6 +68,9 @@ export interface GraphEdge<TData = unknown, TKind extends string = string> {
25
68
  readonly target: GraphId;
26
69
  readonly label?: string;
27
70
  readonly ariaLabel?: string;
71
+ readonly accessibility?: GraphAccessibilityText;
72
+ readonly summary?: string;
73
+ readonly metadata?: readonly GraphMetadataItem[];
28
74
  readonly kind?: TKind;
29
75
  readonly data?: TData;
30
76
  readonly directed?: boolean;
@@ -35,6 +81,7 @@ export interface GraphModel<TNodeData = unknown, TEdgeData = unknown, TNodeKind
35
81
  readonly title: string;
36
82
  readonly description?: string;
37
83
  readonly direction?: GraphDirection;
84
+ readonly groups?: readonly GraphGroup[];
38
85
  readonly nodes: readonly GraphNode<TNodeData, TNodeKind>[];
39
86
  readonly edges: readonly GraphEdge<TEdgeData, TEdgeKind>[];
40
87
  }
@@ -0,0 +1,14 @@
1
+ import type { GraphModel } from "./model.js";
2
+ export type GraphJsonPrimitive = string | number | boolean | null;
3
+ export type GraphJsonValue = GraphJsonPrimitive | readonly GraphJsonValue[] | GraphJsonObject;
4
+ export interface GraphJsonObject {
5
+ readonly [key: string]: GraphJsonValue;
6
+ }
7
+ export interface GraphJsonNormalizationOptions {
8
+ readonly sortItems?: boolean;
9
+ }
10
+ export interface GraphJsonStringifyOptions extends GraphJsonNormalizationOptions {
11
+ readonly space?: number | string;
12
+ }
13
+ export declare function normalizeGraphForJson<TNodeData = unknown, TEdgeData = unknown, TNodeKind extends string = string, TEdgeKind extends string = string>(graph: GraphModel<TNodeData, TEdgeData, TNodeKind, TEdgeKind>, options?: GraphJsonNormalizationOptions): GraphJsonObject;
14
+ export declare function stringifyGraphForJson<TNodeData = unknown, TEdgeData = unknown, TNodeKind extends string = string, TEdgeKind extends string = string>(graph: GraphModel<TNodeData, TEdgeData, TNodeKind, TEdgeKind>, options?: GraphJsonStringifyOptions): string;
@@ -0,0 +1,68 @@
1
+ function normalizeJsonValue(value, seen = new WeakSet()) {
2
+ if (value === undefined || typeof value === "function" || typeof value === "symbol" || typeof value === "bigint") {
3
+ return undefined;
4
+ }
5
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
6
+ return value;
7
+ }
8
+ if (typeof value === "number") {
9
+ return Number.isFinite(value) ? value : null;
10
+ }
11
+ if (Array.isArray(value)) {
12
+ if (seen.has(value)) {
13
+ throw new TypeError("Cannot normalize cyclic graph JSON data.");
14
+ }
15
+ seen.add(value);
16
+ try {
17
+ return value.flatMap((item) => {
18
+ const normalized = normalizeJsonValue(item, seen);
19
+ return normalized === undefined ? [] : [normalized];
20
+ });
21
+ }
22
+ finally {
23
+ seen.delete(value);
24
+ }
25
+ }
26
+ if (typeof value === "object") {
27
+ if (seen.has(value)) {
28
+ throw new TypeError("Cannot normalize cyclic graph JSON data.");
29
+ }
30
+ seen.add(value);
31
+ try {
32
+ const output = {};
33
+ for (const key of Object.keys(value).sort((left, right) => left.localeCompare(right))) {
34
+ const normalized = normalizeJsonValue(value[key], seen);
35
+ if (normalized !== undefined) {
36
+ output[key] = normalized;
37
+ }
38
+ }
39
+ return output;
40
+ }
41
+ finally {
42
+ seen.delete(value);
43
+ }
44
+ }
45
+ return undefined;
46
+ }
47
+ function sortById(items, sortItems) {
48
+ if (!items || !sortItems) {
49
+ return items;
50
+ }
51
+ return [...items].sort((left, right) => left.id.localeCompare(right.id));
52
+ }
53
+ function graphForJson(graph, options) {
54
+ const sortItems = options.sortItems === true;
55
+ const groups = sortById(graph.groups, sortItems);
56
+ return {
57
+ ...graph,
58
+ ...(groups !== undefined ? { groups: groups } : {}),
59
+ nodes: sortById(graph.nodes, sortItems),
60
+ edges: sortById(graph.edges, sortItems)
61
+ };
62
+ }
63
+ export function normalizeGraphForJson(graph, options = {}) {
64
+ return normalizeJsonValue(graphForJson(graph, options));
65
+ }
66
+ export function stringifyGraphForJson(graph, options = {}) {
67
+ return JSON.stringify(normalizeGraphForJson(graph, options), null, options.space);
68
+ }
package/dist/styles.css CHANGED
@@ -3,14 +3,20 @@
3
3
 
4
4
  /* src/styles/graph.css */
5
5
  @layer zvk-graphs-tokens {
6
- :root {
6
+ :where(.zvk-graphs, .zvk-graphs__svg) {
7
7
  --zvk-graphs-canvas-bg: var(--zvk-ui-color-background, Canvas);
8
8
  --zvk-graphs-node-bg: var(--zvk-ui-color-surface, Canvas);
9
9
  --zvk-graphs-node-border: var(--zvk-ui-card-border, GrayText);
10
10
  --zvk-graphs-node-text: var(--zvk-ui-color-foreground, CanvasText);
11
+ --zvk-graphs-node-badge-text: var(--zvk-ui-color-muted-foreground, CanvasText);
12
+ --zvk-graphs-group-bg: var(--zvk-ui-color-muted, color-mix(in srgb, CanvasText 5%, Canvas));
13
+ --zvk-graphs-group-border: var(--zvk-ui-color-border, GrayText);
14
+ --zvk-graphs-group-label-text: var(--zvk-ui-color-muted-foreground, GrayText);
11
15
  --zvk-graphs-muted-text: var(--zvk-ui-color-muted-foreground, GrayText);
12
16
  --zvk-graphs-edge-stroke: var(--zvk-ui-color-border-strong, CanvasText);
13
17
  --zvk-graphs-edge-muted-stroke: var(--zvk-ui-color-border, GrayText);
18
+ --zvk-graphs-edge-label-bg: var(--zvk-ui-color-background, Canvas);
19
+ --zvk-graphs-edge-label-border: var(--zvk-ui-color-border, GrayText);
14
20
  --zvk-graphs-selected-bg: var(--zvk-ui-color-primary-soft, color-mix(in srgb, Highlight 12%, Canvas));
15
21
  --zvk-graphs-selected-stroke: var(--zvk-ui-color-primary, Highlight);
16
22
  --zvk-graphs-focus-outline: var(--zvk-ui-focus-ring, 0 0 0 3px Highlight);
@@ -23,6 +29,9 @@
23
29
  --zvk-graphs-node-shadow: var(--zvk-ui-shadow-xs, none);
24
30
  --zvk-graphs-edge-width: 1.5px;
25
31
  --zvk-graphs-selected-edge-width: 2.5px;
32
+ --zvk-graphs-font-primary: var(--zvk-ui-font-family-primary, var(--zvk-ui-font-family-sans, system-ui, sans-serif));
33
+ --zvk-graphs-font-secondary: var(--zvk-ui-font-family-secondary, var(--zvk-graphs-font-primary));
34
+ --zvk-graphs-font-tertiary: var(--zvk-ui-font-family-tertiary, var(--zvk-ui-font-family-mono, ui-monospace, monospace));
26
35
  }
27
36
  }
28
37
 
@@ -30,6 +39,7 @@
30
39
  :where(.zvk-graphs) {
31
40
  color: var(--zvk-graphs-node-text);
32
41
  background: var(--zvk-graphs-canvas-bg);
42
+ margin: 0;
33
43
  }
34
44
 
35
45
  :where(.zvk-graphs__svg) {
@@ -39,6 +49,41 @@
39
49
  overflow: visible;
40
50
  }
41
51
 
52
+ :where(.zvk-graphs__group-shape) {
53
+ fill: var(--zvk-graphs-group-bg);
54
+ stroke: var(--zvk-graphs-group-border);
55
+ stroke-dasharray: 8 4;
56
+ stroke-width: 1px;
57
+ }
58
+
59
+ :where(.zvk-graphs__group-label) {
60
+ fill: var(--zvk-graphs-group-label-text);
61
+ font: 700 12px var(--zvk-graphs-font-secondary);
62
+ pointer-events: none;
63
+ }
64
+
65
+ :where(.zvk-graphs__group[data-status="success"] .zvk-graphs__group-shape),
66
+ :where(.zvk-graphs__group[data-tone="success"] .zvk-graphs__group-shape) {
67
+ stroke: var(--zvk-graphs-success);
68
+ }
69
+
70
+ :where(.zvk-graphs__group[data-status="warning"] .zvk-graphs__group-shape),
71
+ :where(.zvk-graphs__group[data-tone="warning"] .zvk-graphs__group-shape) {
72
+ stroke: var(--zvk-graphs-warning);
73
+ }
74
+
75
+ :where(.zvk-graphs__group[data-status="danger"] .zvk-graphs__group-shape),
76
+ :where(.zvk-graphs__group[data-status="destructive"] .zvk-graphs__group-shape),
77
+ :where(.zvk-graphs__group[data-tone="danger"] .zvk-graphs__group-shape),
78
+ :where(.zvk-graphs__group[data-tone="destructive"] .zvk-graphs__group-shape) {
79
+ stroke: var(--zvk-graphs-destructive);
80
+ }
81
+
82
+ :where(.zvk-graphs__group[data-status="info"] .zvk-graphs__group-shape),
83
+ :where(.zvk-graphs__group[data-tone="info"] .zvk-graphs__group-shape) {
84
+ stroke: var(--zvk-graphs-info);
85
+ }
86
+
42
87
  :where(.zvk-graphs__edge) {
43
88
  fill: none;
44
89
  stroke: var(--zvk-graphs-edge-stroke);
@@ -50,19 +95,38 @@
50
95
  stroke-width: var(--zvk-graphs-selected-edge-width);
51
96
  }
52
97
 
98
+ :where(.zvk-graphs__edge-group[data-status="success"] .zvk-graphs__edge) {
99
+ stroke: var(--zvk-graphs-success);
100
+ stroke-width: calc(var(--zvk-graphs-edge-width) + 0.5px);
101
+ }
102
+
103
+ :where(.zvk-graphs__edge-group[data-status="warning"] .zvk-graphs__edge) {
104
+ stroke: var(--zvk-graphs-warning);
105
+ stroke-dasharray: 5 4;
106
+ }
107
+
108
+ :where(.zvk-graphs__edge-group[data-status="destructive"] .zvk-graphs__edge) {
109
+ stroke: var(--zvk-graphs-destructive);
110
+ stroke-dasharray: 2 3;
111
+ stroke-width: calc(var(--zvk-graphs-edge-width) + 0.75px);
112
+ }
113
+
53
114
  :where(.zvk-graphs__marker) {
54
115
  fill: var(--zvk-graphs-edge-stroke);
55
116
  }
56
117
 
57
118
  :where(.zvk-graphs__edge-label) {
58
119
  fill: var(--zvk-graphs-muted-text);
59
- font: 12px system-ui, sans-serif;
60
- paint-order: stroke;
61
- stroke: var(--zvk-graphs-canvas-bg);
62
- stroke-width: 3px;
120
+ font: 12px var(--zvk-graphs-font-primary);
63
121
  text-anchor: middle;
64
122
  }
65
123
 
124
+ :where(.zvk-graphs__edge-label-bg) {
125
+ fill: var(--zvk-graphs-edge-label-bg);
126
+ stroke: var(--zvk-graphs-edge-label-border);
127
+ stroke-width: 1px;
128
+ }
129
+
66
130
  :where(.zvk-graphs__node-shape) {
67
131
  fill: var(--zvk-graphs-node-bg);
68
132
  stroke: var(--zvk-graphs-node-border);
@@ -72,12 +136,57 @@
72
136
 
73
137
  :where(.zvk-graphs__node-label) {
74
138
  fill: var(--zvk-graphs-node-text);
75
- font: 13px system-ui, sans-serif;
139
+ font: 13px var(--zvk-graphs-font-primary);
76
140
  dominant-baseline: middle;
77
141
  pointer-events: none;
78
142
  text-anchor: middle;
79
143
  }
80
144
 
145
+ :where(.zvk-graphs__node-glyph) {
146
+ fill: var(--zvk-graphs-muted-text);
147
+ font: 700 10px var(--zvk-graphs-font-secondary);
148
+ pointer-events: none;
149
+ text-anchor: start;
150
+ }
151
+
152
+ :where(.zvk-graphs__node-badge) {
153
+ fill: var(--zvk-graphs-node-badge-text);
154
+ font: 700 10px var(--zvk-graphs-font-secondary);
155
+ pointer-events: none;
156
+ text-anchor: end;
157
+ }
158
+
159
+ :where(.zvk-graphs__node-metadata-item) {
160
+ fill: var(--zvk-graphs-muted-text);
161
+ font: 10px var(--zvk-graphs-font-tertiary);
162
+ pointer-events: none;
163
+ text-anchor: middle;
164
+ }
165
+
166
+ :where(.zvk-graphs__node-metadata-item[data-tone="success"]) {
167
+ fill: var(--zvk-graphs-success);
168
+ }
169
+
170
+ :where(.zvk-graphs__node-metadata-item[data-tone="warning"]) {
171
+ fill: var(--zvk-graphs-warning);
172
+ }
173
+
174
+ :where(.zvk-graphs__node-metadata-item[data-tone="danger"]),
175
+ :where(.zvk-graphs__node-metadata-item[data-tone="destructive"]) {
176
+ fill: var(--zvk-graphs-destructive);
177
+ }
178
+
179
+ :where(.zvk-graphs__node-metadata-item[data-tone="info"]) {
180
+ fill: var(--zvk-graphs-info);
181
+ }
182
+
183
+ :where(.zvk-graphs__node-status) {
184
+ fill: var(--zvk-graphs-muted-text);
185
+ font: 700 10px var(--zvk-graphs-font-secondary);
186
+ pointer-events: none;
187
+ text-anchor: end;
188
+ }
189
+
81
190
  :where(.zvk-graphs__node-selection) {
82
191
  fill: var(--zvk-graphs-selected-bg);
83
192
  stroke: var(--zvk-graphs-selected-stroke);
@@ -86,6 +195,7 @@
86
195
 
87
196
  :where(.zvk-graphs__node[data-status="success"] .zvk-graphs__node-shape) {
88
197
  stroke: var(--zvk-graphs-success);
198
+ stroke-width: 2px;
89
199
  }
90
200
 
91
201
  :where(.zvk-graphs__node[data-status="warning"] .zvk-graphs__node-shape) {
@@ -95,6 +205,46 @@
95
205
 
96
206
  :where(.zvk-graphs__node[data-status="destructive"] .zvk-graphs__node-shape) {
97
207
  stroke: var(--zvk-graphs-destructive);
208
+ stroke-dasharray: 2 3;
209
+ stroke-width: 2px;
210
+ }
211
+
212
+ :where(.zvk-graphs__node[data-status="info"] .zvk-graphs__node-shape) {
213
+ stroke: var(--zvk-graphs-info);
214
+ stroke-dasharray: 8 3 2 3;
215
+ }
216
+
217
+ :where(.zvk-graphs__node[data-status="danger"] .zvk-graphs__node-shape),
218
+ :where(.zvk-graphs__node[data-tone="danger"] .zvk-graphs__node-shape),
219
+ :where(.zvk-graphs__node[data-tone="destructive"] .zvk-graphs__node-shape) {
220
+ stroke: var(--zvk-graphs-destructive);
221
+ stroke-dasharray: 2 3;
222
+ stroke-width: 2px;
223
+ }
224
+
225
+ :where(.zvk-graphs__node[data-tone="success"] .zvk-graphs__node-shape) {
226
+ stroke: var(--zvk-graphs-success);
227
+ stroke-width: 2px;
228
+ }
229
+
230
+ :where(.zvk-graphs__node[data-tone="warning"] .zvk-graphs__node-shape) {
231
+ stroke: var(--zvk-graphs-warning);
232
+ stroke-dasharray: 4 3;
233
+ }
234
+
235
+ :where(.zvk-graphs__node[data-tone="info"] .zvk-graphs__node-shape) {
236
+ stroke: var(--zvk-graphs-info);
237
+ stroke-dasharray: 8 3 2 3;
238
+ }
239
+
240
+ :where(.zvk-graphs__node[data-density="compact"] .zvk-graphs__node-label),
241
+ :where(.zvk-graphs__node[data-density="compact"] .zvk-graphs__node-metadata-item) {
242
+ font-size: 11px;
243
+ }
244
+
245
+ :where(.zvk-graphs__node[data-density="dense"] .zvk-graphs__node-label),
246
+ :where(.zvk-graphs__node[data-density="dense"] .zvk-graphs__node-metadata-item) {
247
+ font-size: 10px;
98
248
  }
99
249
 
100
250
  :where(.zvk-graphs__fallback) {
@@ -103,13 +253,65 @@
103
253
 
104
254
  :where(.zvk-graphs__fallback h3) {
105
255
  margin: 0.75rem 0 0.25rem;
106
- font: 600 0.875rem system-ui, sans-serif;
256
+ font: 600 0.875rem var(--zvk-graphs-font-secondary);
107
257
  }
108
258
 
109
259
  :where(.zvk-graphs__fallback ul) {
110
260
  margin: 0;
111
261
  padding-inline-start: 1.25rem;
112
262
  }
263
+
264
+ :where(.zvk-graphs__fallback-details) {
265
+ display: grid;
266
+ grid-template-columns: max-content minmax(0, 1fr);
267
+ column-gap: 0.5rem;
268
+ margin: 0.25rem 0 0;
269
+ color: var(--zvk-graphs-muted-text);
270
+ font: 0.8125rem var(--zvk-graphs-font-primary);
271
+ }
272
+
273
+ :where(.zvk-graphs__fallback-details dt) {
274
+ font-weight: 600;
275
+ }
276
+
277
+ :where(.zvk-graphs__fallback-details dd) {
278
+ margin: 0;
279
+ }
280
+
281
+ @media (forced-colors: active) {
282
+ :where(.zvk-graphs__edge),
283
+ :where(.zvk-graphs__group-shape),
284
+ :where(.zvk-graphs__node-shape),
285
+ :where(.zvk-graphs__edge-label-bg),
286
+ :where(.zvk-graphs__node-selection) {
287
+ forced-color-adjust: auto;
288
+ stroke: CanvasText;
289
+ }
290
+
291
+ :where(.zvk-graphs__node-shape),
292
+ :where(.zvk-graphs__group-shape),
293
+ :where(.zvk-graphs__edge-label-bg) {
294
+ fill: Canvas;
295
+ }
296
+
297
+ :where(.zvk-graphs__node-label),
298
+ :where(.zvk-graphs__group-label),
299
+ :where(.zvk-graphs__node-badge),
300
+ :where(.zvk-graphs__node-glyph),
301
+ :where(.zvk-graphs__node-metadata-item),
302
+ :where(.zvk-graphs__edge-label) {
303
+ fill: CanvasText;
304
+ }
305
+
306
+ :where(.zvk-graphs__marker) {
307
+ fill: CanvasText;
308
+ }
309
+
310
+ :where(.zvk-graphs__node[data-selected="true"] .zvk-graphs__node-selection),
311
+ :where(.zvk-graphs__edge-group[data-selected="true"] .zvk-graphs__edge) {
312
+ stroke: Highlight;
313
+ }
314
+ }
113
315
  }
114
316
 
115
317