@ts-graphviz/react 0.9.1 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +86 -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 +32 -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
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type FC, useEffect } from 'react';
|
|
2
|
+
import { CurrentGraph } from '../contexts/CurrentGraph.js';
|
|
3
|
+
import { GraphContainer } from '../contexts/GraphContainer.js';
|
|
4
|
+
import { useGraph } from '../hooks/useGraph.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
|
+
* `Graph` component.
|
|
11
|
+
*/
|
|
12
|
+
export const Graph: FC<RootGraphProps> = ({ children, label, ...options }) => {
|
|
13
|
+
const container = useGraphContainer();
|
|
14
|
+
if (container !== null) {
|
|
15
|
+
throw Error(
|
|
16
|
+
'RootCluster is duplicated.\nUse only one of Digraph and Graph.',
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
const renderedLabel = useRenderedID(label);
|
|
20
|
+
if (renderedLabel !== undefined)
|
|
21
|
+
Object.assign(options, { label: renderedLabel });
|
|
22
|
+
const graph = useGraph(options);
|
|
23
|
+
const clusters = useGraphMap();
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (graph.id !== undefined) {
|
|
26
|
+
clusters.set(graph.id, graph);
|
|
27
|
+
}
|
|
28
|
+
}, [clusters, graph]);
|
|
29
|
+
return (
|
|
30
|
+
<GraphContainer.Provider value={graph}>
|
|
31
|
+
<CurrentGraph.Provider value={graph}>{children}</CurrentGraph.Provider>
|
|
32
|
+
</GraphContainer.Provider>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
Graph.displayName = 'Graph';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type FC, useContext, useMemo } from 'react';
|
|
2
|
+
import { CurrentGraph } from '../contexts/CurrentGraph.js';
|
|
3
|
+
import { GraphMap } from '../contexts/GraphMap.js';
|
|
4
|
+
import { useGraphContainer } from '../hooks/useGraphContainer.js';
|
|
5
|
+
import type { GraphPortalProps } from '../types.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* GraphPortal component.
|
|
9
|
+
*/
|
|
10
|
+
export const GraphPortal: FC<GraphPortalProps> = ({ children, id }) => {
|
|
11
|
+
const container = useGraphContainer();
|
|
12
|
+
const map = useContext(GraphMap);
|
|
13
|
+
const cluster = useMemo(
|
|
14
|
+
() => (id ? map.get(id) ?? container : container),
|
|
15
|
+
[container, map, id],
|
|
16
|
+
);
|
|
17
|
+
return (
|
|
18
|
+
<CurrentGraph.Provider value={cluster}>{children}</CurrentGraph.Provider>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
GraphPortal.displayName = 'GraphPortal';
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { renderToDot } from '../render.js';
|
|
3
|
+
import { Digraph } from './Digraph.js';
|
|
4
|
+
import { Node } from './Node.js';
|
|
5
|
+
|
|
6
|
+
describe('Node', () => {
|
|
7
|
+
test('pass without optional props and render correctly', () => {
|
|
8
|
+
const dot = renderToDot(
|
|
9
|
+
<Digraph>
|
|
10
|
+
<Node id="foo" />
|
|
11
|
+
</Digraph>,
|
|
12
|
+
);
|
|
13
|
+
expect(dot).toMatchInlineSnapshot(`
|
|
14
|
+
"digraph {
|
|
15
|
+
"foo";
|
|
16
|
+
}"
|
|
17
|
+
`);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('pass label with string and render correctly', () => {
|
|
21
|
+
const dot = renderToDot(
|
|
22
|
+
<Digraph>
|
|
23
|
+
<Node id="foo" label="label test" />
|
|
24
|
+
</Digraph>,
|
|
25
|
+
);
|
|
26
|
+
expect(dot).toMatchInlineSnapshot(`
|
|
27
|
+
"digraph {
|
|
28
|
+
"foo" [
|
|
29
|
+
label = "label test";
|
|
30
|
+
];
|
|
31
|
+
}"
|
|
32
|
+
`);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('pass label with HTMLLike ReactElement and render correctly', () => {
|
|
36
|
+
const dot = renderToDot(
|
|
37
|
+
<Digraph>
|
|
38
|
+
<Node
|
|
39
|
+
id="foo"
|
|
40
|
+
label={
|
|
41
|
+
<dot:table border={0} cellborder={1} cellSpacing={0}>
|
|
42
|
+
<dot:tr>
|
|
43
|
+
<dot:td>left</dot:td>
|
|
44
|
+
<dot:td port="m">middle</dot:td>
|
|
45
|
+
<dot:td port="r">right</dot:td>
|
|
46
|
+
</dot:tr>
|
|
47
|
+
</dot:table>
|
|
48
|
+
}
|
|
49
|
+
/>
|
|
50
|
+
</Digraph>,
|
|
51
|
+
);
|
|
52
|
+
expect(dot).toMatchInlineSnapshot(`
|
|
53
|
+
"digraph {
|
|
54
|
+
"foo" [
|
|
55
|
+
label = <<table border="0" cellborder="1" cellSpacing="0"><tr><td>left</td><td port="m">middle</td><td port="r">right</td></tr></table>>;
|
|
56
|
+
];
|
|
57
|
+
}"
|
|
58
|
+
`);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('pass xlabel with string and render correctly', () => {
|
|
62
|
+
const dot = renderToDot(
|
|
63
|
+
<Digraph>
|
|
64
|
+
<Node id="foo" xlabel="xlabel test" />
|
|
65
|
+
</Digraph>,
|
|
66
|
+
);
|
|
67
|
+
expect(dot).toMatchInlineSnapshot(`
|
|
68
|
+
"digraph {
|
|
69
|
+
"foo" [
|
|
70
|
+
xlabel = "xlabel test";
|
|
71
|
+
];
|
|
72
|
+
}"
|
|
73
|
+
`);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('pass xlabel with HTMLLike ReactElement and render correctly', () => {
|
|
77
|
+
const dot = renderToDot(
|
|
78
|
+
<Digraph>
|
|
79
|
+
<Node
|
|
80
|
+
id="foo"
|
|
81
|
+
xlabel={
|
|
82
|
+
<dot:table border={0} cellborder={1} cellSpacing={0}>
|
|
83
|
+
<dot:tr>
|
|
84
|
+
<dot:td>left</dot:td>
|
|
85
|
+
<dot:td port="m">middle</dot:td>
|
|
86
|
+
<dot:td port="r">right</dot:td>
|
|
87
|
+
</dot:tr>
|
|
88
|
+
</dot:table>
|
|
89
|
+
}
|
|
90
|
+
/>
|
|
91
|
+
</Digraph>,
|
|
92
|
+
);
|
|
93
|
+
expect(dot).toMatchInlineSnapshot(`
|
|
94
|
+
"digraph {
|
|
95
|
+
"foo" [
|
|
96
|
+
xlabel = <<table border="0" cellborder="1" cellSpacing="0"><tr><td>left</td><td port="m">middle</td><td port="r">right</td></tr></table>>;
|
|
97
|
+
];
|
|
98
|
+
}"
|
|
99
|
+
`);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
import { useNode } from '../hooks/useNode.js';
|
|
3
|
+
import { useRenderedID } from '../hooks/useRenderedID.js';
|
|
4
|
+
import type { NodeProps } from '../types.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* `Node` component.
|
|
8
|
+
*/
|
|
9
|
+
export const Node: FC<NodeProps> = ({ id, label, xlabel, ...options }) => {
|
|
10
|
+
const renderedLabel = useRenderedID(label);
|
|
11
|
+
const renderedXlabel = useRenderedID(xlabel);
|
|
12
|
+
|
|
13
|
+
if (renderedLabel !== undefined)
|
|
14
|
+
Object.assign(options, { label: renderedLabel });
|
|
15
|
+
if (renderedXlabel !== undefined)
|
|
16
|
+
Object.assign(options, { xlabel: renderedXlabel });
|
|
17
|
+
useNode(id, options);
|
|
18
|
+
return null;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
Node.displayName = 'Node';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type FC, useEffect } from 'react';
|
|
2
|
+
import { CurrentGraph } from '../contexts/CurrentGraph.js';
|
|
3
|
+
import { useGraphMap } from '../hooks/useGraphMap.js';
|
|
4
|
+
import { useRenderedID } from '../hooks/useRenderedID.js';
|
|
5
|
+
import { useSubgraph } from '../hooks/useSubgraph.js';
|
|
6
|
+
import type { SubgraphProps } from '../types.js';
|
|
7
|
+
/**
|
|
8
|
+
* `Subgraph` component.
|
|
9
|
+
*/
|
|
10
|
+
export const Subgraph: FC<SubgraphProps> = ({
|
|
11
|
+
children,
|
|
12
|
+
label,
|
|
13
|
+
...options
|
|
14
|
+
}) => {
|
|
15
|
+
const renderedLabel = useRenderedID(label);
|
|
16
|
+
if (renderedLabel !== undefined)
|
|
17
|
+
Object.assign(options, { label: renderedLabel });
|
|
18
|
+
const subgraph = useSubgraph(options);
|
|
19
|
+
const clusters = useGraphMap();
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (subgraph.id !== undefined) {
|
|
22
|
+
clusters.set(subgraph.id, subgraph);
|
|
23
|
+
}
|
|
24
|
+
}, [subgraph, clusters]);
|
|
25
|
+
return (
|
|
26
|
+
<CurrentGraph.Provider value={subgraph}>{children}</CurrentGraph.Provider>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
Subgraph.displayName = 'Subgraph';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createContext } from 'react';
|
|
2
|
+
import type { GraphBaseModel } from 'ts-graphviz';
|
|
3
|
+
|
|
4
|
+
export interface Context<T extends GraphBaseModel = GraphBaseModel> {
|
|
5
|
+
container?: T;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const GraphvizContext = createContext<Context>(
|
|
9
|
+
null as unknown as Context,
|
|
10
|
+
);
|
|
11
|
+
GraphvizContext.displayName = 'GraphvizContext';
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { ComponentProps, FC, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { Digraph } from '../../components/Digraph.js';
|
|
4
|
+
import { Graph } from '../../components/Graph.js';
|
|
5
|
+
import { Subgraph } from '../../components/Subgraph.js';
|
|
6
|
+
import { GraphvizContext } from '../../contexts/GraphvizContext.js';
|
|
7
|
+
|
|
8
|
+
export const context =
|
|
9
|
+
(): FC<{ children: ReactNode }> =>
|
|
10
|
+
({ children }) => {
|
|
11
|
+
return (
|
|
12
|
+
<GraphvizContext.Provider value={{}}>{children}</GraphvizContext.Provider>
|
|
13
|
+
);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const digraph =
|
|
17
|
+
(props: ComponentProps<typeof Digraph> = {}): FC<{ children: ReactNode }> =>
|
|
18
|
+
({ children }) => {
|
|
19
|
+
return (
|
|
20
|
+
<GraphvizContext.Provider value={{}}>
|
|
21
|
+
<Digraph {...props}>{children}</Digraph>
|
|
22
|
+
</GraphvizContext.Provider>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const digraphInSubgraph =
|
|
27
|
+
(props: ComponentProps<typeof Subgraph> = {}): FC<{ children: ReactNode }> =>
|
|
28
|
+
({ children }) => {
|
|
29
|
+
return (
|
|
30
|
+
<GraphvizContext.Provider value={{}}>
|
|
31
|
+
<Digraph>
|
|
32
|
+
<Subgraph {...props}>{children}</Subgraph>
|
|
33
|
+
</Digraph>
|
|
34
|
+
</GraphvizContext.Provider>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const graph =
|
|
39
|
+
(props: ComponentProps<typeof Graph> = {}): FC<{ children: ReactNode }> =>
|
|
40
|
+
({ children }) => {
|
|
41
|
+
return (
|
|
42
|
+
<GraphvizContext.Provider value={{}}>
|
|
43
|
+
<Graph {...props}>{children}</Graph>
|
|
44
|
+
</GraphvizContext.Provider>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const graphInSubgraph =
|
|
49
|
+
(props: ComponentProps<typeof Subgraph> = {}): FC<{ children: ReactNode }> =>
|
|
50
|
+
({ children }) => {
|
|
51
|
+
return (
|
|
52
|
+
<GraphvizContext.Provider value={{}}>
|
|
53
|
+
<Graph>
|
|
54
|
+
<Subgraph {...props}>{children}</Subgraph>
|
|
55
|
+
</Graph>
|
|
56
|
+
</GraphvizContext.Provider>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
// Note: In order to suppress warnings during test execution, pure.js is imported.
|
|
3
|
+
import { renderHook } from '@testing-library/react/pure.js';
|
|
4
|
+
import { Digraph, Graph, Subgraph } from 'ts-graphviz';
|
|
5
|
+
import { describe, expect, test } from 'vitest';
|
|
6
|
+
import {
|
|
7
|
+
digraph,
|
|
8
|
+
digraphInSubgraph,
|
|
9
|
+
graph,
|
|
10
|
+
graphInSubgraph,
|
|
11
|
+
} from './__tests__/wrapper.js';
|
|
12
|
+
import { useCurrentGraph } from './useCurrentGraph.js';
|
|
13
|
+
|
|
14
|
+
describe('useCurrentGraph', () => {
|
|
15
|
+
describe('get parent cluster', () => {
|
|
16
|
+
test('returns Diagram instance in digraph wrapper', () => {
|
|
17
|
+
const { result } = renderHook(() => useCurrentGraph(), {
|
|
18
|
+
wrapper: digraph(),
|
|
19
|
+
});
|
|
20
|
+
expect(result.current).toBeInstanceOf(Digraph);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('returns Graph instance in graph wrapper', () => {
|
|
24
|
+
const { result } = renderHook(() => useCurrentGraph(), {
|
|
25
|
+
wrapper: graph(),
|
|
26
|
+
});
|
|
27
|
+
expect(result.current).toBeInstanceOf(Graph);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('returns Subgraph instance in graphInSubgraph wrapper', () => {
|
|
31
|
+
const { result } = renderHook(() => useCurrentGraph(), {
|
|
32
|
+
wrapper: graphInSubgraph(),
|
|
33
|
+
});
|
|
34
|
+
expect(result.current).toBeInstanceOf(Subgraph);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('returns Subgraph instance in digraphInSubgraph wrapper', () => {
|
|
38
|
+
const { result } = renderHook(() => useCurrentGraph(), {
|
|
39
|
+
wrapper: digraphInSubgraph(),
|
|
40
|
+
});
|
|
41
|
+
expect(result.current).toBeInstanceOf(Subgraph);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('An error occurs when called outside the cluster', () => {
|
|
46
|
+
try {
|
|
47
|
+
expect(() => {
|
|
48
|
+
renderHook(() => useCurrentGraph());
|
|
49
|
+
}).toThrowErrorMatchingInlineSnapshot(
|
|
50
|
+
`[Error: useCurrentGraph must be called within a cluster such as Digraph, Graph, Subgraph.]`,
|
|
51
|
+
);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
console.error(e);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useContext } from 'react';
|
|
2
|
+
import type { GraphBaseModel } from 'ts-graphviz';
|
|
3
|
+
import { CurrentGraph } from '../contexts/CurrentGraph.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Hook to get the current cluster(Digraph, Graph or Subgraph).
|
|
7
|
+
*
|
|
8
|
+
* @throws If it is out of the context of Cluster, it throws an exception.
|
|
9
|
+
*/
|
|
10
|
+
export function useCurrentGraph(): GraphBaseModel {
|
|
11
|
+
const cluster = useContext(CurrentGraph);
|
|
12
|
+
if (cluster === null) {
|
|
13
|
+
throw Error(
|
|
14
|
+
'useCurrentGraph must be called within a cluster such as Digraph, Graph, Subgraph.',
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
return cluster;
|
|
18
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { renderHook } from '@testing-library/react';
|
|
3
|
+
import { Digraph } from 'ts-graphviz';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
import { context } from './__tests__/wrapper.js';
|
|
6
|
+
import { useDigraph } from './useDigraph.js';
|
|
7
|
+
|
|
8
|
+
describe('useDigraph', () => {
|
|
9
|
+
it('returns Digraph instance', () => {
|
|
10
|
+
const { result } = renderHook(() => useDigraph(), {
|
|
11
|
+
wrapper: context(),
|
|
12
|
+
});
|
|
13
|
+
expect(result.current).toBeInstanceOf(Digraph);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useEffect, useMemo } from 'react';
|
|
2
|
+
import { Digraph, type RootGraphModel } from 'ts-graphviz';
|
|
3
|
+
import type { RootGraphOptions } from '../types.js';
|
|
4
|
+
import { useGraphAttributes } from './useGraphAttributes.js';
|
|
5
|
+
import { useGraphvizContext } from './useGraphvizContext.js';
|
|
6
|
+
import { useHasComment } from './useHasComment.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* `useDigraph` is a hook that creates an instance of Digraph
|
|
10
|
+
* according to the object given by props.
|
|
11
|
+
*/
|
|
12
|
+
export function useDigraph(options: RootGraphOptions = {}): RootGraphModel {
|
|
13
|
+
const { id, comment, edge, node, graph, ...attributes } = options;
|
|
14
|
+
const context = useGraphvizContext<RootGraphModel>();
|
|
15
|
+
const digraph = useMemo(() => {
|
|
16
|
+
const g = new Digraph(id);
|
|
17
|
+
context.container = g;
|
|
18
|
+
g.comment = comment;
|
|
19
|
+
g.apply(attributes);
|
|
20
|
+
g.attributes.node.apply(node ?? {});
|
|
21
|
+
g.attributes.edge.apply(edge ?? {});
|
|
22
|
+
g.attributes.graph.apply(graph ?? {});
|
|
23
|
+
return g;
|
|
24
|
+
}, [context, id, comment, edge, node, graph, attributes]);
|
|
25
|
+
useHasComment(digraph, comment);
|
|
26
|
+
useGraphAttributes(digraph, attributes, { edge, node, graph });
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
return (): void => {
|
|
29
|
+
context.container = undefined;
|
|
30
|
+
};
|
|
31
|
+
}, [context]);
|
|
32
|
+
return digraph;
|
|
33
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { renderHook } from '@testing-library/react';
|
|
3
|
+
import { Edge, type EdgeTargetLikeTuple } from 'ts-graphviz';
|
|
4
|
+
import { describe, expect, it, test } from 'vitest';
|
|
5
|
+
import { digraph, graph } from './__tests__/wrapper.js';
|
|
6
|
+
import { useEdge } from './useEdge.js';
|
|
7
|
+
|
|
8
|
+
describe('useEdge', () => {
|
|
9
|
+
it('returns Edge instance in digraph wrapper', () => {
|
|
10
|
+
const { result } = renderHook(() => useEdge(['a', 'b']), {
|
|
11
|
+
wrapper: digraph(),
|
|
12
|
+
});
|
|
13
|
+
expect(result.current).toBeInstanceOf(Edge);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('returns an Edge instance in a digraph wrapper for grouped edge targets', () => {
|
|
17
|
+
const { result } = renderHook(() => useEdge(['a', ['b1', 'b2'], 'c']), {
|
|
18
|
+
wrapper: digraph(),
|
|
19
|
+
});
|
|
20
|
+
expect(result.current).toBeInstanceOf(Edge);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('returns Edge instance in graph wrapper', () => {
|
|
24
|
+
const { result } = renderHook(() => useEdge(['a', 'b']), {
|
|
25
|
+
wrapper: graph(),
|
|
26
|
+
});
|
|
27
|
+
expect(result.current).toBeInstanceOf(Edge);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('returns an Edge instance in a graph wrapper for grouped edge targets', () => {
|
|
31
|
+
const { result } = renderHook(() => useEdge(['a', ['b1', 'b2'], 'c']), {
|
|
32
|
+
wrapper: graph(),
|
|
33
|
+
});
|
|
34
|
+
expect(result.current).toBeInstanceOf(Edge);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('throw error if the target is less than 2', () => {
|
|
38
|
+
expect(() => {
|
|
39
|
+
renderHook(() => useEdge(['a'] as any as EdgeTargetLikeTuple), {
|
|
40
|
+
wrapper: graph(),
|
|
41
|
+
});
|
|
42
|
+
}).toThrowErrorMatchingInlineSnapshot(
|
|
43
|
+
`[Error: Edges must have at least 2 targets.]`,
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { useEffect, useMemo } from 'react';
|
|
2
|
+
import type { EdgeModel, EdgeTargetLikeTuple } from 'ts-graphviz';
|
|
3
|
+
import type { EdgeOptions } from '../types.js';
|
|
4
|
+
import { useCurrentGraph } from './useCurrentGraph.js';
|
|
5
|
+
import { useHasAttributes } from './useHasAttributes.js';
|
|
6
|
+
import { useHasComment } from './useHasComment.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* `useEdge` is a hook that creates an instance of Edge
|
|
10
|
+
* according to the object given by props.
|
|
11
|
+
*/
|
|
12
|
+
export function useEdge(
|
|
13
|
+
targets: EdgeTargetLikeTuple,
|
|
14
|
+
props: EdgeOptions = {},
|
|
15
|
+
): EdgeModel {
|
|
16
|
+
const { comment, ...attributes } = props;
|
|
17
|
+
const cluster = useCurrentGraph();
|
|
18
|
+
if (targets.length < 2) {
|
|
19
|
+
throw Error('Edges must have at least 2 targets.');
|
|
20
|
+
}
|
|
21
|
+
const edge = useMemo(() => {
|
|
22
|
+
const e = cluster.createEdge(targets);
|
|
23
|
+
e.comment = comment;
|
|
24
|
+
e.attributes.apply(attributes);
|
|
25
|
+
return e;
|
|
26
|
+
}, [cluster, targets, comment, attributes]);
|
|
27
|
+
useHasComment(edge, comment);
|
|
28
|
+
useHasAttributes(edge, attributes);
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
return (): void => {
|
|
31
|
+
cluster.removeEdge(edge);
|
|
32
|
+
};
|
|
33
|
+
}, [cluster, edge]);
|
|
34
|
+
return edge;
|
|
35
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { renderHook } from '@testing-library/react';
|
|
3
|
+
import { Graph } from 'ts-graphviz';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
import { context } from './__tests__/wrapper.js';
|
|
6
|
+
import { useGraph } from './useGraph.js';
|
|
7
|
+
|
|
8
|
+
describe('useGraph', () => {
|
|
9
|
+
it('returns Graph instance', () => {
|
|
10
|
+
const { result } = renderHook(() => useGraph(), {
|
|
11
|
+
wrapper: context(),
|
|
12
|
+
});
|
|
13
|
+
expect(result.current).toBeInstanceOf(Graph);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useEffect, useMemo } from 'react';
|
|
2
|
+
import { Graph, type RootGraphModel } from 'ts-graphviz';
|
|
3
|
+
import type { RootGraphOptions } from '../types.js';
|
|
4
|
+
import { useGraphAttributes } from './useGraphAttributes.js';
|
|
5
|
+
import { useGraphvizContext } from './useGraphvizContext.js';
|
|
6
|
+
import { useHasComment } from './useHasComment.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* `useGraph` is a hook that creates an instance of Graph
|
|
10
|
+
* according to the object given by props.
|
|
11
|
+
*/
|
|
12
|
+
export function useGraph(options: RootGraphOptions = {}): RootGraphModel {
|
|
13
|
+
const { id, comment, edge, node, graph, ...attributes } = options;
|
|
14
|
+
const context = useGraphvizContext<RootGraphModel>();
|
|
15
|
+
const memoGraph = useMemo(() => {
|
|
16
|
+
const g = new Graph(id);
|
|
17
|
+
context.container = g;
|
|
18
|
+
g.comment = comment;
|
|
19
|
+
g.apply(attributes);
|
|
20
|
+
g.attributes.node.apply(node ?? {});
|
|
21
|
+
g.attributes.edge.apply(edge ?? {});
|
|
22
|
+
g.attributes.graph.apply(graph ?? {});
|
|
23
|
+
return g;
|
|
24
|
+
}, [context, id, comment, edge, node, graph, attributes]);
|
|
25
|
+
useHasComment(memoGraph, comment);
|
|
26
|
+
useGraphAttributes(memoGraph, attributes, { edge, node, graph });
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
return (): void => {
|
|
29
|
+
context.container = undefined;
|
|
30
|
+
};
|
|
31
|
+
}, [context]);
|
|
32
|
+
return memoGraph;
|
|
33
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import type {
|
|
3
|
+
AttributeKey,
|
|
4
|
+
AttributesObject,
|
|
5
|
+
GraphBaseModel,
|
|
6
|
+
} from 'ts-graphviz';
|
|
7
|
+
import type { GraphBaseAttributesProps } from '../types.js';
|
|
8
|
+
|
|
9
|
+
export function useGraphAttributes<T extends AttributeKey>(
|
|
10
|
+
cluster: GraphBaseModel<T>,
|
|
11
|
+
attributes: AttributesObject<T>,
|
|
12
|
+
{ edge, node, graph }: GraphBaseAttributesProps,
|
|
13
|
+
): void {
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
cluster.clear();
|
|
16
|
+
cluster.apply(attributes);
|
|
17
|
+
}, [cluster, attributes]);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
cluster.attributes.node.clear();
|
|
20
|
+
cluster.attributes.node.apply(node ?? {});
|
|
21
|
+
}, [cluster, node]);
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
cluster.attributes.edge.clear();
|
|
25
|
+
cluster.attributes.edge.apply(edge ?? {});
|
|
26
|
+
}, [cluster, edge]);
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
cluster.attributes.graph.clear();
|
|
30
|
+
cluster.attributes.graph.apply(graph ?? {});
|
|
31
|
+
}, [cluster, graph]);
|
|
32
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
// Note: In order to suppress warnings during test execution, pure.js is imported.
|
|
4
|
+
import { renderHook } from '@testing-library/react/pure.js';
|
|
5
|
+
import { Digraph, Graph } from 'ts-graphviz';
|
|
6
|
+
import { describe, expect, test } from 'vitest';
|
|
7
|
+
import {
|
|
8
|
+
digraph,
|
|
9
|
+
digraphInSubgraph,
|
|
10
|
+
graph,
|
|
11
|
+
graphInSubgraph,
|
|
12
|
+
} from './__tests__/wrapper.js';
|
|
13
|
+
import { useGraphContainer } from './useGraphContainer.js';
|
|
14
|
+
|
|
15
|
+
describe('useGraphContainer', () => {
|
|
16
|
+
describe('get root cluster', () => {
|
|
17
|
+
test('returns Diagram instance in digraph wrapper', () => {
|
|
18
|
+
const { result } = renderHook(() => useGraphContainer(), {
|
|
19
|
+
wrapper: digraph(),
|
|
20
|
+
});
|
|
21
|
+
expect(result.current).toBeInstanceOf(Digraph);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('returns Graph instance in graph wrapper', () => {
|
|
25
|
+
const { result } = renderHook(() => useGraphContainer(), {
|
|
26
|
+
wrapper: graph(),
|
|
27
|
+
});
|
|
28
|
+
expect(result.current).toBeInstanceOf(Graph);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('returns Graph instance in graphInSubgraph wrapper', () => {
|
|
32
|
+
const { result } = renderHook(() => useGraphContainer(), {
|
|
33
|
+
wrapper: graphInSubgraph(),
|
|
34
|
+
});
|
|
35
|
+
expect(result.current).toBeInstanceOf(Graph);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('returns Digraph instance in digraphInSubgraph wrapper', () => {
|
|
39
|
+
const { result } = renderHook(() => useGraphContainer(), {
|
|
40
|
+
wrapper: digraphInSubgraph(),
|
|
41
|
+
});
|
|
42
|
+
expect(result.current).toBeInstanceOf(Digraph);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { useContext } from 'react';
|
|
2
|
+
import type { GraphBaseModel } from 'ts-graphviz';
|
|
3
|
+
import { GraphContainer } from '../contexts/GraphContainer.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Return the cluster of container.
|
|
7
|
+
*/
|
|
8
|
+
export function useGraphContainer(): GraphBaseModel | null {
|
|
9
|
+
return useContext(GraphContainer);
|
|
10
|
+
}
|