@ts-graphviz/react 0.10.6 → 0.11.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +487 -0
  2. package/README.md +630 -67
  3. package/example/example.tsx +1 -1
  4. package/lib/react.d.ts +198 -96
  5. package/lib/react.js +558 -215
  6. package/package.json +15 -20
  7. package/tsconfig.test.json +11 -0
  8. package/lib/react.cjs +0 -457
  9. package/src/components/Digraph.spec.tsx +0 -18
  10. package/src/components/Digraph.tsx +0 -41
  11. package/src/components/Edge.tsx +0 -17
  12. package/src/components/Graph.spec.tsx +0 -18
  13. package/src/components/Graph.tsx +0 -36
  14. package/src/components/GraphPortal.tsx +0 -22
  15. package/src/components/Node.spec.tsx +0 -101
  16. package/src/components/Node.tsx +0 -21
  17. package/src/components/Subgraph.tsx +0 -30
  18. package/src/contexts/CurrentGraph.ts +0 -5
  19. package/src/contexts/GraphContainer.ts +0 -5
  20. package/src/contexts/GraphMap.ts +0 -5
  21. package/src/contexts/GraphvizContext.ts +0 -11
  22. package/src/hooks/__tests__/wrapper.tsx +0 -58
  23. package/src/hooks/useCurrentGraph.spec.ts +0 -56
  24. package/src/hooks/useCurrentGraph.ts +0 -18
  25. package/src/hooks/useDigraph.spec.ts +0 -15
  26. package/src/hooks/useDigraph.ts +0 -33
  27. package/src/hooks/useEdge.spec.ts +0 -46
  28. package/src/hooks/useEdge.ts +0 -35
  29. package/src/hooks/useGraph.spec.ts +0 -15
  30. package/src/hooks/useGraph.ts +0 -33
  31. package/src/hooks/useGraphAttributes.ts +0 -32
  32. package/src/hooks/useGraphContainer.spec.ts +0 -45
  33. package/src/hooks/useGraphContainer.ts +0 -10
  34. package/src/hooks/useGraphMap.ts +0 -7
  35. package/src/hooks/useGraphvizContext.spec.ts +0 -26
  36. package/src/hooks/useGraphvizContext.ts +0 -16
  37. package/src/hooks/useHasAttributes.ts +0 -16
  38. package/src/hooks/useHasComment.ts +0 -8
  39. package/src/hooks/useNode.spec.ts +0 -16
  40. package/src/hooks/useNode.ts +0 -29
  41. package/src/hooks/useRenderedID.spec.tsx +0 -28
  42. package/src/hooks/useRenderedID.ts +0 -6
  43. package/src/hooks/useSubgraph.spec.ts +0 -41
  44. package/src/hooks/useSubgraph.ts +0 -39
  45. package/src/react.ts +0 -16
  46. package/src/reconciler.ts +0 -272
  47. package/src/render.test.tsx +0 -71
  48. package/src/render.ts +0 -142
  49. package/src/renderHTMLLike.ts +0 -9
  50. package/src/renderId.ts +0 -9
  51. package/src/types.ts +0 -107
  52. package/tsconfig.json +0 -8
  53. package/typedoc.json +0 -5
  54. package/vite.config.ts +0 -31
