@ts-graphviz/react 0.9.1 → 0.10.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.
- package/CHANGELOG.md +76 -0
- package/LICENSE +1 -1
- package/README.md +25 -63
- package/example/example.tsx +14 -12
- package/lib/react.cjs +457 -0
- package/lib/react.d.ts +230 -0
- package/lib/react.js +457 -0
- package/package.json +55 -65
- package/src/components/Digraph.spec.tsx +18 -0
- package/src/components/Digraph.tsx +41 -0
- package/src/components/Edge.tsx +17 -0
- package/src/components/Graph.spec.tsx +18 -0
- package/src/components/Graph.tsx +36 -0
- package/src/components/GraphPortal.tsx +22 -0
- package/src/components/Node.spec.tsx +101 -0
- package/src/components/Node.tsx +21 -0
- package/src/components/Subgraph.tsx +30 -0
- package/src/contexts/CurrentGraph.ts +5 -0
- package/src/contexts/GraphContainer.ts +5 -0
- package/src/contexts/GraphMap.ts +5 -0
- package/src/contexts/GraphvizContext.ts +11 -0
- package/src/hooks/__tests__/wrapper.tsx +58 -0
- package/src/hooks/useCurrentGraph.spec.ts +56 -0
- package/src/hooks/useCurrentGraph.ts +18 -0
- package/src/hooks/useDigraph.spec.ts +15 -0
- package/src/hooks/useDigraph.ts +33 -0
- package/src/hooks/useEdge.spec.ts +46 -0
- package/src/hooks/useEdge.ts +35 -0
- package/src/hooks/useGraph.spec.ts +15 -0
- package/src/hooks/useGraph.ts +33 -0
- package/src/hooks/useGraphAttributes.ts +32 -0
- package/src/hooks/useGraphContainer.spec.ts +45 -0
- package/src/hooks/useGraphContainer.ts +10 -0
- package/src/hooks/useGraphMap.ts +7 -0
- package/src/hooks/useGraphvizContext.spec.ts +26 -0
- package/src/hooks/useGraphvizContext.ts +16 -0
- package/src/hooks/useHasAttributes.ts +16 -0
- package/src/hooks/useHasComment.ts +8 -0
- package/src/hooks/useNode.spec.ts +16 -0
- package/src/hooks/useNode.ts +29 -0
- package/src/hooks/useRenderedID.spec.tsx +28 -0
- package/src/hooks/useRenderedID.ts +6 -0
- package/src/hooks/useSubgraph.spec.ts +41 -0
- package/src/hooks/useSubgraph.ts +39 -0
- package/src/react.ts +16 -0
- package/src/reconciler.ts +272 -0
- package/src/render.test.tsx +71 -0
- package/src/render.ts +142 -0
- package/src/renderHTMLLike.ts +9 -0
- package/src/renderId.ts +9 -0
- package/src/types.ts +107 -0
- package/tsconfig.json +8 -0
- package/typedoc.json +5 -0
- package/vite.config.ts +31 -0
- package/.all-contributorsrc +0 -47
- package/.devcontainer/Dockerfile +0 -16
- package/.devcontainer/devcontainer.json +0 -35
- package/.eslintignore +0 -2
- package/lib/index.d.ts +0 -368
- package/lib/index.js +0 -1
- package/lib/index.mjs +0 -1
- package/rollup.config.js +0 -55
package/lib/react.js
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
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";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
import { renderToStaticMarkup } from "react-dom/server";
|
|
5
|
+
import ReactReconciler from "react-reconciler";
|
|
6
|
+
const GraphContainer = createContext(null);
|
|
7
|
+
GraphContainer.displayName = "GraphContainer";
|
|
8
|
+
function useGraphContainer() {
|
|
9
|
+
return useContext(GraphContainer);
|
|
10
|
+
}
|
|
11
|
+
const CurrentGraph = createContext(null);
|
|
12
|
+
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
|
+
}
|
|
22
|
+
function useGraphAttributes(cluster, attributes, { edge, node, graph }) {
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
cluster.clear();
|
|
25
|
+
cluster.apply(attributes);
|
|
26
|
+
}, [cluster, attributes]);
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
cluster.attributes.node.clear();
|
|
29
|
+
cluster.attributes.node.apply(node ?? {});
|
|
30
|
+
}, [cluster, node]);
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
cluster.attributes.edge.clear();
|
|
33
|
+
cluster.attributes.edge.apply(edge ?? {});
|
|
34
|
+
}, [cluster, edge]);
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
cluster.attributes.graph.clear();
|
|
37
|
+
cluster.attributes.graph.apply(graph ?? {});
|
|
38
|
+
}, [cluster, graph]);
|
|
39
|
+
}
|
|
40
|
+
const GraphvizContext = createContext(
|
|
41
|
+
null
|
|
42
|
+
);
|
|
43
|
+
GraphvizContext.displayName = "GraphvizContext";
|
|
44
|
+
function useGraphvizContext() {
|
|
45
|
+
const context = useContext(
|
|
46
|
+
GraphvizContext
|
|
47
|
+
);
|
|
48
|
+
if (context === null) {
|
|
49
|
+
throw Error(
|
|
50
|
+
"Cannot call useGraphvizContext outside GraphvizContext.\nBasically, you need to use the render function provided by @ts-graphviz/react."
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return context;
|
|
54
|
+
}
|
|
55
|
+
function useHasComment(target, comment) {
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
target.comment = comment;
|
|
58
|
+
}, [target, comment]);
|
|
59
|
+
}
|
|
60
|
+
function useDigraph(options = {}) {
|
|
61
|
+
const { id, comment, edge, node, graph, ...attributes } = options;
|
|
62
|
+
const context = useGraphvizContext();
|
|
63
|
+
const digraph = useMemo(() => {
|
|
64
|
+
const g = new Digraph$1(id);
|
|
65
|
+
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
|
+
return g;
|
|
72
|
+
}, [context, id, comment, edge, node, graph, attributes]);
|
|
73
|
+
useHasComment(digraph, comment);
|
|
74
|
+
useGraphAttributes(digraph, attributes, { edge, node, graph });
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
return () => {
|
|
77
|
+
context.container = void 0;
|
|
78
|
+
};
|
|
79
|
+
}, [context]);
|
|
80
|
+
return digraph;
|
|
81
|
+
}
|
|
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;
|
|
103
|
+
}
|
|
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;
|
|
114
|
+
}
|
|
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 });
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
return () => {
|
|
126
|
+
cluster.removeSubgraph(subgraph);
|
|
127
|
+
};
|
|
128
|
+
}, [cluster, subgraph]);
|
|
129
|
+
return subgraph;
|
|
130
|
+
}
|
|
131
|
+
function useHasAttributes(target, attributes) {
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
target.attributes.clear();
|
|
134
|
+
target.attributes.apply(attributes);
|
|
135
|
+
}, [target, attributes]);
|
|
136
|
+
}
|
|
137
|
+
function useEdge(targets, props = {}) {
|
|
138
|
+
const { comment, ...attributes } = props;
|
|
139
|
+
const cluster = useCurrentGraph();
|
|
140
|
+
if (targets.length < 2) {
|
|
141
|
+
throw Error("Edges must have at least 2 targets.");
|
|
142
|
+
}
|
|
143
|
+
const edge = useMemo(() => {
|
|
144
|
+
const e = cluster.createEdge(targets);
|
|
145
|
+
e.comment = comment;
|
|
146
|
+
e.attributes.apply(attributes);
|
|
147
|
+
return e;
|
|
148
|
+
}, [cluster, targets, comment, attributes]);
|
|
149
|
+
useHasComment(edge, comment);
|
|
150
|
+
useHasAttributes(edge, attributes);
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
return () => {
|
|
153
|
+
cluster.removeEdge(edge);
|
|
154
|
+
};
|
|
155
|
+
}, [cluster, edge]);
|
|
156
|
+
return edge;
|
|
157
|
+
}
|
|
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);
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
return () => {
|
|
171
|
+
cluster.removeNode(node);
|
|
172
|
+
};
|
|
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]);
|
|
193
|
+
}
|
|
194
|
+
const Graph = ({ children, label, ...options }) => {
|
|
195
|
+
const container = useGraphContainer();
|
|
196
|
+
if (container !== null) {
|
|
197
|
+
throw Error(
|
|
198
|
+
"RootCluster is duplicated.\nUse only one of Digraph and Graph."
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
const renderedLabel = useRenderedID(label);
|
|
202
|
+
if (renderedLabel !== void 0)
|
|
203
|
+
Object.assign(options, { label: renderedLabel });
|
|
204
|
+
const graph = useGraph(options);
|
|
205
|
+
const clusters = useGraphMap();
|
|
206
|
+
useEffect(() => {
|
|
207
|
+
if (graph.id !== void 0) {
|
|
208
|
+
clusters.set(graph.id, graph);
|
|
209
|
+
}
|
|
210
|
+
}, [clusters, graph]);
|
|
211
|
+
return /* @__PURE__ */ jsx(GraphContainer.Provider, { value: graph, children: /* @__PURE__ */ jsx(CurrentGraph.Provider, { value: graph, children }) });
|
|
212
|
+
};
|
|
213
|
+
Graph.displayName = "Graph";
|
|
214
|
+
const Digraph = ({
|
|
215
|
+
children,
|
|
216
|
+
label,
|
|
217
|
+
...options
|
|
218
|
+
}) => {
|
|
219
|
+
const container = useGraphContainer();
|
|
220
|
+
if (container !== null) {
|
|
221
|
+
throw Error(
|
|
222
|
+
"RootCluster is duplicated.\nUse only one of Digraph and Graph."
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
const renderedLabel = useRenderedID(label);
|
|
226
|
+
if (renderedLabel !== void 0)
|
|
227
|
+
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 }) });
|
|
236
|
+
};
|
|
237
|
+
Digraph.displayName = "Digraph";
|
|
238
|
+
const Subgraph = ({
|
|
239
|
+
children,
|
|
240
|
+
label,
|
|
241
|
+
...options
|
|
242
|
+
}) => {
|
|
243
|
+
const renderedLabel = useRenderedID(label);
|
|
244
|
+
if (renderedLabel !== void 0)
|
|
245
|
+
Object.assign(options, { label: renderedLabel });
|
|
246
|
+
const subgraph = useSubgraph(options);
|
|
247
|
+
const clusters = useGraphMap();
|
|
248
|
+
useEffect(() => {
|
|
249
|
+
if (subgraph.id !== void 0) {
|
|
250
|
+
clusters.set(subgraph.id, subgraph);
|
|
251
|
+
}
|
|
252
|
+
}, [subgraph, clusters]);
|
|
253
|
+
return /* @__PURE__ */ jsx(CurrentGraph.Provider, { value: subgraph, children });
|
|
254
|
+
};
|
|
255
|
+
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
|
+
class HostConfig {
|
|
286
|
+
preparePortalMount(containerInfo) {
|
|
287
|
+
}
|
|
288
|
+
scheduleTimeout(fn, delay) {
|
|
289
|
+
}
|
|
290
|
+
cancelTimeout(id) {
|
|
291
|
+
}
|
|
292
|
+
queueMicrotask(fn) {
|
|
293
|
+
}
|
|
294
|
+
cloneInstance;
|
|
295
|
+
cloneFundamentalInstance;
|
|
296
|
+
createContainerChildSet;
|
|
297
|
+
appendChildToContainerChildSet;
|
|
298
|
+
finalizeContainerChildren;
|
|
299
|
+
replaceContainerChildren;
|
|
300
|
+
cloneHiddenInstance;
|
|
301
|
+
cloneHiddenTextInstance;
|
|
302
|
+
now = Date.now;
|
|
303
|
+
setTimeout = setTimeout;
|
|
304
|
+
clearTimeout = clearTimeout;
|
|
305
|
+
noTimeout = -1;
|
|
306
|
+
// TODO
|
|
307
|
+
// Temporary workaround for scenario where multiple renderers concurrently
|
|
308
|
+
// render using the same context objects. E.g. React DOM and React ART on the
|
|
309
|
+
// same page. DOM is the primary renderer; ART is the secondary renderer.
|
|
310
|
+
isPrimaryRenderer = false;
|
|
311
|
+
supportsMutation = false;
|
|
312
|
+
supportsPersistence = false;
|
|
313
|
+
supportsHydration = false;
|
|
314
|
+
getPublicInstance(instance) {
|
|
315
|
+
return instance;
|
|
316
|
+
}
|
|
317
|
+
getRootHostContext(rootContainerInstance) {
|
|
318
|
+
return {};
|
|
319
|
+
}
|
|
320
|
+
getChildHostContext(parentHostContext, type, rootContainerInstance) {
|
|
321
|
+
return parentHostContext;
|
|
322
|
+
}
|
|
323
|
+
prepareForCommit(containerInfo) {
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
resetAfterCommit(containerInfo) {
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Create component instance
|
|
330
|
+
*/
|
|
331
|
+
createInstance(type, props, rootContainerInstance, hostContext, internalInstanceHandle) {
|
|
332
|
+
return type(props);
|
|
333
|
+
}
|
|
334
|
+
appendInitialChild(parentInstance, child) {
|
|
335
|
+
parentInstance.appendChild(child);
|
|
336
|
+
}
|
|
337
|
+
finalizeInitialChildren(parentInstance, type, props, rootContainerInstance, hostContext) {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
prepareUpdate(instance, type, oldProps, newProps, rootContainerInstance, hostContext) {
|
|
341
|
+
return {};
|
|
342
|
+
}
|
|
343
|
+
shouldSetTextContent(type, props) {
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
shouldDeprioritizeSubtree(type, props) {
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
createTextInstance(text, rootContainerInstance, hostContext, internalInstanceHandle) {
|
|
350
|
+
return text;
|
|
351
|
+
}
|
|
352
|
+
scheduleDeferredCallback(callback, options) {
|
|
353
|
+
}
|
|
354
|
+
cancelDeferredCallback(callbackID) {
|
|
355
|
+
}
|
|
356
|
+
// -------------------
|
|
357
|
+
// Mutation
|
|
358
|
+
// (optional)
|
|
359
|
+
// -------------------
|
|
360
|
+
appendChild(parentInstance, child) {
|
|
361
|
+
if (parentInstance.appendChild) {
|
|
362
|
+
parentInstance.appendChild(child);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
appendChildToContainer(container, child) {
|
|
366
|
+
}
|
|
367
|
+
commitTextUpdate(textInstance, oldText, newText) {
|
|
368
|
+
}
|
|
369
|
+
commitMount(instance, type, newProps, internalInstanceHandle) {
|
|
370
|
+
}
|
|
371
|
+
commitUpdate(instance, updatePayload, type, oldProps, newProps, internalInstanceHandle) {
|
|
372
|
+
}
|
|
373
|
+
insertBefore(parentInstance, child, beforeChild) {
|
|
374
|
+
}
|
|
375
|
+
insertInContainerBefore(container, child, beforeChild) {
|
|
376
|
+
}
|
|
377
|
+
removeChild(parentInstance, child) {
|
|
378
|
+
}
|
|
379
|
+
removeChildFromContainer(container, child) {
|
|
380
|
+
}
|
|
381
|
+
resetTextContent(instance) {
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
const reconciler = ReactReconciler(new HostConfig());
|
|
385
|
+
const noop = () => void 0;
|
|
386
|
+
function clusterMap(graph, map = /* @__PURE__ */ new Map()) {
|
|
387
|
+
if (graph) {
|
|
388
|
+
if (graph.id) {
|
|
389
|
+
map.set(graph.id, graph);
|
|
390
|
+
}
|
|
391
|
+
for (const s of graph.subgraphs) {
|
|
392
|
+
clusterMap(s, map);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return map;
|
|
396
|
+
}
|
|
397
|
+
function render(element, container) {
|
|
398
|
+
const context = { container };
|
|
399
|
+
reconciler.updateContainer(
|
|
400
|
+
createElement(
|
|
401
|
+
GraphvizContext.Provider,
|
|
402
|
+
{ value: context },
|
|
403
|
+
createElement(
|
|
404
|
+
GraphMap.Provider,
|
|
405
|
+
{ value: clusterMap(container) },
|
|
406
|
+
createElement(
|
|
407
|
+
GraphContainer.Provider,
|
|
408
|
+
{ value: container ?? null },
|
|
409
|
+
container ? createElement(
|
|
410
|
+
CurrentGraph.Provider,
|
|
411
|
+
{ value: container },
|
|
412
|
+
element
|
|
413
|
+
) : element
|
|
414
|
+
)
|
|
415
|
+
)
|
|
416
|
+
),
|
|
417
|
+
reconciler.createContainer(
|
|
418
|
+
{},
|
|
419
|
+
0,
|
|
420
|
+
null,
|
|
421
|
+
true,
|
|
422
|
+
null,
|
|
423
|
+
"@ts-graphviz/react",
|
|
424
|
+
noop,
|
|
425
|
+
null
|
|
426
|
+
),
|
|
427
|
+
null,
|
|
428
|
+
noop
|
|
429
|
+
);
|
|
430
|
+
if (!context.container) {
|
|
431
|
+
throw Error(
|
|
432
|
+
"There are no clusters of container(Subgraph, Digraph, Graph)."
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
return context.container;
|
|
436
|
+
}
|
|
437
|
+
function renderToDot(element, container) {
|
|
438
|
+
return toDot(render(element, container));
|
|
439
|
+
}
|
|
440
|
+
export {
|
|
441
|
+
Digraph,
|
|
442
|
+
Edge,
|
|
443
|
+
Graph,
|
|
444
|
+
GraphPortal,
|
|
445
|
+
Node,
|
|
446
|
+
Subgraph,
|
|
447
|
+
render,
|
|
448
|
+
renderHTMLLike,
|
|
449
|
+
renderToDot,
|
|
450
|
+
useCurrentGraph,
|
|
451
|
+
useDigraph,
|
|
452
|
+
useEdge,
|
|
453
|
+
useGraph,
|
|
454
|
+
useGraphContainer,
|
|
455
|
+
useNode,
|
|
456
|
+
useSubgraph
|
|
457
|
+
};
|
package/package.json
CHANGED
|
@@ -1,81 +1,71 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ts-graphviz/react",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"license": "MIT",
|
|
5
|
-
"author": "kamiazya <yuki@kamiazya.tech>",
|
|
3
|
+
"version": "0.10.0",
|
|
6
4
|
"description": "Graphviz-dot Renderer for React.",
|
|
7
|
-
"repository": {
|
|
8
|
-
"type": "git",
|
|
9
|
-
"url": "https://github.com/ts-graphviz/react.git"
|
|
10
|
-
},
|
|
11
|
-
"bugs": {
|
|
12
|
-
"url": "https://github.com/ts-graphviz/react/issues"
|
|
13
|
-
},
|
|
14
|
-
"funding": {
|
|
15
|
-
"type": "github",
|
|
16
|
-
"url": "https://github.com/sponsors/kamiazya"
|
|
17
|
-
},
|
|
18
5
|
"keywords": [
|
|
19
6
|
"graphviz",
|
|
20
7
|
"dot",
|
|
21
8
|
"react"
|
|
22
9
|
],
|
|
23
|
-
"
|
|
24
|
-
|
|
10
|
+
"homepage": "https://github.com/ts-graphviz/ts-graphviz#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/ts-graphviz/ts-graphviz/issues"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/ts-graphviz/ts-graphviz.git",
|
|
17
|
+
"directory": "packages/react"
|
|
25
18
|
},
|
|
26
|
-
"
|
|
19
|
+
"funding": [
|
|
20
|
+
{
|
|
21
|
+
"type": "github",
|
|
22
|
+
"url": "https://github.com/sponsors/ts-graphviz"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"type": "opencollective",
|
|
26
|
+
"url": "https://opencollective.com/ts-graphviz"
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"author": "kamiazya <yuki@kamiazya.tech>",
|
|
31
|
+
"type": "module",
|
|
27
32
|
"exports": {
|
|
28
|
-
"
|
|
29
|
-
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./lib/react.d.ts",
|
|
35
|
+
"require": "./lib/react.cjs",
|
|
36
|
+
"default": "./lib/react.js"
|
|
37
|
+
},
|
|
38
|
+
"./package.json": "./package.json"
|
|
30
39
|
},
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
|
|
40
|
+
"main": "./lib/react.js",
|
|
41
|
+
"types": "./lib/react.d.ts",
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"react-reconciler": "^0.29.0",
|
|
44
|
+
"@ts-graphviz/common": "^2.1.0",
|
|
45
|
+
"ts-graphviz": "^2.1.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@testing-library/react": "^14.2.1",
|
|
49
|
+
"@types/react": "^18.2.65",
|
|
50
|
+
"@types/react-dom": "^18.2.22",
|
|
51
|
+
"@types/react-reconciler": "^0.26.7",
|
|
52
|
+
"react": "^18.2.0",
|
|
53
|
+
"react-dom": "^18.2.0",
|
|
54
|
+
"react-test-renderer": "^18.2.0",
|
|
55
|
+
"typescript": "^5.3.3",
|
|
56
|
+
"vite": "^5.1.3",
|
|
57
|
+
"vite-plugin-dts": "^3.7.3"
|
|
38
58
|
},
|
|
39
59
|
"peerDependencies": {
|
|
40
|
-
"react": ">=
|
|
60
|
+
"react": ">=18",
|
|
61
|
+
"react-dom": ">=18"
|
|
41
62
|
},
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"react-reconciler": "^0.26.2",
|
|
46
|
-
"ts-graphviz": "^0.16.0"
|
|
63
|
+
"publishConfig": {
|
|
64
|
+
"access": "public",
|
|
65
|
+
"provenance": true
|
|
47
66
|
},
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"@testing-library/react-hooks": "^5.1.2",
|
|
52
|
-
"@types/prop-types": "^15.7.3",
|
|
53
|
-
"@types/react": "^17.0.5",
|
|
54
|
-
"@types/react-dom": "^17.0.5",
|
|
55
|
-
"@types/react-reconciler": "^0.26.1",
|
|
56
|
-
"@typescript-eslint/eslint-plugin": "^4.23.0",
|
|
57
|
-
"@typescript-eslint/parser": "^4.23.0",
|
|
58
|
-
"eslint": "^7.26.0",
|
|
59
|
-
"eslint-config-airbnb": "^18.2.1",
|
|
60
|
-
"eslint-config-prettier": "^8.3.0",
|
|
61
|
-
"eslint-plugin-import": "^2.23.1",
|
|
62
|
-
"eslint-plugin-jest": "^24.3.6",
|
|
63
|
-
"eslint-plugin-jsx-a11y": "^6.4.1",
|
|
64
|
-
"eslint-plugin-prettier": "^3.4.0",
|
|
65
|
-
"eslint-plugin-react": "^7.23.2",
|
|
66
|
-
"eslint-plugin-react-hooks": "^4.2.0",
|
|
67
|
-
"jest": "^26.6.3",
|
|
68
|
-
"jest-graphviz": "^0.4.0",
|
|
69
|
-
"prettier": "^2.3.0",
|
|
70
|
-
"react": "^17.0.2",
|
|
71
|
-
"react-test-renderer": "^17.0.2",
|
|
72
|
-
"rollup": "^2.48.0",
|
|
73
|
-
"rollup-plugin-delete": "^2.0.0",
|
|
74
|
-
"rollup-plugin-dts": "^3.0.1",
|
|
75
|
-
"rollup-plugin-terser": "^7.0.2",
|
|
76
|
-
"rollup-plugin-typescript2": "^0.30.0",
|
|
77
|
-
"ts-jest": "^26.5.6",
|
|
78
|
-
"ts-node": "^9.1.1",
|
|
79
|
-
"typescript": "^4.2.4"
|
|
67
|
+
"scripts": {
|
|
68
|
+
"build": "vite build",
|
|
69
|
+
"example": "ts-node example/example"
|
|
80
70
|
}
|
|
81
|
-
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { render } from '../render.js';
|
|
3
|
+
import { Digraph } from './Digraph.js';
|
|
4
|
+
|
|
5
|
+
describe('Digraph', () => {
|
|
6
|
+
test('An error occurs when duplicate <Digraph />', () => {
|
|
7
|
+
expect(() => {
|
|
8
|
+
render(
|
|
9
|
+
<Digraph>
|
|
10
|
+
<Digraph />
|
|
11
|
+
</Digraph>,
|
|
12
|
+
);
|
|
13
|
+
}).toThrowErrorMatchingInlineSnapshot(`
|
|
14
|
+
[Error: RootCluster is duplicated.
|
|
15
|
+
Use only one of Digraph and Graph.]
|
|
16
|
+
`);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type FC, useEffect } from 'react';
|
|
2
|
+
import { CurrentGraph } from '../contexts/CurrentGraph.js';
|
|
3
|
+
import { GraphContainer } from '../contexts/GraphContainer.js';
|
|
4
|
+
import { useDigraph } from '../hooks/useDigraph.js';
|
|
5
|
+
import { useGraphContainer } from '../hooks/useGraphContainer.js';
|
|
6
|
+
import { useGraphMap } from '../hooks/useGraphMap.js';
|
|
7
|
+
import { useRenderedID } from '../hooks/useRenderedID.js';
|
|
8
|
+
import type { RootGraphProps } from '../types.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* `Digraph` component.
|
|
12
|
+
*/
|
|
13
|
+
export const Digraph: FC<RootGraphProps> = ({
|
|
14
|
+
children,
|
|
15
|
+
label,
|
|
16
|
+
...options
|
|
17
|
+
}) => {
|
|
18
|
+
const container = useGraphContainer();
|
|
19
|
+
if (container !== null) {
|
|
20
|
+
throw Error(
|
|
21
|
+
'RootCluster is duplicated.\nUse only one of Digraph and Graph.',
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
const renderedLabel = useRenderedID(label);
|
|
25
|
+
if (renderedLabel !== undefined)
|
|
26
|
+
Object.assign(options, { label: renderedLabel });
|
|
27
|
+
const digraph = useDigraph(options);
|
|
28
|
+
const clusters = useGraphMap();
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (digraph.id !== undefined) {
|
|
31
|
+
clusters.set(digraph.id, digraph);
|
|
32
|
+
}
|
|
33
|
+
}, [clusters, digraph]);
|
|
34
|
+
return (
|
|
35
|
+
<GraphContainer.Provider value={digraph}>
|
|
36
|
+
<CurrentGraph.Provider value={digraph}>{children}</CurrentGraph.Provider>
|
|
37
|
+
</GraphContainer.Provider>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
Digraph.displayName = 'Digraph';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
import { useEdge } from '../hooks/useEdge.js';
|
|
3
|
+
import { useRenderedID } from '../hooks/useRenderedID.js';
|
|
4
|
+
import type { EdgeProps } from '../types.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* `Edge` component.
|
|
8
|
+
*/
|
|
9
|
+
export const Edge: FC<EdgeProps> = ({ targets, label, ...options }) => {
|
|
10
|
+
const renderedLabel = useRenderedID(label);
|
|
11
|
+
if (renderedLabel !== undefined)
|
|
12
|
+
Object.assign(options, { label: renderedLabel });
|
|
13
|
+
useEdge(targets, options);
|
|
14
|
+
return null;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
Edge.displayName = 'Edge';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { render } from '../render.js';
|
|
3
|
+
import { Graph } from './Graph.js';
|
|
4
|
+
|
|
5
|
+
describe('Graph', () => {
|
|
6
|
+
test('An error occurs when duplicate <Graph />', () => {
|
|
7
|
+
expect(() => {
|
|
8
|
+
render(
|
|
9
|
+
<Graph>
|
|
10
|
+
<Graph />
|
|
11
|
+
</Graph>,
|
|
12
|
+
);
|
|
13
|
+
}).toThrowErrorMatchingInlineSnapshot(`
|
|
14
|
+
[Error: RootCluster is duplicated.
|
|
15
|
+
Use only one of Digraph and Graph.]
|
|
16
|
+
`);
|
|
17
|
+
});
|
|
18
|
+
});
|