@ts-graphviz/react 0.10.6 → 0.11.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +497 -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/lib/react.js CHANGED
@@ -1,45 +1,31 @@
1
- import { createContext, useContext, useEffect, useMemo, isValidElement, createElement } from "react";
2
- import { Digraph as Digraph$1, Graph as Graph$1, Subgraph as Subgraph$1, toDot } from "ts-graphviz";
1
+ import { Digraph as Digraph$1, Graph as Graph$1, Subgraph as Subgraph$1, isRootGraphModel, toDot } from "ts-graphviz";
2
+ import { isAttributeListModel, isEdgeModel, isNodeModel, isRootGraphModel as isRootGraphModel2, isSubgraphModel } from "ts-graphviz";
3
3
  import { jsx } from "react/jsx-runtime";
4
- import { renderToStaticMarkup } from "react-dom/server";
4
+ import { createContext, useLayoutEffect, useContext, useMemo, useEffect, isValidElement, useImperativeHandle, createElement } from "react";
5
5
  import ReactReconciler from "react-reconciler";
6
- const GraphContainer = createContext(null);
7
- GraphContainer.displayName = "GraphContainer";
8
- function useGraphContainer() {
9
- return useContext(GraphContainer);
10
- }
11
6
  const CurrentGraph = createContext(null);
12
7
  CurrentGraph.displayName = "CurrentGraph";