package/README.md CHANGED
@@ -1,8 +1,41 @@
1
+ <div align="center">
2
+
3
+ [![Main](https://github.com/ts-graphviz/ts-graphviz/actions/workflows/main.yaml/badge.svg)](https://github.com/ts-graphviz/ts-graphviz/actions/workflows/main.yaml)
4
+ [![CodeQL](https://github.com/ts-graphviz/ts-graphviz/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/ts-graphviz/ts-graphviz/actions/workflows/codeql-analysis.yml)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/ts-graphviz/ts-graphviz/blob/main/LICENSE)
6
+ [![All Contributors](https://img.shields.io/github/all-contributors/ts-graphviz/ts-graphviz?color=orange)](#contributors-)
7
+
8
+ [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/8396/badge)](https://www.bestpractices.dev/projects/8396)
9
+ [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/ts-graphviz/ts-graphviz/badge)](https://scorecard.dev/viewer/?uri=github.com/ts-graphviz/ts-graphviz)
10
+ [![Tidelift](https://tidelift.com/badges/package/npm/ts-graphviz?style=flat)](https://tidelift.com/subscription/pkg/npm-ts-graphviz?utm_source=npm-ts-graphviz&utm_medium=readme)
11
+
12
+ [![npm version](https://badge.fury.io/js/ts-graphviz.svg)](https://badge.fury.io/js/ts-graphviz)
13
+ ![node version](https://img.shields.io/node/v/ts-graphviz)
14
+ [![deno version](https://img.shields.io/badge/deno-lts-black?logo=deno)](https://github.com/denoland/deno)
15
+ [![npm](https://img.shields.io/npm/dm/ts-graphviz)](https://npmtrends.com/ts-graphviz)
16
+
1
17
  # @ts-graphviz/react
2
18
 
3
- > It is part of the ts-graphviz library, which is split into modular packages to improve maintainability, flexibility, and ease of use.
19
+ React Components and Renderer for ts-graphviz
20
+ 🔗
21
+
22
+ [![GitHub](https://img.shields.io/badge/-GitHub-181717?logo=GitHub&style=flat)](https://github.com/ts-graphviz/ts-graphviz)
23
+ [![npm](https://img.shields.io/badge/-npm-CB3837?logo=npm&style=flat)](https://www.npmjs.com/package/ts-graphviz)
24
+ [![Reference](https://img.shields.io/badge/-API_Reference-3178C6?logo=TypeScript&style=flat&logoColor=fff)](https://ts-graphviz.github.io/ts-graphviz/)
25
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/ts-graphviz/ts-graphviz)
26
+
27
+ [![Sponsor](https://img.shields.io/badge/-GitHub%20Sponsor-fff?logo=GitHub%20Sponsors&style=flat)](https://github.com/sponsors/ts-graphviz)
28
+ [![OpenCollective](https://img.shields.io/badge/-OpenCollective-7FADF2?logo=opencollective&style=flat&logoColor=white)](https://opencollective.com/ts-graphviz)
29
+
30
+ [![format: Biome](https://img.shields.io/badge/format%20with-Biome-F7B911?logo=biome&style=flat)](https://biomejs.dev/)
31
+ [![test: Vitest](https://img.shields.io/badge/tested%20with-Vitest-6E9F18?logo=vitest&style=flat)](https://vitest.dev/)
32
+ [![build: Vite](https://img.shields.io/badge/build%20with-Vite-646CFF?logo=vite&style=flat)](https://rollupjs.org/)
33
+
34
+ </div>
4
35
 
5
- Graphviz-dot Renderer using React.
36
+ ---
37
+
38
+ > It is part of the ts-graphviz library, which is split into modular packages to improve maintainability, flexibility, and ease of use.
6
39
 
7
40
  ## Installation
8
41
 
@@ -12,93 +45,623 @@ The module can then be installed using [npm](https://www.npmjs.com/):
12
45
 
13
46
  ```bash
14
47
  # yarn
15
- $ yarn add @ts-graphviz/react react react-dom
48
+ $ yarn add @ts-graphviz/react react@^19
16
49
  # or npm
17
- $ npm install -S @ts-graphviz/react react react-dom
50
+ $ npm install -S @ts-graphviz/react react@^19
51
+ # or pnpm
52
+ $ pnpm add @ts-graphviz/react react@^19
53
+ ```
54
+
55
+ > **Important**: Install React 19+ as a peerDependency. React 18 and earlier versions are not supported. Note that `react-dom` is no longer required as this package now uses a custom HTML rendering implementation.
56
+
57
+ ## Key Features
58
+
59
+ ## React Component
60
+
61
+ ### Core Components
62
+
63
+ The package provides React components that map directly to Graphviz DOT language constructs:
64
+
65
+ #### Graph Containers
66
+ - **`<Digraph>`** - Creates a directed graph (arrows point from source to target)
67
+ - **`<Graph>`** - Creates an undirected graph (no arrow direction)
68
+ - **`<Subgraph>`** - Creates a subgraph cluster for grouping related nodes
69
+
70
+ #### Graph Elements
71
+ - **`<Node>`** - Creates a node/vertex with customizable attributes
72
+ - **`<Edge>`** - Creates an edge/connection between nodes with optional styling
73
+
74
+ ### Component Props & Attributes
75
+
76
+ All components accept Graphviz DOT attributes as props with full TypeScript support:
77
+
78
+ ```tsx
79
+ // Node with styling attributes
80
+ <Node
81
+ id="server1"
82
+ label="Web Server"
83
+ shape="box"
84
+ color="blue"
85
+ style="filled"
86
+ fillcolor="lightblue"
87
+ />
88
+
89
+ // Edge with custom styling
90
+ <Edge
91
+ targets={["server1", "database1"]}
92
+ label="API calls"
93
+ color="red"
94
+ style="dashed"
95
+ weight={2}
96
+ />
97
+
98
+ // Digraph with global attributes
99
+ <Digraph
100
+ rankdir="LR"
101
+ bgcolor="white"
102
+ node={{ shape: "ellipse", color: "gray" }}
103
+ edge={{ color: "black", arrowhead: "vee" }}
104
+ >
105
+ {/* nodes and edges */}
106
+ </Digraph>
107
+ ```
108
+
109
+ ### Nested Structure & Composition
110
+
111
+ Components can be freely nested to create complex graph structures:
112
+
113
+ ```tsx
114
+ // Define reusable components
115
+ const ServerNode = ({ id, name, type }) => (
116
+ <Node
117
+ id={id}
118
+ label={
119
+ <dot:table border="1" cellborder="0" cellspacing="0">
120
+ <dot:tr>
121
+ <dot:td bgcolor="lightgray">
122
+ <dot:b>{name}</dot:b>
123
+ </dot:td>
124
+ </dot:tr>
125
+ <dot:tr>
126
+ <dot:td>{type}</dot:td>
127
+ </dot:tr>
128
+ </dot:table>
129
+ }
130
+ shape="record"
131
+ />
132
+ );
133
+
134
+ const ServiceCluster = ({ id, label, children }) => (
135
+ <Subgraph id={id} label={label} style="filled" fillcolor="lightblue">
136
+ {children}
137
+ </Subgraph>
138
+ );
139
+
140
+ // Compose the architecture
141
+ const SystemArchitecture = () => (
142
+ <Digraph rankdir="TB">
143
+ <ServiceCluster id="cluster_frontend" label="Frontend Layer">
144
+ <ServerNode id="web1" name="Web Server" type="Nginx" />
145
+ <ServerNode id="app1" name="App Server" type="React" />
146
+ </ServiceCluster>
147
+
148
+ <ServiceCluster id="cluster_backend" label="Backend Layer">
149
+ <ServerNode id="api1" name="API Gateway" type="REST" />
150
+ <ServerNode id="auth1" name="Auth Service" type="OAuth" />
151
+ </ServiceCluster>
152
+
153
+ <ServiceCluster id="cluster_data" label="Data Layer">
154
+ <ServerNode id="db1" name="Database" type="PostgreSQL" />
155
+ <ServerNode id="cache1" name="Cache" type="Redis" />
156
+ </ServiceCluster>
157
+
158
+ <Edge targets={["web1", "app1"]} label="serves" />
159
+ <Edge targets={["app1", "api1"]} label="API calls" />
160
+ <Edge targets={["api1", "auth1"]} label="validates" />
161
+ <Edge targets={["api1", "db1"]} label="queries" />
162
+ <Edge targets={["api1", "cache1"]} label="caches" />
163
+ </Digraph>
164
+ );
18
165
  ```
19
166
 
20
- > Install [React and React DOM](https://github.com/facebook/react/) as peerDependencies at the same time.
167
+ ### HTML-Like Labels with JSX
21
168
 
22
- ## Example
169
+ Create rich, formatted labels using Graphviz's HTML-like label syntax with natural JSX:
23
170
 
24
- ```jsx
25
- import { Digraph, Node, Subgraph, Edge, renderToDot } from '@ts-graphviz/react';
171
+ ```tsx
172
+ // Reusable table component for data records
173
+ const DataRecord = ({ title, fields }) => (
174
+ <dot:table border="1" cellborder="0" cellspacing="0">
175
+ <dot:tr>
176
+ <dot:td bgcolor="lightblue" colspan="2">
177
+ <dot:b>{title}</dot:b>
178
+ </dot:td>
179
+ </dot:tr>
180
+ {fields.map(([key, value]) => (
181
+ <dot:tr key={key}>
182
+ <dot:td port={key}>{key}</dot:td>
183
+ <dot:td>{value}</dot:td>
184
+ </dot:tr>
185
+ ))}
186
+ </dot:table>
187
+ );
26
188
 
27
- const Example = () => (
28
- <Digraph
29
- rankdir="TB"
30
- edge={{
31
- color: 'blue',
32
- fontcolor: 'blue',
33
- }}
34
- node={{
35
- shape: 'none',
36
- }}
37
- >
189
+ // Status indicator component
190
+ const StatusIndicator = ({ status, message }) => (
191
+ <>
192
+ <dot:b>Status:</dot:b><dot:br/>
193
+ <dot:font color={status === 'active' ? 'green' : 'red'}>
194
+ <dot:b>{status.toUpperCase()}</dot:b>
195
+ </dot:font><dot:br/>
196
+ <dot:i>{message}</dot:i>
197
+ </>
198
+ );
199
+
200
+ // Usage in graph
201
+ const DatabaseDiagram = () => (
202
+ <Digraph>
38
203
  <Node
39
- id="nodeA"
40
- shape="none"
41
- label={
42
- <dot:table border="0" cellborder="1" cellspacing="0">
43
- <dot:tr>
44
- <dot:td>left</dot:td>
45
- <dot:td port="m">middle</dot:td>
46
- <dot:td port="r">right</dot:td>
47
- </dot:tr>
48
- </dot:table>
49
- }
204
+ id="user-table"
205
+ label={<DataRecord
206
+ title="Users"
207
+ fields={[['id', '1001'], ['name', 'John Doe'], ['email', 'john@example.com']]}
208
+ />}
209
+ shape="record"
50
210
  />
51
211
 
52
- <Subgraph id="cluster" label="Cluster" labeljust="l">
53
- <Node id="nodeB" label="This is label for nodeB." />
212
+ <Node
213
+ id="server-status"
214
+ label={<StatusIndicator status="active" message="All systems operational" />}
215
+ shape="box"
216
+ style="rounded,filled"
217
+ fillcolor="lightyellow"
218
+ />
219
+
220
+ <Edge targets={["user-table", "server-status"]} label="monitors" />
221
+ </Digraph>
222
+ );
223
+ ```
224
+
225
+ ### Available HTML-Like Elements
226
+
227
+ - `<dot:table>`, `<dot:tr>`, `<dot:td>` - Table structures
228
+ - `<dot:b>`, `<dot:i>`, `<dot:u>` - Text formatting (bold, italic, underline)
229
+ - `<dot:font>` - Font styling with color, face, point-size
230
+ - `<dot:br>` - Line breaks
231
+ - `<dot:hr>`, `<dot:vr>` - Horizontal/vertical rules
232
+ - `<dot:img>` - Images
233
+ - `<dot:s>`, `<dot:sub>`, `<dot:sup>`, `<dot:o>` - Advanced text formatting
234
+
235
+ ### TypeScript Integration
236
+
237
+ Full type safety with IntelliSense support for all Graphviz attributes:
238
+
239
+ ```tsx
240
+ import type { NodeProps, EdgeProps, DigraphProps } from '@ts-graphviz/react';
241
+
242
+ // Typed component props
243
+ const MyNode: React.FC<NodeProps> = (props) => (
244
+ <Node
245
+ shape="box" // ✅ TypeScript knows valid shapes
246
+ color="blue" // ✅ String colors supported
247
+ style="filled" // ✅ Valid style options
248
+ {...props}
249
+ />
250
+ );
251
+
252
+ // Edge with typed targets
253
+ <Edge
254
+ targets={["node1", "node2"]} // ✅ Tuple type enforced
255
+ arrowhead="diamond" // ✅ Valid arrowhead styles
256
+ />
257
+ ```
258
+
259
+ #### Enhanced Type Safety
260
+
261
+ The package provides sophisticated TypeScript support with automatic type inference and runtime type filtering:
262
+
263
+ ```tsx
264
+ // ✅ Automatic type inference - no casting needed
265
+ const root = createRoot();
266
+ await root.render(
267
+ <Digraph id="myGraph" rankdir="LR">
268
+ <Node id="A" shape="box" />
269
+ <Node id="B" shape="circle" />
270
+ <Edge targets={["A", "B"]} />
271
+ </Digraph>
272
+ );
273
+
274
+ // ✅ Type-safe model access
275
+ const models = root.getTopLevelModels();
276
+ // models is automatically typed as DotObjectModel[]
277
+
278
+ // ✅ Runtime type filtering with built-in type guards
279
+ import { isNodeModel, isEdgeModel, isRootGraphModel } from '@ts-graphviz/common';
280
+
281
+ // Filter by model type with automatic type narrowing
282
+ const nodes = root.getTopLevelModels(isNodeModel);
283
+ nodes.forEach(node => console.log(node.id)); // TypeScript knows this is NodeModel
284
+
285
+ const edges = root.getTopLevelModels(isEdgeModel);
286
+ edges.forEach(edge => console.log(edge.targets)); // TypeScript knows this is EdgeModel
287
+
288
+ const graphs = root.getTopLevelModels(isRootGraphModel);
289
+ graphs.forEach(graph => console.log(graph.directed)); // TypeScript knows this is RootGraphModel
290
+
291
+ // ✅ Direct type casting (trusted user assertion)
292
+ // When you know the exact types, you can cast directly without runtime validation
293
+ const trustedNodes = root.getTopLevelModels<NodeModel>();
294
+ trustedNodes.forEach(node => console.log(node.id)); // TypeScript trusts your assertion
295
+
296
+ const trustedEdges = root.getTopLevelModels<EdgeModel>();
297
+ trustedEdges.forEach(edge => console.log(edge.targets)); // No runtime type checking
298
+
299
+ // ✅ Advanced model type checking
300
+ const allModels = root.getTopLevelModels();
301
+ for (const model of allModels) {
302
+ if (isNodeModel(model)) {
303
+ console.log(`Node: ${model.id}`);
304
+ } else if (isEdgeModel(model)) {
305
+ console.log(`Edge: ${model.targets.map(t => t.id).join(' -> ')}`);
306
+ } else if (isRootGraphModel(model)) {
307
+ console.log(`Graph: ${model.id} (directed: ${model.directed})`);
308
+ }
309
+ }
310
+ ```
311
+
312
+ #### Container Mode Type Safety
313
+
314
+ When using container mode, you get access to all rendered models with full type safety:
315
+
316
+ ```tsx
317
+ import { digraph } from 'ts-graphviz';
318
+ import { isNodeModel, isEdgeModel, isSubgraphModel } from '@ts-graphviz/react';
319
+
320
+ const container = digraph('myContainer');
321
+ const root = createRoot({ container });
322
+
323
+ await root.render(
324
+ <>
325
+ <Node id="node1" />
326
+ <Node id="node2" />
327
+ <Edge targets={['node1', 'node2']} />
328
+ <Subgraph id="cluster1">
329
+ <Node id="node3" />
54
330
  </Subgraph>
55
- <Edge targets={['nodeB', 'nodeA:m']} comment="Edge from node A to B" label={<dot:b>A to B</dot:b>} />
331
+ </>
332
+ );
333
+
334
+ // Container mode: access all non-container models with type safety
335
+
336
+ // Runtime type filtering (safe, validates at runtime)
337
+ const allNodes = root.getTopLevelModels(isNodeModel); // NodeModel[]
338
+ const allEdges = root.getTopLevelModels(isEdgeModel); // EdgeModel[]
339
+ const subgraphs = root.getTopLevelModels(isSubgraphModel); // SubgraphModel[]
340
+
341
+ // Direct type casting (user knows the types, no runtime validation)
342
+ const trustedNodes = root.getTopLevelModels<NodeModel>(); // All models cast as NodeModel[]
343
+ const trustedEdges = root.getTopLevelModels<EdgeModel>(); // All models cast as EdgeModel[]
344
+
345
+ // Type-safe operations
346
+ allNodes.forEach(node => {
347
+ node.attributes.set('color', 'blue'); // TypeScript knows node attributes
348
+ });
349
+
350
+ allEdges.forEach(edge => {
351
+ console.log(`Edge from ${edge.targets[0]} to ${edge.targets[1]}`);
352
+ });
353
+ ```
354
+
355
+ ## Core Functions
356
+ The package provides clean async rendering APIs:
357
+
358
+ - `createRoot()` - Creates a rendering root following React 19's createRoot pattern
359
+ - `renderToDot()` - Primary async function for converting React components to DOT language strings
360
+ - `renderHTMLLike()` - Renders HTML-like label structures for use in node or edge labels
361
+
362
+ All rendering functions are **async-only** and provide a clean, consistent API surface. The new `createRoot()` API follows React 19's modern patterns for better performance and error handling.
363
+
364
+ ## Usage Examples
365
+
366
+ ### Creating a Simple Graph
367
+
368
+ ```tsx
369
+ import { Digraph, Node, Edge, createRoot, renderToDot } from "@ts-graphviz/react";
370
+
371
+ // Define a reusable process component
372
+ const ProcessNode = ({ id, label, color = "lightblue" }) => (
373
+ <Node
374
+ id={id}
375
+ label={
376
+ <dot:table border="0" cellborder="1" cellspacing="0">
377
+ <dot:tr>
378
+ <dot:td bgcolor={color}>
379
+ <dot:b>{label}</dot:b>
380
+ </dot:td>
381
+ </dot:tr>
382
+ </dot:table>
383
+ }
384
+ shape="record"
385
+ />
386
+ );
387
+
388
+ // Create a workflow diagram
389
+ const WorkflowDiagram = () => (
390
+ <Digraph rankdir="LR">
391
+ <ProcessNode id="start" label="Start" color="lightgreen" />
392
+ <ProcessNode id="process" label="Process Data" />
393
+ <ProcessNode id="validate" label="Validate" />
394
+ <ProcessNode id="end" label="End" color="lightcoral" />
395
+
396
+ <Edge targets={["start", "process"]} />
397
+ <Edge targets={["process", "validate"]} />
398
+ <Edge targets={["validate", "end"]} />
399
+ </Digraph>
400
+ );
401
+
402
+ // Create root and render to graph models
403
+ const root = createRoot();
404
+ await root.render(<WorkflowDiagram />);
405
+ const models = root.getTopLevelModels();
406
+
407
+ // Convert to DOT string
408
+ const dotString = await renderToDot(<WorkflowDiagram />);
409
+ ```
410
+
411
+ ### Using HTML-Like Labels
412
+
413
+ ```tsx
414
+ import { Digraph, Node, Edge, renderToDot } from "@ts-graphviz/react";
415
+
416
+ // Reusable card component with HTML-like labels
417
+ const InfoCard = ({ id, title, items }) => (
418
+ <Node
419
+ id={id}
420
+ label={
421
+ <dot:table border="1" cellborder="0" cellspacing="0">
422
+ <dot:tr>
423
+ <dot:td bgcolor="navy">
424
+ <dot:font color="white">
425
+ <dot:b>{title}</dot:b>
426
+ </dot:font>
427
+ </dot:td>
428
+ </dot:tr>
429
+ {items.map((item, index) => (
430
+ <dot:tr key={index}>
431
+ <dot:td align="left">• {item}</dot:td>
432
+ </dot:tr>
433
+ ))}
434
+ </dot:table>
435
+ }
436
+ shape="record"
437
+ />
438
+ );
439
+
440
+ // Usage in graph
441
+ const ProjectDiagram = () => (
442
+ <Digraph>
443
+ <InfoCard
444
+ id="requirements"
445
+ title="Requirements"
446
+ items={["User login", "Data processing", "Reporting"]}
447
+ />
448
+ <InfoCard
449
+ id="implementation"
450
+ title="Implementation"
451
+ items={["React frontend", "Node.js API", "PostgreSQL DB"]}
452
+ />
453
+
454
+ <Edge targets={["requirements", "implementation"]} label="leads to" />
56
455
  </Digraph>
57
456
  );
58
457
 
59
- const dot = renderToDot(<Example />);
458
+ const dotString = await renderToDot(<ProjectDiagram />);
459
+ ```
460
+
461
+ ### Using the renderHTMLLike Function
462
+
463
+ ```ts
464
+ import { renderHTMLLike } from "@ts-graphviz/react";
465
+
466
+ const htmlLabel = renderHTMLLike(
467
+ <dot:table>
468
+ <dot:tr>
469
+ <dot:td>left</dot:td>
470
+ <dot:td>right</dot:td>
471
+ </dot:tr>
472
+ </dot:table>
473
+ );
474
+ ```
475
+
476
+ ### Advanced Root Options
477
+
478
+ ```ts
479
+ import { Digraph, Node, Edge, createRoot } from "@ts-graphviz/react";
480
+
481
+ // Basic usage
482
+ const root = createRoot();
483
+ await root.render(
484
+ <Digraph>
485
+ <Node id="A" />
486
+ <Node id="B" />
487
+ <Edge targets={["A", "B"]} />
488
+ </Digraph>
489
+ );
60
490
 
61
- console.log(dot);
491
+ // Container mode - render into existing graph
492
+ import { digraph } from 'ts-graphviz';
493
+ const container = digraph('MyGraph');
494
+ const containerRoot = createRoot({ container });
495
+ await containerRoot.render(
496
+ <>
497
+ <Node id="A" />
498
+ <Node id="B" />
499
+ <Edge targets={["A", "B"]} />
500
+ </>
501
+ );
502
+
503
+ // Error handling options
504
+ const rootWithErrorHandling = createRoot({
505
+ onUncaughtError: (error, errorInfo) => {
506
+ console.error('Rendering error:', error);
507
+ console.log('Component stack:', errorInfo.componentStack);
508
+ },
509
+ onCaughtError: (error, errorInfo) => {
510
+ console.error('Caught error:', error);
511
+ }
512
+ });
513
+
514
+ await rootWithErrorHandling.render(<MyComplexGraph />);
62
515
  ```
63
516
 
64
- ### Output dot
65
-
66
- ```dot
67
- digraph {
68
- rankdir = "TB";
69
- edge [
70
- color = "blue";
71
- fontcolor = "blue";
72
- ];
73
- node [
74
- shape = "none";
75
- ];
76
- "nodeA" [
77
- shape = "none";
78
- label = <<table border="0" cellborder="1" cellSpacing="0"><tr><td>left</td><td port="m">middle</td><td port="r">right</td></tr></table>>;
79
- ];
80
- subgraph "cluster" {
81
- labeljust = "l";
82
- label = "Cluster";
83
- "nodeB" [
84
- label = "This is label for nodeB.";
85
- ];
517
+ ## Advanced Features
518
+
519
+ ### Error Handling
520
+
521
+ The package provides robust error handling capabilities for rendering errors:
522
+
523
+ ```ts
524
+ import { createRoot, renderToDot } from "@ts-graphviz/react";
525
+
526
+ // Error handling with createRoot
527
+ const root = createRoot({
528
+ onUncaughtError: (error, errorInfo) => {
529
+ console.error('Uncaught rendering error:', error.message);
530
+ console.log('Component stack:', errorInfo.componentStack);
531
+ // Send to error tracking service
532
+ errorTracker.captureException(error, { extra: errorInfo });
533
+ },
534
+ onCaughtError: (error, errorInfo) => {
535
+ console.error('Caught by error boundary:', error.message);
536
+ // Handle recoverable errors
86
537
  }
87
- // Edge from node A to B
88
- "nodeB" -> "nodeA":"m" [
89
- label = <<b>A to B</b>>;
90
- ];
538
+ });
539
+
540
+ await root.render(<MyGraph />);
541
+
542
+ // renderToDot also supports error handling
543
+ try {
544
+ const dotString = await renderToDot(<ComplexGraph />);
545
+ } catch (error) {
546
+ if (error.message.includes('Multiple top-level graphs')) {
547
+ console.error('Invalid graph structure');
548
+ }
549
+ }
550
+ ```
551
+
552
+ ### Ref as Prop Support
553
+
554
+ The package supports using `ref` to access and manipulate graph models directly, allowing for dynamic updates and interactions:
555
+
556
+ ```ts
557
+ import { useRef } from 'react';
558
+ import { Digraph, Graph, Node, Edge, createRoot } from "@ts-graphviz/react";
559
+ import type { NodeModel, EdgeModel, GraphBaseModel } from 'ts-graphviz';
560
+
561
+ function MyGraphComponent() {
562
+ const nodeRef = useRef<NodeModel>(null);
563
+ const edgeRef = useRef<EdgeModel>(null);
564
+ const digraphRef = useRef<GraphBaseModel>(null);
565
+ const graphRef = useRef<GraphBaseModel>(null);
566
+
567
+ const handleRender = async () => {
568
+ // Example with Digraph component
569
+ const digraphRoot = createRoot();
570
+ await digraphRoot.render(
571
+ <Digraph id="mygraph" ref={digraphRef}>
572
+ <Node id="A" ref={nodeRef} label="Node A" />
573
+ <Node id="B" label="Node B" />
574
+ <Edge targets={['A', 'B']} ref={edgeRef} label="A to B" />
575
+ </Digraph>
576
+ );
577
+
578
+ // Example with Graph component (undirected)
579
+ const graphRoot = createRoot();
580
+ await graphRoot.render(
581
+ <Graph id="undirected-graph" ref={graphRef}>
582
+ <Node id="X" label="Node X" />
583
+ <Node id="Y" label="Node Y" />
584
+ <Edge targets={['X', 'Y']} label="X -- Y" />
585
+ </Graph>
586
+ );
587
+
588
+ // Access and manipulate the models directly
589
+ if (nodeRef.current) {
590
+ nodeRef.current.attributes.set('color', 'red');
591
+ nodeRef.current.comment = 'Modified via ref';
592
+ }
593
+
594
+ if (edgeRef.current) {
595
+ edgeRef.current.attributes.set('style', 'dashed');
596
+ }
597
+
598
+ console.log('Digraph nodes:', digraphRef.current?.nodes.length);
599
+ console.log('Digraph edges:', digraphRef.current?.edges.length);
600
+ console.log('Graph nodes:', graphRef.current?.nodes.length);
601
+ console.log('Graph edges:', graphRef.current?.edges.length);
602
+ };
603
+
604
+ return (
605
+ <button onClick={handleRender}>
606
+ Render Graph
607
+ </button>
608
+ );
91
609
  }
92
610
  ```
93
611
 
94
- ![dot](./example/example.svg)
612
+ ## Contributors 👥
613
+
614
+ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
615
+
616
+ <!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
617
+ <!-- prettier-ignore-start -->
618
+ <!-- markdownlint-disable -->
619
+ <table>
620
+ <tbody>
621
+ <tr>
622
+ <td align="center" valign="top" width="14.28%"><a href="http://blog.kamiazya.tech/"><img src="https://avatars0.githubusercontent.com/u/35218186?v=4?s=100" width="100px;" alt="Yuki Yamazaki"/><br /><sub><b>Yuki Yamazaki</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/commits?author=kamiazya" title="Code">💻</a> <a href="https://github.com/ts-graphviz/ts-graphviz/commits?author=kamiazya" title="Tests">⚠️</a> <a href="https://github.com/ts-graphviz/ts-graphviz/commits?author=kamiazya" title="Documentation">📖</a> <a href="#ideas-kamiazya" title="Ideas, Planning, & Feedback">🤔</a></td>
623
+ <td align="center" valign="top" width="14.28%"><a href="https://laysent.com"><img src="https://avatars2.githubusercontent.com/u/1191606?v=4?s=100" width="100px;" alt="LaySent"/><br /><sub><b>LaySent</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/issues?q=author%3Alaysent" title="Bug reports">🐛</a> <a href="https://github.com/ts-graphviz/ts-graphviz/commits?author=laysent" title="Tests">⚠️</a></td>
624
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/elasticdotventures"><img src="https://avatars0.githubusercontent.com/u/35611074?v=4?s=100" width="100px;" alt="elasticdotventures"/><br /><sub><b>elasticdotventures</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/commits?author=elasticdotventures" title="Documentation">📖</a></td>
625
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/ChristianMurphy"><img src="https://avatars.githubusercontent.com/u/3107513?v=4?s=100" width="100px;" alt="Christian Murphy"/><br /><sub><b>Christian Murphy</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/commits?author=ChristianMurphy" title="Code">💻</a> <a href="#ideas-ChristianMurphy" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/ts-graphviz/ts-graphviz/commits?author=ChristianMurphy" title="Documentation">📖</a></td>
626
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/ArtemAdamenko"><img src="https://avatars.githubusercontent.com/u/2178516?v=4?s=100" width="100px;" alt="Artem"/><br /><sub><b>Artem</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/issues?q=author%3AArtemAdamenko" title="Bug reports">🐛</a></td>
627
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/fredericohpandolfo"><img src="https://avatars.githubusercontent.com/u/24229136?v=4?s=100" width="100px;" alt="fredericohpandolfo"/><br /><sub><b>fredericohpandolfo</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/issues?q=author%3Afredericohpandolfo" title="Bug reports">🐛</a></td>
628
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/diegoquinteiro"><img src="https://avatars.githubusercontent.com/u/1878108?v=4?s=100" width="100px;" alt="diegoquinteiro"/><br /><sub><b>diegoquinteiro</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/issues?q=author%3Adiegoquinteiro" title="Bug reports">🐛</a></td>
629
+ </tr>
630
+ <tr>
631
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/robross0606"><img src="https://avatars.githubusercontent.com/u/2965467?v=4?s=100" width="100px;" alt="robross0606"/><br /><sub><b>robross0606</b></sub></a><br /><a href="#ideas-robross0606" title="Ideas, Planning, & Feedback">🤔</a></td>
632
+ <td align="center" valign="top" width="14.28%"><a href="https://blake-regalia.net"><img src="https://avatars.githubusercontent.com/u/1456400?v=4?s=100" width="100px;" alt="Blake Regalia"/><br /><sub><b>Blake Regalia</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/issues?q=author%3Ablake-regalia" title="Bug reports">🐛</a></td>
633
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/bigbug"><img src="https://avatars.githubusercontent.com/u/27259?v=4?s=100" width="100px;" alt="bigbug"/><br /><sub><b>bigbug</b></sub></a><br /><a href="#question-bigbug" title="Answering Questions">💬</a></td>
634
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/murawakimitsuhiro"><img src="https://avatars.githubusercontent.com/u/13833242?v=4?s=100" width="100px;" alt="mrwk"/><br /><sub><b>mrwk</b></sub></a><br /><a href="#question-murawakimitsuhiro" title="Answering Questions">💬</a></td>
635
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/svdvonde"><img src="https://avatars.githubusercontent.com/u/2751783?v=4?s=100" width="100px;" alt="svdvonde"/><br /><sub><b>svdvonde</b></sub></a><br /><a href="#question-svdvonde" title="Answering Questions">💬</a></td>
636
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/seethroughdev"><img src="https://avatars.githubusercontent.com/u/203779?v=4?s=100" width="100px;" alt="Adam"/><br /><sub><b>Adam</b></sub></a><br /><a href="#question-seethroughdev" title="Answering Questions">💬</a></td>
637
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/trevor-scheer"><img src="https://avatars.githubusercontent.com/u/29644393?v=4?s=100" width="100px;" alt="Trevor Scheer"/><br /><sub><b>Trevor Scheer</b></sub></a><br /><a href="#a11y-trevor-scheer" title="Accessibility">️️️️♿️</a></td>
638
+ </tr>
639
+ <tr>
640
+ <td align="center" valign="top" width="14.28%"><a href="https://pre.ms"><img src="https://avatars.githubusercontent.com/u/238277?v=4?s=100" width="100px;" alt="Prem Pillai"/><br /><sub><b>Prem Pillai</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/issues?q=author%3Acloud-on-prem" title="Bug reports">🐛</a></td>
641
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/nagasawaryoya"><img src="https://avatars.githubusercontent.com/u/53528726?v=4?s=100" width="100px;" alt="nagasawaryoya"/><br /><sub><b>nagasawaryoya</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/commits?author=nagasawaryoya" title="Code">💻</a> <a href="https://github.com/ts-graphviz/ts-graphviz/commits?author=nagasawaryoya" title="Tests">⚠️</a></td>
642
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/tokidrill"><img src="https://avatars.githubusercontent.com/u/42460318?v=4?s=100" width="100px;" alt="YukiSasaki"/><br /><sub><b>YukiSasaki</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/commits?author=tokidrill" title="Code">💻</a> <a href="https://github.com/ts-graphviz/ts-graphviz/commits?author=tokidrill" title="Tests">⚠️</a></td>
643
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/Madd0g"><img src="https://avatars.githubusercontent.com/u/1171003?v=4?s=100" width="100px;" alt="Madd0g"/><br /><sub><b>Madd0g</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/issues?q=author%3AMadd0g" title="Bug reports">🐛</a></td>
644
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/j4k0xb"><img src="https://avatars.githubusercontent.com/u/55899582?v=4?s=100" width="100px;" alt="j4k0xb"/><br /><sub><b>j4k0xb</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/issues?q=author%3Aj4k0xb" title="Bug reports">🐛</a></td>
645
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/haved"><img src="https://avatars.githubusercontent.com/u/3748845?v=4?s=100" width="100px;" alt="HKrogstie"/><br /><sub><b>HKrogstie</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/issues?q=author%3Ahaved" title="Bug reports">🐛</a></td>
646
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/septatrix"><img src="https://avatars.githubusercontent.com/u/24257556?v=4?s=100" width="100px;" alt="Nils K"/><br /><sub><b>Nils K</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/issues?q=author%3Aseptatrix" title="Bug reports">🐛</a></td>
647
+ </tr>
648
+ <tr>
649
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/hao2013"><img src="https://avatars.githubusercontent.com/u/67059492?v=4?s=100" width="100px;" alt="hao2013"/><br /><sub><b>hao2013</b></sub></a><br /><a href="#maintenance-hao2013" title="Maintenance">🚧</a> <a href="https://github.com/ts-graphviz/ts-graphviz/pulls?q=is%3Apr+reviewed-by%3Ahao2013" title="Reviewed Pull Requests">👀</a></td>
650
+ <td align="center" valign="top" width="14.28%"><a href="http://www.walterra.dev"><img src="https://avatars.githubusercontent.com/u/230104?v=4?s=100" width="100px;" alt="Walter Rafelsberger"/><br /><sub><b>Walter Rafelsberger</b></sub></a><br /><a href="#question-walterra" title="Answering Questions">💬</a></td>
651
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/grsjst"><img src="https://avatars.githubusercontent.com/u/4739018?v=4?s=100" width="100px;" alt="grsjst"/><br /><sub><b>grsjst</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/issues?q=author%3Agrsjst" title="Bug reports">🐛</a></td>
652
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/stephenirven"><img src="https://avatars.githubusercontent.com/u/4293560?v=4?s=100" width="100px;" alt="Steve"/><br /><sub><b>Steve</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/issues?q=author%3Astephenirven" title="Bug reports">🐛</a></td>
653
+ </tr>
654
+ </tbody>
655
+ </table>
95
656
 
96
- ## Contributing
657
+ <!-- markdownlint-restore -->
658
+ <!-- prettier-ignore-end -->
97
659
 
98
- Contributions to the ts-graphviz project are welcome.
660
+ <!-- ALL-CONTRIBUTORS-LIST:END -->
99
661
 
100
- Please refer to the main ts-graphviz repository for guidelines on how to contribute.
662
+ This project follows the [all-contributors](https://github.com/all-contributors/all-contributors)
663
+ specification. Contributions of any kind welcome!
101
664
 
102
- ## License
665
+ ## License ⚖️
103
666
 
104
- This software is released under the MIT License.
667
+ This software is released under the MIT License, see [LICENSE](https://github.com/ts-graphviz/ts-graphviz/blob/main/LICENSE).