@tuicomponents/graph 0.1.1

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/index.js ADDED
@@ -0,0 +1,478 @@
1
+ // src/graph.ts
2
+ import {
3
+ BaseTuiComponent,
4
+ measureLines,
5
+ registry
6
+ } from "@tuicomponents/core";
7
+ import { zodToJsonSchema } from "zod-to-json-schema";
8
+
9
+ // src/schema.ts
10
+ import { z } from "zod";
11
+ var graphStyleSchema = z.enum(["ascii", "unicode"]);
12
+ var graphNodeSchema = z.object({
13
+ /**
14
+ * Unique identifier for this node.
15
+ */
16
+ id: z.string(),
17
+ /**
18
+ * Display label for the node (e.g., commit message).
19
+ */
20
+ label: z.string(),
21
+ /**
22
+ * IDs of parent nodes (empty for root nodes).
23
+ */
24
+ parents: z.array(z.string()).default([]),
25
+ /**
26
+ * Optional ref names (branch/tag names) to display.
27
+ */
28
+ refs: z.array(z.string()).optional(),
29
+ /**
30
+ * Whether this node should be highlighted.
31
+ */
32
+ highlight: z.boolean().optional(),
33
+ /**
34
+ * Custom character to represent this node (overrides global nodeChar).
35
+ */
36
+ nodeChar: z.string().length(1).optional()
37
+ });
38
+ var graphInputSchema = z.object({
39
+ /**
40
+ * Array of nodes in topological order (newest/most recent first).
41
+ */
42
+ nodes: z.array(graphNodeSchema),
43
+ /**
44
+ * Style for graph characters.
45
+ * @default "unicode"
46
+ */
47
+ style: graphStyleSchema.default("unicode"),
48
+ /**
49
+ * Maximum width for labels (truncated if longer).
50
+ * @default 50
51
+ */
52
+ labelWidth: z.number().int().positive().default(50),
53
+ /**
54
+ * Whether to show refs (branch/tag names).
55
+ * @default true
56
+ */
57
+ showRefs: z.boolean().default(true),
58
+ /**
59
+ * Gap between graph and label.
60
+ * @default 1
61
+ */
62
+ labelGap: z.number().int().nonnegative().default(1),
63
+ /**
64
+ * Custom character to represent nodes (can be overridden per-node).
65
+ * If not specified, uses the default for the style (● for unicode, * for ascii).
66
+ */
67
+ nodeChar: z.string().length(1).optional()
68
+ });
69
+
70
+ // src/chars.ts
71
+ var unicodeChars = {
72
+ node: "\u25CF",
73
+ vertical: "\u2502",
74
+ horizontal: "\u2500",
75
+ branchLeft: "\u251C",
76
+ branchRight: "\u2524",
77
+ merge: "\u253C",
78
+ cornerDownRight: "\u256D",
79
+ cornerDownLeft: "\u256E",
80
+ diagonalRight: "\u2571",
81
+ diagonalLeft: "\u2572",
82
+ space: " "
83
+ };
84
+ var asciiChars = {
85
+ node: "*",
86
+ vertical: "|",
87
+ horizontal: "-",
88
+ branchLeft: "+",
89
+ branchRight: "+",
90
+ merge: "+",
91
+ cornerDownRight: ",",
92
+ cornerDownLeft: ".",
93
+ diagonalRight: "/",
94
+ diagonalLeft: "\\",
95
+ space: " "
96
+ };
97
+ function getGraphChars(style) {
98
+ switch (style) {
99
+ case "unicode":
100
+ return unicodeChars;
101
+ case "ascii":
102
+ return asciiChars;
103
+ }
104
+ }
105
+
106
+ // src/layout.ts
107
+ function findAvailableColumn(state) {
108
+ let col = 0;
109
+ while (state.activeColumns.has(col)) {
110
+ col++;
111
+ }
112
+ return col;
113
+ }
114
+ function computeGraphRow(nodeColumn, activeColumns, maxCol, chars, isMerge, mergeFromColumns, nodeChar) {
115
+ const parts = [];
116
+ const maxMergeCol = isMerge ? Math.max(...mergeFromColumns, nodeColumn) : -1;
117
+ for (let col = 0; col <= maxCol; col++) {
118
+ if (col === nodeColumn) {
119
+ parts.push(nodeChar);
120
+ } else if (isMerge && mergeFromColumns.includes(col)) {
121
+ parts.push(chars.cornerDownLeft);
122
+ } else if (activeColumns.has(col)) {
123
+ parts.push(chars.vertical);
124
+ } else {
125
+ parts.push(chars.space);
126
+ }
127
+ if (col < maxCol) {
128
+ if (isMerge && col >= nodeColumn && col < maxMergeCol) {
129
+ parts.push(chars.horizontal);
130
+ } else {
131
+ parts.push(chars.space);
132
+ }
133
+ }
134
+ }
135
+ return parts.join("");
136
+ }
137
+ function computeContinuationLine(activeColumns, maxCol, chars) {
138
+ const parts = [];
139
+ for (let col = 0; col <= maxCol; col++) {
140
+ if (activeColumns.has(col)) {
141
+ parts.push(chars.vertical);
142
+ } else {
143
+ parts.push(chars.space);
144
+ }
145
+ if (col < maxCol) {
146
+ parts.push(chars.space);
147
+ }
148
+ }
149
+ return parts.join("");
150
+ }
151
+ function computeGraphLayout(input, chars) {
152
+ if (input.nodes.length === 0) {
153
+ return {
154
+ nodes: [],
155
+ maxColumns: 0,
156
+ graphWidth: 0
157
+ };
158
+ }
159
+ const state = {
160
+ nodeColumns: /* @__PURE__ */ new Map(),
161
+ activeColumns: /* @__PURE__ */ new Set(),
162
+ maxColumn: 0
163
+ };
164
+ const childrenCount = /* @__PURE__ */ new Map();
165
+ for (const node of input.nodes) {
166
+ for (const parentId of node.parents) {
167
+ childrenCount.set(parentId, (childrenCount.get(parentId) ?? 0) + 1);
168
+ }
169
+ }
170
+ const nodeLayouts = [];
171
+ for (let i = 0; i < input.nodes.length; i++) {
172
+ const node = input.nodes[i];
173
+ if (!node) continue;
174
+ const isLastNode = i === input.nodes.length - 1;
175
+ let nodeColumn;
176
+ const preassigned = state.nodeColumns.get(node.id);
177
+ if (preassigned !== void 0) {
178
+ nodeColumn = preassigned;
179
+ } else if (node.parents.length > 0) {
180
+ const firstParent = node.parents[0];
181
+ const firstParentCol = firstParent ? state.nodeColumns.get(firstParent) : void 0;
182
+ if (firstParentCol !== void 0) {
183
+ nodeColumn = firstParentCol;
184
+ } else {
185
+ nodeColumn = findAvailableColumn(state);
186
+ }
187
+ } else {
188
+ nodeColumn = findAvailableColumn(state);
189
+ }
190
+ state.nodeColumns.set(node.id, nodeColumn);
191
+ state.activeColumns.add(nodeColumn);
192
+ state.maxColumn = Math.max(state.maxColumn, nodeColumn);
193
+ const parentColumns = [];
194
+ for (let p = 0; p < node.parents.length; p++) {
195
+ const parentId = node.parents[p];
196
+ if (!parentId) continue;
197
+ let parentCol = state.nodeColumns.get(parentId);
198
+ if (parentCol === void 0) {
199
+ if (p === 0) {
200
+ parentCol = nodeColumn;
201
+ } else {
202
+ parentCol = findAvailableColumn(state);
203
+ }
204
+ state.nodeColumns.set(parentId, parentCol);
205
+ state.activeColumns.add(parentCol);
206
+ state.maxColumn = Math.max(state.maxColumn, parentCol);
207
+ }
208
+ parentColumns.push(parentCol);
209
+ }
210
+ const isMerge = node.parents.length >= 2;
211
+ const mergeFromColumns = isMerge ? parentColumns.filter((c) => c !== nodeColumn) : [];
212
+ const nodeChar = node.nodeChar ?? input.nodeChar ?? chars.node;
213
+ const graphLine = computeGraphRow(
214
+ nodeColumn,
215
+ state.activeColumns,
216
+ state.maxColumn,
217
+ chars,
218
+ isMerge,
219
+ mergeFromColumns,
220
+ nodeChar
221
+ );
222
+ const continuationLines = [];
223
+ if (!isLastNode) {
224
+ if (parentColumns.length > 0 && parentColumns[0] !== nodeColumn) {
225
+ state.activeColumns.delete(nodeColumn);
226
+ }
227
+ if (state.activeColumns.size > 0) {
228
+ continuationLines.push(
229
+ computeContinuationLine(state.activeColumns, state.maxColumn, chars)
230
+ );
231
+ }
232
+ }
233
+ nodeLayouts.push({
234
+ node,
235
+ column: nodeColumn,
236
+ graphLine,
237
+ continuationLines
238
+ });
239
+ }
240
+ const graphWidth = state.maxColumn * 2 + 1;
241
+ return {
242
+ nodes: nodeLayouts,
243
+ maxColumns: state.maxColumn + 1,
244
+ graphWidth
245
+ };
246
+ }
247
+
248
+ // src/renderers.ts
249
+ import {
250
+ truncateToWidth,
251
+ anchorLine,
252
+ DEFAULT_ANCHOR
253
+ } from "@tuicomponents/core";
254
+ function formatRefs(refs, theme) {
255
+ if (!refs || refs.length === 0) {
256
+ return "";
257
+ }
258
+ const refStr = `(${refs.join(", ")})`;
259
+ if (theme) {
260
+ return theme.semantic.secondary(refStr) + " ";
261
+ }
262
+ return refStr + " ";
263
+ }
264
+ function renderNodeLineAnsi(nodeLayout, input, graphWidth, theme) {
265
+ const { node, graphLine } = nodeLayout;
266
+ const refsStr = input.showRefs ? formatRefs(node.refs, theme) : "";
267
+ const availableWidth = input.labelWidth - (refsStr ? refsStr.length : 0);
268
+ let label = truncateToWidth(node.label, Math.max(10, availableWidth));
269
+ if (node.highlight && theme) {
270
+ label = theme.semantic.primary(label);
271
+ } else if (theme) {
272
+ label = theme.semantic.header(label);
273
+ }
274
+ const gap = " ".repeat(input.labelGap);
275
+ const paddedGraph = graphLine.padEnd(graphWidth);
276
+ const coloredPaddedGraph = theme ? theme.semantic.border(paddedGraph) : paddedGraph;
277
+ return `${coloredPaddedGraph}${gap}${refsStr}${label}`;
278
+ }
279
+ function renderContinuationLineAnsi(line, graphWidth, theme) {
280
+ const paddedLine = line.padEnd(graphWidth);
281
+ return theme ? theme.semantic.border(paddedLine) : paddedLine;
282
+ }
283
+ function renderGraphAnsi(layout, input, theme) {
284
+ if (layout.nodes.length === 0) {
285
+ return "";
286
+ }
287
+ const lines = [];
288
+ for (const nodeLayout of layout.nodes) {
289
+ lines.push(renderNodeLineAnsi(nodeLayout, input, layout.graphWidth, theme));
290
+ for (const contLine of nodeLayout.continuationLines) {
291
+ lines.push(
292
+ renderContinuationLineAnsi(contLine, layout.graphWidth, theme)
293
+ );
294
+ }
295
+ }
296
+ return lines.join("\n");
297
+ }
298
+ function renderNodeLineMarkdown(nodeLayout, input, graphWidth) {
299
+ const { node, graphLine } = nodeLayout;
300
+ const refsStr = input.showRefs && node.refs?.length ? `(${node.refs.join(", ")}) ` : "";
301
+ const availableWidth = input.labelWidth - refsStr.length;
302
+ const label = truncateToWidth(node.label, Math.max(10, availableWidth));
303
+ const gap = " ".repeat(input.labelGap);
304
+ const paddedGraph = graphLine.padEnd(graphWidth);
305
+ const line = `${paddedGraph}${gap}${refsStr}${label}`;
306
+ if (node.highlight) {
307
+ return anchorLine(`\`${line}\``, DEFAULT_ANCHOR);
308
+ }
309
+ return anchorLine(line, DEFAULT_ANCHOR);
310
+ }
311
+ function renderContinuationLineMarkdown(line, graphWidth) {
312
+ const paddedLine = line.padEnd(graphWidth);
313
+ return anchorLine(paddedLine, DEFAULT_ANCHOR);
314
+ }
315
+ function renderGraphMarkdown(layout, input) {
316
+ if (layout.nodes.length === 0) {
317
+ return "";
318
+ }
319
+ const lines = [];
320
+ for (const nodeLayout of layout.nodes) {
321
+ lines.push(renderNodeLineMarkdown(nodeLayout, input, layout.graphWidth));
322
+ for (const contLine of nodeLayout.continuationLines) {
323
+ lines.push(renderContinuationLineMarkdown(contLine, layout.graphWidth));
324
+ }
325
+ }
326
+ return lines.join("\n");
327
+ }
328
+
329
+ // src/graph.ts
330
+ var GraphComponent = class extends BaseTuiComponent {
331
+ metadata = {
332
+ name: "graph",
333
+ description: "Renders DAG visualizations similar to git log --graph",
334
+ version: "0.1.0",
335
+ supportedModes: ["ansi", "markdown"],
336
+ examples: [
337
+ {
338
+ name: "linear",
339
+ description: "Simple linear commit history",
340
+ input: {
341
+ nodes: [
342
+ { id: "c", label: "feat: add validation" },
343
+ { id: "b", label: "fix: handle edge case", parents: ["c"] },
344
+ { id: "a", label: "initial commit", parents: ["b"] }
345
+ ]
346
+ }
347
+ },
348
+ {
349
+ name: "with-refs",
350
+ description: "Commits with branch/tag refs",
351
+ input: {
352
+ nodes: [
353
+ { id: "d", label: "latest changes", refs: ["main", "HEAD"] },
354
+ {
355
+ id: "c",
356
+ label: "feat: new feature",
357
+ parents: ["d"],
358
+ refs: ["feature-branch"]
359
+ },
360
+ { id: "b", label: "fix: bug fix", parents: ["c"] },
361
+ {
362
+ id: "a",
363
+ label: "initial commit",
364
+ parents: ["b"],
365
+ refs: ["v1.0.0"]
366
+ }
367
+ ]
368
+ }
369
+ },
370
+ {
371
+ name: "branch-and-merge",
372
+ description: "Branch and merge visualization",
373
+ input: {
374
+ nodes: [
375
+ {
376
+ id: "e",
377
+ label: "Merge branch 'feature'",
378
+ parents: ["d", "c"],
379
+ refs: ["main"]
380
+ },
381
+ { id: "d", label: "hotfix on main", parents: ["a"] },
382
+ {
383
+ id: "c",
384
+ label: "add new feature",
385
+ parents: ["b"],
386
+ refs: ["feature"]
387
+ },
388
+ { id: "b", label: "start feature branch", parents: ["a"] },
389
+ { id: "a", label: "initial commit" }
390
+ ]
391
+ }
392
+ },
393
+ {
394
+ name: "multiple-branches",
395
+ description: "Multiple concurrent branches",
396
+ input: {
397
+ nodes: [
398
+ {
399
+ id: "g",
400
+ label: "Merge all branches",
401
+ parents: ["f", "e", "d"],
402
+ refs: ["main"]
403
+ },
404
+ { id: "f", label: "work on branch 1", parents: ["a"] },
405
+ { id: "e", label: "work on branch 2", parents: ["a"] },
406
+ { id: "d", label: "work on branch 3", parents: ["a"] },
407
+ { id: "a", label: "initial commit" }
408
+ ]
409
+ }
410
+ },
411
+ {
412
+ name: "ascii-style",
413
+ description: "Using ASCII characters",
414
+ input: {
415
+ nodes: [
416
+ { id: "c", label: "third commit" },
417
+ { id: "b", label: "second commit", parents: ["c"] },
418
+ { id: "a", label: "first commit", parents: ["b"] }
419
+ ],
420
+ style: "ascii"
421
+ }
422
+ },
423
+ {
424
+ name: "highlighted",
425
+ description: "Commit with highlight",
426
+ input: {
427
+ nodes: [
428
+ { id: "d", label: "current HEAD", highlight: true, refs: ["HEAD"] },
429
+ { id: "c", label: "previous commit", parents: ["d"] },
430
+ { id: "b", label: "earlier commit", parents: ["c"] },
431
+ { id: "a", label: "initial commit", parents: ["b"] }
432
+ ]
433
+ }
434
+ }
435
+ ]
436
+ };
437
+ schema = graphInputSchema;
438
+ /**
439
+ * Override getJsonSchema to use direct schema generation.
440
+ */
441
+ getJsonSchema() {
442
+ return zodToJsonSchema(this.schema, {
443
+ name: this.metadata.name,
444
+ $refStrategy: "none"
445
+ });
446
+ }
447
+ render(input, context) {
448
+ const parsed = this.schema.parse(input);
449
+ if (parsed.nodes.length === 0) {
450
+ return { output: "", actualWidth: 0, lineCount: 0 };
451
+ }
452
+ const chars = getGraphChars(parsed.style);
453
+ const layout = computeGraphLayout(parsed, chars);
454
+ const output = context.renderMode === "markdown" ? renderGraphMarkdown(layout, parsed) : renderGraphAnsi(layout, parsed, context.theme);
455
+ const measured = measureLines(output);
456
+ return {
457
+ output,
458
+ actualWidth: measured.maxWidth,
459
+ lineCount: measured.lineCount
460
+ };
461
+ }
462
+ };
463
+ function createGraph() {
464
+ return new GraphComponent();
465
+ }
466
+ registry.register(createGraph);
467
+ export {
468
+ GraphComponent,
469
+ computeGraphLayout,
470
+ createGraph,
471
+ getGraphChars,
472
+ graphInputSchema,
473
+ graphNodeSchema,
474
+ graphStyleSchema,
475
+ renderGraphAnsi,
476
+ renderGraphMarkdown
477
+ };
478
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/graph.ts","../src/schema.ts","../src/chars.ts","../src/layout.ts","../src/renderers.ts"],"sourcesContent":["import {\n BaseTuiComponent,\n type ComponentMetadata,\n type RenderContext,\n type RenderResult,\n measureLines,\n registry,\n} from \"@tuicomponents/core\";\nimport { zodToJsonSchema } from \"zod-to-json-schema\";\nimport { graphInputSchema, type GraphInput } from \"./schema.js\";\nimport { getGraphChars } from \"./chars.js\";\nimport { computeGraphLayout } from \"./layout.js\";\nimport { renderGraphAnsi, renderGraphMarkdown } from \"./renderers.js\";\n\n/**\n * Graph component for rendering DAG visualizations (git log style).\n */\nclass GraphComponent extends BaseTuiComponent<\n GraphInput,\n typeof graphInputSchema\n> {\n readonly metadata: ComponentMetadata<GraphInput> = {\n name: \"graph\",\n description: \"Renders DAG visualizations similar to git log --graph\",\n version: \"0.1.0\",\n supportedModes: [\"ansi\", \"markdown\"],\n examples: [\n {\n name: \"linear\",\n description: \"Simple linear commit history\",\n input: {\n nodes: [\n { id: \"c\", label: \"feat: add validation\" },\n { id: \"b\", label: \"fix: handle edge case\", parents: [\"c\"] },\n { id: \"a\", label: \"initial commit\", parents: [\"b\"] },\n ],\n },\n },\n {\n name: \"with-refs\",\n description: \"Commits with branch/tag refs\",\n input: {\n nodes: [\n { id: \"d\", label: \"latest changes\", refs: [\"main\", \"HEAD\"] },\n {\n id: \"c\",\n label: \"feat: new feature\",\n parents: [\"d\"],\n refs: [\"feature-branch\"],\n },\n { id: \"b\", label: \"fix: bug fix\", parents: [\"c\"] },\n {\n id: \"a\",\n label: \"initial commit\",\n parents: [\"b\"],\n refs: [\"v1.0.0\"],\n },\n ],\n },\n },\n {\n name: \"branch-and-merge\",\n description: \"Branch and merge visualization\",\n input: {\n nodes: [\n {\n id: \"e\",\n label: \"Merge branch 'feature'\",\n parents: [\"d\", \"c\"],\n refs: [\"main\"],\n },\n { id: \"d\", label: \"hotfix on main\", parents: [\"a\"] },\n {\n id: \"c\",\n label: \"add new feature\",\n parents: [\"b\"],\n refs: [\"feature\"],\n },\n { id: \"b\", label: \"start feature branch\", parents: [\"a\"] },\n { id: \"a\", label: \"initial commit\" },\n ],\n },\n },\n {\n name: \"multiple-branches\",\n description: \"Multiple concurrent branches\",\n input: {\n nodes: [\n {\n id: \"g\",\n label: \"Merge all branches\",\n parents: [\"f\", \"e\", \"d\"],\n refs: [\"main\"],\n },\n { id: \"f\", label: \"work on branch 1\", parents: [\"a\"] },\n { id: \"e\", label: \"work on branch 2\", parents: [\"a\"] },\n { id: \"d\", label: \"work on branch 3\", parents: [\"a\"] },\n { id: \"a\", label: \"initial commit\" },\n ],\n },\n },\n {\n name: \"ascii-style\",\n description: \"Using ASCII characters\",\n input: {\n nodes: [\n { id: \"c\", label: \"third commit\" },\n { id: \"b\", label: \"second commit\", parents: [\"c\"] },\n { id: \"a\", label: \"first commit\", parents: [\"b\"] },\n ],\n style: \"ascii\",\n },\n },\n {\n name: \"highlighted\",\n description: \"Commit with highlight\",\n input: {\n nodes: [\n { id: \"d\", label: \"current HEAD\", highlight: true, refs: [\"HEAD\"] },\n { id: \"c\", label: \"previous commit\", parents: [\"d\"] },\n { id: \"b\", label: \"earlier commit\", parents: [\"c\"] },\n { id: \"a\", label: \"initial commit\", parents: [\"b\"] },\n ],\n },\n },\n ],\n };\n\n readonly schema = graphInputSchema;\n\n /**\n * Override getJsonSchema to use direct schema generation.\n */\n override getJsonSchema(): object {\n return zodToJsonSchema(this.schema, {\n name: this.metadata.name,\n $refStrategy: \"none\",\n });\n }\n\n render(input: GraphInput, context: RenderContext): RenderResult {\n const parsed = this.schema.parse(input);\n\n if (parsed.nodes.length === 0) {\n return { output: \"\", actualWidth: 0, lineCount: 0 };\n }\n\n // Get character set based on style\n const chars = getGraphChars(parsed.style);\n\n // Compute layout once, share between renderers\n const layout = computeGraphLayout(parsed, chars);\n\n // Choose renderer based on render mode\n const output =\n context.renderMode === \"markdown\"\n ? renderGraphMarkdown(layout, parsed)\n : renderGraphAnsi(layout, parsed, context.theme);\n\n const measured = measureLines(output);\n\n return {\n output,\n actualWidth: measured.maxWidth,\n lineCount: measured.lineCount,\n };\n }\n}\n\n/**\n * Factory function to create a graph component.\n */\nexport function createGraph(): GraphComponent {\n return new GraphComponent();\n}\n\n// Register with global registry\nregistry.register(createGraph);\n\nexport { GraphComponent };\n","import { z } from \"zod\";\n\n/**\n * Style options for graph line characters.\n */\nexport const graphStyleSchema = z.enum([\"ascii\", \"unicode\"]);\n\nexport type GraphStyle = z.infer<typeof graphStyleSchema>;\n\n/**\n * Schema for a single node in the graph.\n */\nexport const graphNodeSchema = z.object({\n /**\n * Unique identifier for this node.\n */\n id: z.string(),\n\n /**\n * Display label for the node (e.g., commit message).\n */\n label: z.string(),\n\n /**\n * IDs of parent nodes (empty for root nodes).\n */\n parents: z.array(z.string()).default([]),\n\n /**\n * Optional ref names (branch/tag names) to display.\n */\n refs: z.array(z.string()).optional(),\n\n /**\n * Whether this node should be highlighted.\n */\n highlight: z.boolean().optional(),\n\n /**\n * Custom character to represent this node (overrides global nodeChar).\n */\n nodeChar: z.string().length(1).optional(),\n});\n\nexport type GraphNode = z.infer<typeof graphNodeSchema>;\n\n/**\n * Schema for graph component input.\n */\nexport const graphInputSchema = z.object({\n /**\n * Array of nodes in topological order (newest/most recent first).\n */\n nodes: z.array(graphNodeSchema),\n\n /**\n * Style for graph characters.\n * @default \"unicode\"\n */\n style: graphStyleSchema.default(\"unicode\"),\n\n /**\n * Maximum width for labels (truncated if longer).\n * @default 50\n */\n labelWidth: z.number().int().positive().default(50),\n\n /**\n * Whether to show refs (branch/tag names).\n * @default true\n */\n showRefs: z.boolean().default(true),\n\n /**\n * Gap between graph and label.\n * @default 1\n */\n labelGap: z.number().int().nonnegative().default(1),\n\n /**\n * Custom character to represent nodes (can be overridden per-node).\n * If not specified, uses the default for the style (● for unicode, * for ascii).\n */\n nodeChar: z.string().length(1).optional(),\n});\n\nexport type GraphInput = z.input<typeof graphInputSchema>;\nexport type GraphInputWithDefaults = z.output<typeof graphInputSchema>;\n","import type { GraphStyle } from \"./schema.js\";\n\n/**\n * Characters used for graph rendering.\n */\nexport interface GraphChars {\n /** Node marker */\n node: string;\n /** Vertical line */\n vertical: string;\n /** Horizontal line */\n horizontal: string;\n /** Left branch (fork going down-right) */\n branchLeft: string;\n /** Right branch (fork going down-left) */\n branchRight: string;\n /** Merge point (multiple lines converging) */\n merge: string;\n /** Down and right corner */\n cornerDownRight: string;\n /** Down and left corner */\n cornerDownLeft: string;\n /** Diagonal going down-right */\n diagonalRight: string;\n /** Diagonal going down-left */\n diagonalLeft: string;\n /** Space for alignment */\n space: string;\n}\n\n/**\n * Unicode graph characters.\n */\nconst unicodeChars: GraphChars = {\n node: \"●\",\n vertical: \"│\",\n horizontal: \"─\",\n branchLeft: \"├\",\n branchRight: \"┤\",\n merge: \"┼\",\n cornerDownRight: \"╭\",\n cornerDownLeft: \"╮\",\n diagonalRight: \"╱\",\n diagonalLeft: \"╲\",\n space: \" \",\n};\n\n/**\n * ASCII graph characters.\n */\nconst asciiChars: GraphChars = {\n node: \"*\",\n vertical: \"|\",\n horizontal: \"-\",\n branchLeft: \"+\",\n branchRight: \"+\",\n merge: \"+\",\n cornerDownRight: \",\",\n cornerDownLeft: \".\",\n diagonalRight: \"/\",\n diagonalLeft: \"\\\\\",\n space: \" \",\n};\n\n/**\n * Get graph characters for a given style.\n */\nexport function getGraphChars(style: GraphStyle): GraphChars {\n switch (style) {\n case \"unicode\":\n return unicodeChars;\n case \"ascii\":\n return asciiChars;\n }\n}\n","import type { GraphInputWithDefaults, GraphNode } from \"./schema.js\";\nimport type { GraphChars } from \"./chars.js\";\n\n/**\n * Layout information for a single node.\n */\nexport interface NodeLayout {\n /** The original node */\n node: GraphNode;\n /** Column index for this node (0-based) */\n column: number;\n /** Graph portion of this row (before label) */\n graphLine: string;\n /** Continuation lines between this node and the next */\n continuationLines: string[];\n}\n\n/**\n * Pre-computed layout for the entire graph.\n */\nexport interface GraphLayout {\n /** Layout for each node */\n nodes: NodeLayout[];\n /** Maximum number of columns used */\n maxColumns: number;\n /** Width of the graph portion (in characters) */\n graphWidth: number;\n}\n\n/**\n * Internal state for column tracking during layout computation.\n */\ninterface ColumnState {\n /** Maps node ID to its assigned column */\n nodeColumns: Map<string, number>;\n /** Which columns are currently occupied by active branches */\n activeColumns: Set<number>;\n /** Maximum column index used so far */\n maxColumn: number;\n}\n\n/**\n * Find the first available column (not in use).\n */\nfunction findAvailableColumn(state: ColumnState): number {\n let col = 0;\n while (state.activeColumns.has(col)) {\n col++;\n }\n return col;\n}\n\n/**\n * Compute the graph portion for a single row.\n * Uses git-style: * for node, | for active columns\n */\nfunction computeGraphRow(\n nodeColumn: number,\n activeColumns: Set<number>,\n maxCol: number,\n chars: GraphChars,\n isMerge: boolean,\n mergeFromColumns: number[],\n nodeChar: string\n): string {\n const parts: string[] = [];\n\n // For merges, compute the rightmost merge column\n const maxMergeCol = isMerge ? Math.max(...mergeFromColumns, nodeColumn) : -1;\n\n for (let col = 0; col <= maxCol; col++) {\n if (col === nodeColumn) {\n parts.push(nodeChar);\n } else if (isMerge && mergeFromColumns.includes(col)) {\n // This column has a parent merging in - show corner\n parts.push(chars.cornerDownLeft);\n } else if (activeColumns.has(col)) {\n parts.push(chars.vertical);\n } else {\n parts.push(chars.space);\n }\n\n // Add spacing between columns\n if (col < maxCol) {\n // If this is a merge and we're between node and a merge source,\n // draw horizontal line instead of space\n if (isMerge && col >= nodeColumn && col < maxMergeCol) {\n parts.push(chars.horizontal);\n } else {\n parts.push(chars.space);\n }\n }\n }\n\n return parts.join(\"\");\n}\n\n/**\n * Compute a simple continuation line showing active columns.\n */\nfunction computeContinuationLine(\n activeColumns: Set<number>,\n maxCol: number,\n chars: GraphChars\n): string {\n const parts: string[] = [];\n for (let col = 0; col <= maxCol; col++) {\n if (activeColumns.has(col)) {\n parts.push(chars.vertical);\n } else {\n parts.push(chars.space);\n }\n if (col < maxCol) {\n parts.push(chars.space);\n }\n }\n return parts.join(\"\");\n}\n\n/**\n * Compute the layout for a graph.\n *\n * Uses a column-sweep algorithm optimized for newest-first processing order,\n * producing git log --graph style output.\n *\n * @param input - Validated graph input with defaults applied\n * @param chars - Character set to use\n * @returns Computed layout ready for rendering\n */\nexport function computeGraphLayout(\n input: GraphInputWithDefaults,\n chars: GraphChars\n): GraphLayout {\n if (input.nodes.length === 0) {\n return {\n nodes: [],\n maxColumns: 0,\n graphWidth: 0,\n };\n }\n\n const state: ColumnState = {\n nodeColumns: new Map(),\n activeColumns: new Set(),\n maxColumn: 0,\n };\n\n // Track children count for each node\n const childrenCount = new Map<string, number>();\n for (const node of input.nodes) {\n for (const parentId of node.parents) {\n childrenCount.set(parentId, (childrenCount.get(parentId) ?? 0) + 1);\n }\n }\n\n const nodeLayouts: NodeLayout[] = [];\n\n for (let i = 0; i < input.nodes.length; i++) {\n const node = input.nodes[i];\n if (!node) continue;\n const isLastNode = i === input.nodes.length - 1;\n\n // Determine column for this node\n let nodeColumn: number;\n\n // Check if this node was pre-assigned a column (by a child's parent reference)\n const preassigned = state.nodeColumns.get(node.id);\n if (preassigned !== undefined) {\n nodeColumn = preassigned;\n } else if (node.parents.length > 0) {\n // Check if first parent already has a column assigned\n const firstParent = node.parents[0];\n const firstParentCol = firstParent\n ? state.nodeColumns.get(firstParent)\n : undefined;\n if (firstParentCol !== undefined) {\n nodeColumn = firstParentCol;\n } else {\n nodeColumn = findAvailableColumn(state);\n }\n } else {\n nodeColumn = findAvailableColumn(state);\n }\n\n // Record this node's column and mark as active\n state.nodeColumns.set(node.id, nodeColumn);\n state.activeColumns.add(nodeColumn);\n state.maxColumn = Math.max(state.maxColumn, nodeColumn);\n\n // Pre-assign columns to parents\n const parentColumns: number[] = [];\n for (let p = 0; p < node.parents.length; p++) {\n const parentId = node.parents[p];\n if (!parentId) continue;\n let parentCol = state.nodeColumns.get(parentId);\n\n if (parentCol === undefined) {\n if (p === 0) {\n parentCol = nodeColumn;\n } else {\n parentCol = findAvailableColumn(state);\n }\n state.nodeColumns.set(parentId, parentCol);\n state.activeColumns.add(parentCol);\n state.maxColumn = Math.max(state.maxColumn, parentCol);\n }\n parentColumns.push(parentCol);\n }\n\n // Determine if this is a merge (2+ parents)\n const isMerge = node.parents.length >= 2;\n const mergeFromColumns = isMerge\n ? parentColumns.filter((c) => c !== nodeColumn)\n : [];\n\n // Determine the node character (per-node override > global override > default)\n const nodeChar = node.nodeChar ?? input.nodeChar ?? chars.node;\n\n // Compute the graph line for this row\n const graphLine = computeGraphRow(\n nodeColumn,\n state.activeColumns,\n state.maxColumn,\n chars,\n isMerge,\n mergeFromColumns,\n nodeChar\n );\n\n // Compute continuation lines\n const continuationLines: string[] = [];\n if (!isLastNode) {\n // Release columns for branches that merge into this node's column\n if (parentColumns.length > 0 && parentColumns[0] !== nodeColumn) {\n state.activeColumns.delete(nodeColumn);\n }\n\n // Add continuation line if there are active columns\n if (state.activeColumns.size > 0) {\n continuationLines.push(\n computeContinuationLine(state.activeColumns, state.maxColumn, chars)\n );\n }\n }\n\n nodeLayouts.push({\n node,\n column: nodeColumn,\n graphLine,\n continuationLines,\n });\n }\n\n // Calculate graph width (each column is 2 chars: symbol + space)\n const graphWidth = state.maxColumn * 2 + 1;\n\n return {\n nodes: nodeLayouts,\n maxColumns: state.maxColumn + 1,\n graphWidth,\n };\n}\n","import {\n type TuiTheme,\n truncateToWidth,\n anchorLine,\n DEFAULT_ANCHOR,\n} from \"@tuicomponents/core\";\nimport type { GraphLayout, NodeLayout } from \"./layout.js\";\nimport type { GraphInputWithDefaults } from \"./schema.js\";\n\n/**\n * Format refs (branch/tag names) for display.\n */\nfunction formatRefs(refs: string[] | undefined, theme?: TuiTheme): string {\n if (!refs || refs.length === 0) {\n return \"\";\n }\n\n const refStr = `(${refs.join(\", \")})`;\n if (theme) {\n return theme.semantic.secondary(refStr) + \" \";\n }\n return refStr + \" \";\n}\n\n/**\n * Render a node line in ANSI mode.\n */\nfunction renderNodeLineAnsi(\n nodeLayout: NodeLayout,\n input: GraphInputWithDefaults,\n graphWidth: number,\n theme?: TuiTheme\n): string {\n const { node, graphLine } = nodeLayout;\n\n // Format refs if enabled\n const refsStr = input.showRefs ? formatRefs(node.refs, theme) : \"\";\n\n // Truncate label if needed (accounting for refs)\n const availableWidth = input.labelWidth - (refsStr ? refsStr.length : 0);\n let label = truncateToWidth(node.label, Math.max(10, availableWidth));\n\n // Apply highlighting if enabled\n if (node.highlight && theme) {\n label = theme.semantic.primary(label);\n } else if (theme) {\n label = theme.semantic.header(label);\n }\n\n // Build the full line\n const gap = \" \".repeat(input.labelGap);\n const paddedGraph = graphLine.padEnd(graphWidth);\n const coloredPaddedGraph = theme\n ? theme.semantic.border(paddedGraph)\n : paddedGraph;\n\n return `${coloredPaddedGraph}${gap}${refsStr}${label}`;\n}\n\n/**\n * Render a continuation line in ANSI mode.\n */\nfunction renderContinuationLineAnsi(\n line: string,\n graphWidth: number,\n theme?: TuiTheme\n): string {\n const paddedLine = line.padEnd(graphWidth);\n return theme ? theme.semantic.border(paddedLine) : paddedLine;\n}\n\n/**\n * Render a graph using ANSI escape codes for rich terminal output.\n *\n * @param layout - Pre-computed graph layout\n * @param input - Original input with defaults\n * @param theme - Optional theme for colors\n * @returns ANSI-formatted graph string\n */\nexport function renderGraphAnsi(\n layout: GraphLayout,\n input: GraphInputWithDefaults,\n theme?: TuiTheme\n): string {\n if (layout.nodes.length === 0) {\n return \"\";\n }\n\n const lines: string[] = [];\n\n for (const nodeLayout of layout.nodes) {\n // Render the node line\n lines.push(renderNodeLineAnsi(nodeLayout, input, layout.graphWidth, theme));\n\n // Render continuation lines\n for (const contLine of nodeLayout.continuationLines) {\n lines.push(\n renderContinuationLineAnsi(contLine, layout.graphWidth, theme)\n );\n }\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Render a node line in markdown mode.\n */\nfunction renderNodeLineMarkdown(\n nodeLayout: NodeLayout,\n input: GraphInputWithDefaults,\n graphWidth: number\n): string {\n const { node, graphLine } = nodeLayout;\n\n // Format refs if enabled (no colors in markdown)\n const refsStr =\n input.showRefs && node.refs?.length ? `(${node.refs.join(\", \")}) ` : \"\";\n\n // Truncate label if needed\n const availableWidth = input.labelWidth - refsStr.length;\n const label = truncateToWidth(node.label, Math.max(10, availableWidth));\n\n // Build the line\n const gap = \" \".repeat(input.labelGap);\n const paddedGraph = graphLine.padEnd(graphWidth);\n const line = `${paddedGraph}${gap}${refsStr}${label}`;\n\n // Highlight with inline code backticks in markdown\n if (node.highlight) {\n return anchorLine(`\\`${line}\\``, DEFAULT_ANCHOR);\n }\n\n return anchorLine(line, DEFAULT_ANCHOR);\n}\n\n/**\n * Render a continuation line in markdown mode.\n */\nfunction renderContinuationLineMarkdown(\n line: string,\n graphWidth: number\n): string {\n const paddedLine = line.padEnd(graphWidth);\n return anchorLine(paddedLine, DEFAULT_ANCHOR);\n}\n\n/**\n * Render a graph using markdown-friendly output.\n *\n * Uses anchored lines to prevent whitespace collapse in markdown renderers.\n *\n * @param layout - Pre-computed graph layout\n * @param input - Original input with defaults\n * @returns Markdown-friendly graph string\n */\nexport function renderGraphMarkdown(\n layout: GraphLayout,\n input: GraphInputWithDefaults\n): string {\n if (layout.nodes.length === 0) {\n return \"\";\n }\n\n const lines: string[] = [];\n\n for (const nodeLayout of layout.nodes) {\n // Render the node line\n lines.push(renderNodeLineMarkdown(nodeLayout, input, layout.graphWidth));\n\n // Render continuation lines\n for (const contLine of nodeLayout.continuationLines) {\n lines.push(renderContinuationLineMarkdown(contLine, layout.graphWidth));\n }\n }\n\n return lines.join(\"\\n\");\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@tuicomponents/graph",
3
+ "version": "0.1.1",
4
+ "description": "DAG graph visualization component for TUI (git log style)",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "keywords": [
25
+ "tui",
26
+ "terminal",
27
+ "graph",
28
+ "dag",
29
+ "git",
30
+ "visualization"
31
+ ],
32
+ "license": "UNLICENSED",
33
+ "dependencies": {
34
+ "zod": "^3.25.56",
35
+ "zod-to-json-schema": "^3.24.5",
36
+ "@tuicomponents/core": "0.1.1"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "^22.0.0"
40
+ },
41
+ "scripts": {
42
+ "build": "tsup",
43
+ "typecheck": "tsc --noEmit",
44
+ "lint": "eslint src",
45
+ "test": "vitest run",
46
+ "test:watch": "vitest",
47
+ "api-report": "api-extractor run",
48
+ "api-report:update": "api-extractor run --local",
49
+ "clean": "rm -rf dist"
50
+ }
51
+ }