13
- function useCurrentGraph() {
14
- const cluster = useContext(CurrentGraph);
15
- if (cluster === null) {
16
- throw Error(
17
- "useCurrentGraph must be called within a cluster such as Digraph, Graph, Subgraph."
18
- );
19
- }
20
- return cluster;
21
- }
8
+ const GraphContainer = createContext(null);
9
+ GraphContainer.displayName = "GraphContainer";
22
10
  function useGraphAttributes(cluster, attributes, { edge, node, graph }) {
23
- useEffect(() => {
11
+ useLayoutEffect(() => {
24
12
  cluster.clear();
25
13
  cluster.apply(attributes);
26
14
  }, [cluster, attributes]);
27
- useEffect(() => {
15
+ useLayoutEffect(() => {
28
16
  cluster.attributes.node.clear();
29
17
  cluster.attributes.node.apply(node ?? {});
30
18
  }, [cluster, node]);
31
- useEffect(() => {
19
+ useLayoutEffect(() => {
32
20
  cluster.attributes.edge.clear();
33
21
  cluster.attributes.edge.apply(edge ?? {});
34
22
  }, [cluster, edge]);
35
- useEffect(() => {
23
+ useLayoutEffect(() => {
36
24
  cluster.attributes.graph.clear();
37
25
  cluster.attributes.graph.apply(graph ?? {});
38
26
  }, [cluster, graph]);
39
27
  }
40
- const GraphvizContext = createContext(
41
- null
42
- );
28
+ const GraphvizContext = createContext(null);
43
29
  GraphvizContext.displayName = "GraphvizContext";
44
30
  function useGraphvizContext() {
45
31
  const context = useContext(
@@ -53,7 +39,7 @@ function useGraphvizContext() {
53
39
  return context;
54
40
  }
55
41
  function useHasComment(target, comment) {
56
- useEffect(() => {
42
+ useLayoutEffect(() => {
57
43
  target.comment = comment;
58
44
  }, [target, comment]);
59
45
  }
@@ -63,13 +49,8 @@ function useDigraph(options = {}) {
63
49
  const digraph = useMemo(() => {
64
50
  const g = new Digraph$1(id);
65
51
  context.container = g;
66
- g.comment = comment;
67
- g.apply(attributes);
68
- g.attributes.node.apply(node ?? {});
69
- g.attributes.edge.apply(edge ?? {});
70
- g.attributes.graph.apply(graph ?? {});
71
52
  return g;
72
- }, [context, id, comment, edge, node, graph, attributes]);
53
+ }, [context, id]);
73
54
  useHasComment(digraph, comment);
74
55
  useGraphAttributes(digraph, attributes, { edge, node, graph });
75
56
  useEffect(() => {
@@ -79,57 +60,174 @@ function useDigraph(options = {}) {
79
60
  }, [context]);
80
61
  return digraph;
81
62
  }
82
- function useGraph(options = {}) {
83
- const { id, comment, edge, node, graph, ...attributes } = options;
84
- const context = useGraphvizContext();
85
- const memoGraph = useMemo(() => {
86
- const g = new Graph$1(id);
87
- context.container = g;
88
- g.comment = comment;
89
- g.apply(attributes);
90
- g.attributes.node.apply(node ?? {});
91
- g.attributes.edge.apply(edge ?? {});
92
- g.attributes.graph.apply(graph ?? {});
93
- return g;
94
- }, [context, id, comment, edge, node, graph, attributes]);
95
- useHasComment(memoGraph, comment);
96
- useGraphAttributes(memoGraph, attributes, { edge, node, graph });
97
- useEffect(() => {
98
- return () => {
99
- context.container = void 0;
100
- };
101
- }, [context]);
102
- return memoGraph;
63
+ function useGraphContainer() {
64
+ return useContext(GraphContainer);
103
65
  }
104
- function useSubgraph(props = {}) {
105
- const { id, comment, edge, node, graph, ...attributes } = props;
106
- const context = useGraphvizContext();
107
- const cluster = useCurrentGraph();
108
- const subgraph = useMemo(() => {
109
- const g = new Subgraph$1(id);
110
- if (cluster !== null) {
111
- cluster.addSubgraph(g);
112
- } else if (!context.container) {
113
- context.container = g;
66
+ const GraphMap = createContext(
67
+ null
68
+ );
69
+ GraphMap.displayName = "GraphMap";
70
+ function useGraphMap() {
71
+ const graphMap = useContext(GraphMap);
72
+ if (graphMap === null) {
73
+ throw new Error(
74
+ "useGraphMap must be called within a GraphMap provider. This usually means you are calling it outside of a Graphviz root context."
75
+ );
76
+ }
77
+ return graphMap;
78
+ }
79
+ const ModelCollectorContext = createContext(null);
80
+ ModelCollectorContext.displayName = "ModelCollectorContext";
81
+ function useModelCollector() {
82
+ return useContext(ModelCollectorContext);
83
+ }
84
+ const DOT_ELEMENT_MAP = {
85
+ "dot:table": "table",
86
+ "dot:tr": "tr",
87
+ "dot:td": "td",
88
+ "dot:font": "font",
89
+ "dot:br": "br",
90
+ "dot:img": "img",
91
+ "dot:i": "i",
92
+ "dot:b": "b",
93
+ "dot:u": "u",
94
+ "dot:o": "o",
95
+ "dot:sub": "sub",
96
+ "dot:sup": "sup",
97
+ "dot:s": "s",
98
+ "dot:hr": "hr",
99
+ "dot:vr": "vr"
100
+ };
101
+ function manuallyRenderElement(element) {
102
+ const { type, props } = element;
103
+ if (typeof type === "function") {
104
+ try {
105
+ const result = typeof type === "function" && type.prototype?.render ? new type(props).render() : type(props);
106
+ if (result && typeof result === "object" && result.type) {
107
+ return manuallyRenderElement(result);
108
+ }
109
+ } catch {
114
110
  }
115
- g.comment = comment;
116
- g.apply(attributes);
117
- g.attributes.node.apply(node ?? {});
118
- g.attributes.edge.apply(edge ?? {});
119
- g.attributes.graph.apply(graph ?? {});
120
- return g;
121
- }, [context, cluster, id, comment, edge, node, graph, attributes]);
122
- useHasComment(subgraph, comment);
123
- useGraphAttributes(subgraph, attributes, { edge, node, graph });
111
+ return "";
112
+ }
113
+ if (typeof type === "string") {
114
+ let tagName = type;
115
+ if (tagName === "dot:port") {
116
+ const propsWithChildren2 = props;
117
+ const childrenString2 = renderChildrenToString(
118
+ propsWithChildren2?.children
119
+ );
120
+ return `<${childrenString2}>`;
121
+ }
122
+ if (tagName.startsWith("dot:")) {
123
+ const htmlElement = DOT_ELEMENT_MAP[tagName];
124
+ if (htmlElement) {
125
+ tagName = htmlElement;
126
+ }
127
+ }
128
+ const attributes = [];
129
+ const propsWithChildren = props;
130
+ Object.entries(propsWithChildren || {}).forEach(([key, value]) => {
131
+ if (key !== "children" && value != null) {
132
+ if (typeof value === "string" || typeof value === "number") {
133
+ attributes.push(`${key}="${String(value)}"`);
134
+ } else if (typeof value === "boolean" && value) {
135
+ attributes.push(key);
136
+ }
137
+ }
138
+ });
139
+ const attrsString = attributes.length > 0 ? ` ${attributes.join(" ")}` : "";
140
+ const childrenString = renderChildrenToString(propsWithChildren?.children);
141
+ if (childrenString) {
142
+ return `<${tagName}${attrsString}>${childrenString}</${tagName}>`;
143
+ }
144
+ return `<${tagName}${attrsString}></${tagName}>`;
145
+ }
146
+ return "";
147
+ }
148
+ function renderChildrenToString(children) {
149
+ if (children == null || typeof children === "boolean") {
150
+ return "";
151
+ }
152
+ if (typeof children === "string" || typeof children === "number") {
153
+ return String(children);
154
+ }
155
+ if (Array.isArray(children)) {
156
+ return children.map(renderChildrenToString).join("");
157
+ }
158
+ if (typeof children === "object" && children !== null && "type" in children) {
159
+ return manuallyRenderElement(children);
160
+ }
161
+ return "";
162
+ }
163
+ function renderHTMLLike(label) {
164
+ if (!label) {
165
+ return "<>";
166
+ }
167
+ try {
168
+ const markup = manuallyRenderElement(label);
169
+ if (!markup) {
170
+ return "<>";
171
+ }
172
+ return `<${markup}>`;
173
+ } catch (error) {
174
+ console.warn(
175
+ "renderHTMLLike fallback:",
176
+ error instanceof Error ? error.message : error
177
+ );
178
+ return "<>";
179
+ }
180
+ }
181
+ function renderId(id) {
182
+ if (isValidElement(id)) {
183
+ return renderHTMLLike(id);
184
+ }
185
+ return id;
186
+ }
187
+ function useRenderedID(id) {
188
+ return useMemo(() => renderId(id), [id]);
189
+ }
190
+ const Digraph = ({
191
+ children,
192
+ label,
193
+ ref,
194
+ ...options
195
+ }) => {
196
+ const container = useGraphContainer();
197
+ if (container !== null) {
198
+ throw Error(
199
+ "RootCluster is duplicated.\nUse only one of Digraph and Graph."
200
+ );
201
+ }
202
+ const renderedLabel = useRenderedID(label);
203
+ if (renderedLabel !== void 0)
204
+ Object.assign(options, { label: renderedLabel });
205
+ const digraph = useDigraph(options);
206
+ const clusters = useGraphMap();
207
+ const modelCollector = useModelCollector();
208
+ useImperativeHandle(ref, () => digraph, [digraph]);
209
+ useLayoutEffect(() => {
210
+ modelCollector?.collectModel(digraph);
211
+ }, [modelCollector, digraph]);
124
212
  useEffect(() => {
125
- return () => {
126
- cluster.removeSubgraph(subgraph);
127
- };
128
- }, [cluster, subgraph]);
129
- return subgraph;
213
+ if (digraph.id !== void 0) {
214
+ clusters.set(digraph.id, digraph);
215
+ }
216
+ }, [clusters, digraph]);
217
+ return /* @__PURE__ */ jsx(GraphContainer.Provider, { value: digraph, children: /* @__PURE__ */ jsx(CurrentGraph.Provider, { value: digraph, children }) });
218
+ };
219
+ Digraph.displayName = "Digraph";
220
+ function useCurrentGraph() {
221
+ const cluster = useContext(CurrentGraph);
222
+ if (cluster === null) {
223
+ throw Error(
224
+ "useCurrentGraph must be called within a cluster such as Digraph, Graph, Subgraph."
225
+ );
226
+ }
227
+ return cluster;
130
228
  }
131
229
  function useHasAttributes(target, attributes) {
132
- useEffect(() => {
230
+ useLayoutEffect(() => {
133
231
  target.attributes.clear();
134
232
  target.attributes.apply(attributes);
135
233
  }, [target, attributes]);
@@ -142,10 +240,8 @@ function useEdge(targets, props = {}) {
142
240
  }
143
241
  const edge = useMemo(() => {
144
242
  const e = cluster.createEdge(targets);
145
- e.comment = comment;
146
- e.attributes.apply(attributes);
147
243
  return e;
148
- }, [cluster, targets, comment, attributes]);
244
+ }, [cluster, targets]);
149
245
  useHasComment(edge, comment);
150
246
  useHasAttributes(edge, attributes);
151
247
  useEffect(() => {
@@ -155,43 +251,42 @@ function useEdge(targets, props = {}) {
155
251
  }, [cluster, edge]);
156
252
  return edge;
157
253
  }
158
- function useNode(id, options = {}) {
159
- const { comment, ...attributes } = options;
160
- const cluster = useCurrentGraph();
161
- const node = useMemo(() => {
162
- const n = cluster.createNode(id);
163
- n.attributes.apply(attributes);
164
- n.comment = comment;
165
- return n;
166
- }, [cluster, id, attributes, comment]);
167
- useHasComment(node, comment);
168
- useHasAttributes(node, attributes);
254
+ const Edge = ({ targets, label, ref, ...options }) => {
255
+ const renderedLabel = useRenderedID(label);
256
+ if (renderedLabel !== void 0)
257
+ Object.assign(options, { label: renderedLabel });
258
+ const edge = useEdge(targets, options);
259
+ const modelCollector = useModelCollector();
260
+ useImperativeHandle(ref, () => edge, [edge]);
261
+ useLayoutEffect(() => {
262
+ modelCollector?.collectModel(edge);
263
+ }, [modelCollector, edge]);
264
+ return null;
265
+ };
266
+ Edge.displayName = "Edge";
267
+ function useGraph(options = {}) {
268
+ const { id, comment, edge, node, graph, ...attributes } = options;
269
+ const context = useGraphvizContext();
270
+ const memoGraph = useMemo(() => {
271
+ const g = new Graph$1(id);
272
+ context.container = g;
273
+ return g;
274
+ }, [context, id]);
275
+ useHasComment(memoGraph, comment);
276
+ useGraphAttributes(memoGraph, attributes, { edge, node, graph });
169
277
  useEffect(() => {
170
278
  return () => {
171
- cluster.removeNode(node);
279
+ context.container = void 0;
172
280
  };
173
- }, [cluster, node]);
174
- return node;
175
- }
176
- const GraphMap = createContext(/* @__PURE__ */ new Map());
177
- GraphMap.displayName = "GraphMap";
178
- function useGraphMap() {
179
- return useContext(GraphMap);
180
- }
181
- function renderHTMLLike(label) {
182
- const htmlLike = renderToStaticMarkup(label).replace(/<dot:port>(.+?)<\/dot:port>/gi, "<$1>").replace(/<(\/?)dot:([a-z-]+)/gi, (_, $1, $2) => `<${$1}${$2}`);
183
- return `<${htmlLike}>`;
184
- }
185
- function renderId(id) {
186
- if (isValidElement(id)) {
187
- return renderHTMLLike(id);
188
- }
189
- return id;
190
- }
191
- function useRenderedID(id) {
192
- return useMemo(() => renderId(id), [id]);
281
+ }, [context]);
282
+ return memoGraph;
193
283
  }
194
- const Graph = ({ children, label, ...options }) => {
284
+ const Graph = ({
285
+ children,
286
+ label,
287
+ ref,
288
+ ...options
289
+ }) => {
195
290
  const container = useGraphContainer();
196
291
  if (container !== null) {
197
292
  throw Error(
@@ -203,6 +298,11 @@ const Graph = ({ children, label, ...options }) => {
203
298
  Object.assign(options, { label: renderedLabel });
204
299
  const graph = useGraph(options);
205
300
  const clusters = useGraphMap();
301
+ const modelCollector = useModelCollector();
302
+ useImperativeHandle(ref, () => graph, [graph]);
303
+ useLayoutEffect(() => {
304
+ modelCollector?.collectModel(graph);
305
+ }, [modelCollector, graph]);
206
306
  useEffect(() => {
207
307
  if (graph.id !== void 0) {
208
308
  clusters.set(graph.id, graph);
@@ -211,33 +311,74 @@ const Graph = ({ children, label, ...options }) => {
211
311
  return /* @__PURE__ */ jsx(GraphContainer.Provider, { value: graph, children: /* @__PURE__ */ jsx(CurrentGraph.Provider, { value: graph, children }) });
212
312
  };
213
313
  Graph.displayName = "Graph";
214
- const Digraph = ({
215
- children,
216
- label,
217
- ...options
218
- }) => {
314
+ const GraphPortal = ({ children, id }) => {
219
315
  const container = useGraphContainer();
220
- if (container !== null) {
221
- throw Error(
222
- "RootCluster is duplicated.\nUse only one of Digraph and Graph."
223
- );
224
- }
316
+ const map = useContext(GraphMap);
317
+ const cluster = useMemo(
318
+ () => id && map ? map.get(id) ?? container : container,
319
+ [container, map, id]
320
+ );
321
+ return /* @__PURE__ */ jsx(CurrentGraph.Provider, { value: cluster, children });
322
+ };
323
+ GraphPortal.displayName = "GraphPortal";
324
+ function useNode(id, options = {}) {
325
+ const { comment, ...attributes } = options;
326
+ const cluster = useCurrentGraph();
327
+ const node = useMemo(() => {
328
+ const n = cluster.createNode(id);
329
+ return n;
330
+ }, [cluster, id]);
331
+ useHasComment(node, comment);
332
+ useHasAttributes(node, attributes);
333
+ useEffect(() => {
334
+ return () => {
335
+ cluster.removeNode(node);
336
+ };
337
+ }, [cluster, node]);
338
+ return node;
339
+ }
340
+ const Node = ({ id, label, xlabel, ref, ...options }) => {
225
341
  const renderedLabel = useRenderedID(label);
342
+ const renderedXlabel = useRenderedID(xlabel);
226
343
  if (renderedLabel !== void 0)
227
344
  Object.assign(options, { label: renderedLabel });
228
- const digraph = useDigraph(options);
229
- const clusters = useGraphMap();
230
- useEffect(() => {
231
- if (digraph.id !== void 0) {
232
- clusters.set(digraph.id, digraph);
233
- }
234
- }, [clusters, digraph]);
235
- return /* @__PURE__ */ jsx(GraphContainer.Provider, { value: digraph, children: /* @__PURE__ */ jsx(CurrentGraph.Provider, { value: digraph, children }) });
345
+ if (renderedXlabel !== void 0)
346
+ Object.assign(options, { xlabel: renderedXlabel });
347
+ const node = useNode(id, options);
348
+ const modelCollector = useModelCollector();
349
+ useImperativeHandle(ref, () => node, [node]);
350
+ useLayoutEffect(() => {
351
+ modelCollector?.collectModel(node);
352
+ }, [modelCollector, node]);
353
+ return null;
236
354
  };
237
- Digraph.displayName = "Digraph";
355
+ Node.displayName = "Node";
356
+ function useSubgraph(props = {}) {
357
+ const { id, comment, edge, node, graph, ...attributes } = props;
358
+ const context = useGraphvizContext();
359
+ const cluster = useCurrentGraph();
360
+ const subgraph = useMemo(() => {
361
+ const g = new Subgraph$1(id);
362
+ if (cluster !== null) {
363
+ cluster.addSubgraph(g);
364
+ } else if (!context.container) {
365
+ context.container = g;
366
+ }
367
+ return g;
368
+ }, [context, cluster, id]);
369
+ useHasComment(subgraph, comment);
370
+ useGraphAttributes(subgraph, attributes, { edge, node, graph });
371
+ useEffect(() => {
372
+ return () => {
373
+ cluster.removeSubgraph(subgraph);
374
+ };
375
+ }, [cluster, subgraph]);
376
+ return subgraph;
377
+ }
238
378
  const Subgraph = ({
239
379
  children,
240
380
  label,
381
+ ref,
241
382
  ...options
242
383
  }) => {
243
384
  const renderedLabel = useRenderedID(label);
@@ -245,6 +386,11 @@ const Subgraph = ({
245
386
  Object.assign(options, { label: renderedLabel });
246
387
  const subgraph = useSubgraph(options);
247
388
  const clusters = useGraphMap();
389
+ const modelCollector = useModelCollector();
390
+ useImperativeHandle(ref, () => subgraph, [subgraph]);
391
+ useLayoutEffect(() => {
392
+ modelCollector?.collectModel(subgraph);
393
+ }, [modelCollector, subgraph]);
248
394
  useEffect(() => {
249
395
  if (subgraph.id !== void 0) {
250
396
  clusters.set(subgraph.id, subgraph);
@@ -253,43 +399,80 @@ const Subgraph = ({
253
399
  return /* @__PURE__ */ jsx(CurrentGraph.Provider, { value: subgraph, children });
254
400
  };
255
401
  Subgraph.displayName = "Subgraph";
256
- const Node = ({ id, label, xlabel, ...options }) => {
257
- const renderedLabel = useRenderedID(label);
258
- const renderedXlabel = useRenderedID(xlabel);
259
- if (renderedLabel !== void 0)
260
- Object.assign(options, { label: renderedLabel });
261
- if (renderedXlabel !== void 0)
262
- Object.assign(options, { xlabel: renderedXlabel });
263
- useNode(id, options);
264
- return null;
265
- };
266
- Node.displayName = "Node";
267
- const Edge = ({ targets, label, ...options }) => {
268
- const renderedLabel = useRenderedID(label);
269
- if (renderedLabel !== void 0)
270
- Object.assign(options, { label: renderedLabel });
271
- useEdge(targets, options);
272
- return null;
273
- };
274
- Edge.displayName = "Edge";
275
- const GraphPortal = ({ children, id }) => {
276
- const container = useGraphContainer();
277
- const map = useContext(GraphMap);
278
- const cluster = useMemo(
279
- () => id ? map.get(id) ?? container : container,
280
- [container, map, id]
281
- );
282
- return /* @__PURE__ */ jsx(CurrentGraph.Provider, { value: cluster, children });
283
- };
284
- GraphPortal.displayName = "GraphPortal";
285
402
  class HostConfig {
286
- preparePortalMount(containerInfo) {
403
+ preparePortalMount(_containerInfo) {
287
404
  }
288
405
  scheduleTimeout(fn, delay) {
406
+ return setTimeout(fn, delay || 0);
289
407
  }
290
408
  cancelTimeout(id) {
409
+ clearTimeout(id);
291
410
  }
292
411
  queueMicrotask(fn) {
412
+ fn();
413
+ }
414
+ // For update priority management
415
+ resolveUpdatePriority() {
416
+ return 1;
417
+ }
418
+ setCurrentUpdatePriority(_priority) {
419
+ }
420
+ getCurrentUpdatePriority() {
421
+ return 1;
422
+ }
423
+ // form-related methods
424
+ resetFormInstance(_form) {
425
+ }
426
+ // transition-related properties
427
+ NotPendingTransition = null;
428
+ // suspense and commit methods
429
+ maySuspendCommit(_type, _props) {
430
+ return false;
431
+ }
432
+ preloadInstance(_type, _props) {
433
+ return false;
434
+ }
435
+ startSuspendingCommit() {
436
+ }
437
+ suspendInstance(_type, _props) {
438
+ }
439
+ waitForCommitToBeReady() {
440
+ return null;
441
+ }
442
+ // additional required methods
443
+ getInstanceFromNode(_node) {
444
+ return null;
445
+ }
446
+ beforeActiveInstanceBlur() {
447
+ }
448
+ afterActiveInstanceBlur() {
449
+ }
450
+ prepareScopeUpdate(_scopeInstance, _instance) {
451
+ }
452
+ getInstanceFromScope(_scopeInstance) {
453
+ return null;
454
+ }
455
+ detachDeletedInstance(_instance) {
456
+ }
457
+ logRecoverableError(_error) {
458
+ }
459
+ requestPostPaintCallback(_callback) {
460
+ }
461
+ // container clearing method
462
+ clearContainer(_container) {
463
+ }
464
+ // transition context and scheduling methods
465
+ HostTransitionContext = null;
466
+ shouldAttemptEagerTransition() {
467
+ return false;
468
+ }
469
+ trackSchedulerEvent() {
470
+ }
471
+ resolveEventType() {
472
+ return null;
473
+ }
474
+ resolveEventTimeStamp() {
475
+ return Date.now();
293
476
  }
294
477
  cloneInstance;
295
478
  cloneFundamentalInstance;
@@ -307,51 +490,75 @@ class HostConfig {
307
490
  // Temporary workaround for scenario where multiple renderers concurrently
308
491
  // render using the same context objects. E.g. React DOM and React ART on the
309
492
  // same page. DOM is the primary renderer; ART is the secondary renderer.
310
- isPrimaryRenderer = false;
311
- supportsMutation = false;
493
+ isPrimaryRenderer = true;
494
+ supportsMutation = true;
312
495
  supportsPersistence = false;
313
496
  supportsHydration = false;
497
+ // specific configurations for synchronous rendering
498
+ supportsMicrotasks = false;
314
499
  getPublicInstance(instance) {
315
500
  return instance;
316
501
  }
317
- getRootHostContext(rootContainerInstance) {
318
- return {};
502
+ getRootHostContext(_rootContainerInstance) {
503
+ return {
504
+ graph: void 0
505
+ };
319
506
  }
320
- getChildHostContext(parentHostContext, type, rootContainerInstance) {
321
- return parentHostContext;
507
+ getChildHostContext(parentHostContext, _type, _rootContainerInstance) {
508
+ return {
509
+ ...parentHostContext
510
+ // You could extend context based on component type if needed
511
+ };
322
512
  }
323
- prepareForCommit(containerInfo) {
513
+ prepareForCommit(_containerInfo) {
324
514
  return null;
325
515
  }
326
- resetAfterCommit(containerInfo) {
516
+ resetAfterCommit(_containerInfo) {
327
517
  }
328
518
  /**
329
- * Create component instance
519
+ * Create component instance for Graphviz elements
330
520
  */
331
- createInstance(type, props, rootContainerInstance, hostContext, internalInstanceHandle) {
332
- return type(props);
521
+ createInstance(type, props, _rootContainerInstance, _hostContext, _internalInstanceHandle) {
522
+ const instance = {
523
+ type: type.displayName || type.name || "Unknown",
524
+ props,
525
+ children: [],
526
+ appendChild: (child) => {
527
+ instance.children.push(child);
528
+ }
529
+ };
530
+ return instance;
333
531
  }
334
532
  appendInitialChild(parentInstance, child) {
335
- parentInstance.appendChild(child);
533
+ if (parentInstance.appendChild) {
534
+ parentInstance.appendChild(child);
535
+ }
336
536
  }
337
- finalizeInitialChildren(parentInstance, type, props, rootContainerInstance, hostContext) {
537
+ finalizeInitialChildren(_instance, _type, _props, _rootContainerInstance, _hostContext) {
338
538
  return false;
339
539
  }
340
- prepareUpdate(instance, type, oldProps, newProps, rootContainerInstance, hostContext) {
341
- return {};
540
+ prepareUpdate(_instance, _type, oldProps, newProps, _rootContainerInstance, _hostContext) {
541
+ if (JSON.stringify(oldProps) !== JSON.stringify(newProps)) {
542
+ return {
543
+ type: "UPDATE",
544
+ oldProps,
545
+ newProps
546
+ };
547
+ }
548
+ return null;
342
549
  }
343
- shouldSetTextContent(type, props) {
550
+ shouldSetTextContent(_type, _props) {
344
551
  return false;
345
552
  }
346
- shouldDeprioritizeSubtree(type, props) {
553
+ shouldDeprioritizeSubtree(_type, _props) {
347
554
  return false;
348
555
  }
349
- createTextInstance(text, rootContainerInstance, hostContext, internalInstanceHandle) {
350
- return text;
556
+ createTextInstance(text, _rootContainerInstance, _hostContext, _internalInstanceHandle) {
557
+ return text.trim();
351
558
  }
352
- scheduleDeferredCallback(callback, options) {
559
+ scheduleDeferredCallback(_callback, _options) {
353
560
  }
354
- cancelDeferredCallback(callbackID) {
561
+ cancelDeferredCallback(_callbackID) {
355
562
  }
356
563
  // -------------------
357
564
  // Mutation
@@ -363,22 +570,26 @@ class HostConfig {
363
570
  }
364
571
  }
365
572
  appendChildToContainer(container, child) {
573
+ if (typeof child === "object" && "type" in child) {
574
+ container.__rootInstance = child;
575
+ }
366
576
  }
367
- commitTextUpdate(textInstance, oldText, newText) {
577
+ commitTextUpdate(_textInstance, _oldText, _newText) {
368
578
  }
369
- commitMount(instance, type, newProps, internalInstanceHandle) {
579
+ commitMount(_instance, _type, _props, _internalInstanceHandle) {
370
580
  }
371
- commitUpdate(instance, updatePayload, type, oldProps, newProps, internalInstanceHandle) {
581
+ commitUpdate(instance, _type, _prevProps, nextProps, _internalHandle) {
582
+ instance.props = nextProps;
372
583
  }
373
- insertBefore(parentInstance, child, beforeChild) {
584
+ insertBefore(_parentInstance, _child, _beforeChild) {
374
585
  }
375
- insertInContainerBefore(container, child, beforeChild) {
586
+ insertInContainerBefore(_container, _child, _beforeChild) {
376
587
  }
377
- removeChild(parentInstance, child) {
588
+ removeChild(_parentInstance, _child) {
378
589
  }
379
- removeChildFromContainer(container, child) {
590
+ removeChildFromContainer(_container, _child) {
380
591
  }
381
- resetTextContent(instance) {
592
+ resetTextContent(_instance) {
382
593
  }
383
594
  }
384
595
  const reconciler = ReactReconciler(new HostConfig());
@@ -394,12 +605,13 @@ function clusterMap(graph, map = /* @__PURE__ */ new Map()) {
394
605
  }
395
606
  return map;
396
607
  }
397
- function render(element, container) {
398
- const context = { container };
399
- reconciler.updateContainer(
608
+ function createWrappedElement(element, context, collectModel, container) {
609
+ return createElement(
610
+ GraphvizContext.Provider,
611
+ { value: context },
400
612
  createElement(
401
- GraphvizContext.Provider,
402
- { value: context },
613
+ ModelCollectorContext.Provider,
614
+ { value: { collectModel } },
403
615
  createElement(
404
616
  GraphMap.Provider,
405
617
  { value: clusterMap(container) },
@@ -413,29 +625,155 @@ function render(element, container) {
413
625
  ) : element
414
626
  )
415
627
  )
416
- ),
417
- reconciler.createContainer(
418
- {},
419
- 0,
420
- null,
421
- true,
422
- null,
423
- "@ts-graphviz/react",
424
- noop,
425
- null
426
- ),
628
+ )
629
+ );
630
+ }
631
+ function createFiberRoot(containerInfo, options = {}) {
632
+ const { onUncaughtError = noop } = options;
633
+ return reconciler.createContainer(
634
+ containerInfo,
635
+ 1,
636
+ // tag - ConcurrentRoot = 1 for concurrent mode (React 19 default)
637
+ null,
638
+ // hydrationCallbacks
639
+ false,
640
+ // isStrictMode
427
641
  null,
428
- noop
642
+ // concurrentUpdatesByDefaultOverride
643
+ "@ts-graphviz/react",
644
+ // identifierPrefix
645
+ // Enhance onRecoverableError to forward componentStack for better diagnostics
646
+ onUncaughtError ? (error, errorInfo = {}) => onUncaughtError(error, errorInfo) : noop,
647
+ // onRecoverableError
648
+ null
649
+ // transitionCallbacks
429
650
  );
430
- if (!context.container) {
431
- throw Error(
432
- "There are no clusters of container(Subgraph, Digraph, Graph)."
651
+ }
652
+ function validateModels(models, container) {
653
+ if (container) {
654
+ return;
655
+ }
656
+ const topLevelGraphs = models.filter((model) => model.$$type === "Graph");
657
+ if (topLevelGraphs.length === 0) {
658
+ throw new Error(
659
+ "No top-level graph found. When not using a container, you must provide exactly one Graph or Digraph component at the top level."
433
660
  );
434
661
  }
435
- return context.container;
662
+ if (topLevelGraphs.length > 1) {
663
+ throw new Error(
664
+ `Multiple top-level graphs detected (${topLevelGraphs.length}). DOT language only supports one graph per file. Consider using a container or rendering graphs separately.`
665
+ );
666
+ }
667
+ }
668
+ async function renderInternal(containerInfo, fiberRoot, wrappedElement, originalContainer, options = {}) {
669
+ const { timeout = 5e3, onRenderComplete } = options;
670
+ return new Promise((resolve, reject) => {
671
+ const timeoutId = setTimeout(() => {
672
+ reject(new Error(`Rendering timeout after ${timeout}ms`));
673
+ }, timeout);
674
+ try {
675
+ reconciler.updateContainer(wrappedElement, fiberRoot, null, () => {
676
+ clearTimeout(timeoutId);
677
+ try {
678
+ validateModels(containerInfo.renderedModels, originalContainer);
679
+ if (onRenderComplete) {
680
+ onRenderComplete([...containerInfo.renderedModels]);
681
+ }
682
+ resolve();
683
+ } catch (error) {
684
+ reject(error);
685
+ }
686
+ });
687
+ } catch (error) {
688
+ clearTimeout(timeoutId);
689
+ reject(error);
690
+ }
691
+ });
436
692
  }
437
- function renderToDot(element, container) {
438
- return toDot(render(element, container));
693
+ function createRoot(options = {}) {
694
+ const { container, ...otherOptions } = options;
695
+ const containerInfo = {
696
+ renderedModels: []
697
+ };
698
+ const collectModel = (model) => {
699
+ if (container) {
700
+ if (model !== container) {
701
+ containerInfo.renderedModels.push(model);
702
+ }
703
+ } else {
704
+ if (isRootGraphModel(model)) {
705
+ containerInfo.renderedModels.push(model);
706
+ }
707
+ }
708
+ };
709
+ const context = {
710
+ container
711
+ };
712
+ containerInfo.context = context;
713
+ const fiberRoot = createFiberRoot(containerInfo, otherOptions);
714
+ let isUnmounted = false;
715
+ return {
716
+ async render(element) {
717
+ if (isUnmounted) {
718
+ throw new Error("Cannot render on an unmounted root");
719
+ }
720
+ containerInfo.renderedModels = [];
721
+ const wrappedElement = createWrappedElement(
722
+ element,
723
+ context,
724
+ collectModel,
725
+ container
726
+ );
727
+ return renderInternal(
728
+ containerInfo,
729
+ fiberRoot,
730
+ wrappedElement,
731
+ container,
732
+ otherOptions
733
+ );
734
+ },
735
+ unmount() {
736
+ if (isUnmounted) {
737
+ return;
738
+ }
739
+ isUnmounted = true;
740
+ reconciler.updateContainer(null, fiberRoot, null, noop);
741
+ containerInfo.renderedModels = [];
742
+ },
743
+ getTopLevelModels(typeGuard) {
744
+ const models = [...containerInfo.renderedModels];
745
+ if (typeGuard) {
746
+ return models.filter(typeGuard);
747
+ }
748
+ return models;
749
+ }
750
+ };
751
+ }
752
+ async function renderToDot(element, options = {}) {
753
+ const { container, ...rootOptions } = options;
754
+ if (container) {
755
+ const root = createRoot({ container, ...rootOptions });
756
+ try {
757
+ await root.render(element);
758
+ return toDot(container);
759
+ } finally {
760
+ root.unmount();
761
+ }
762
+ } else {
763
+ const root = createRoot(rootOptions);
764
+ try {
765
+ await root.render(element);
766
+ const models = root.getTopLevelModels();
767
+ if (models.length !== 1) {
768
+ throw new Error(
769
+ `Expected exactly one top-level graph, but got ${models.length}. Use a container or ensure only one Graph/Digraph component is at the top level.`
770
+ );
771
+ }
772
+ return toDot(models[0]);
773
+ } finally {
774
+ root.unmount();
775
+ }
776
+ }
439
777
  }
440
778
  export {
441
779
  Digraph,
@@ -444,7 +782,12 @@ export {
444
782
  GraphPortal,
445
783
  Node,
446
784
  Subgraph,
447
- render,
785
+ createRoot,
786
+ isAttributeListModel,
787
+ isEdgeModel,
788
+ isNodeModel,
789
+ isRootGraphModel2 as isRootGraphModel,
790
+ isSubgraphModel,
448
791
  renderHTMLLike,
449
792
  renderToDot,
450
793
  useCurrentGraph,