@docusaurus/theme-mermaid 3.0.0-alpha.0 → 3.0.0-beta.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.
@@ -4,9 +4,12 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
- import { type MermaidConfig } from 'mermaid';
7
+ import type { RenderResult, MermaidConfig } from 'mermaid';
8
8
  import type { ThemeConfig } from '@docusaurus/theme-mermaid';
9
9
  export declare const MermaidContainerClassName = "docusaurus-mermaid-container";
10
10
  export declare function useMermaidThemeConfig(): ThemeConfig['mermaid'];
11
11
  export declare function useMermaidConfig(): MermaidConfig;
12
- export declare function useMermaidSvg(txt: string, mermaidConfigParam?: MermaidConfig): string;
12
+ export declare function useMermaidRenderResult({ text, config: providedConfig, }: {
13
+ text: string;
14
+ config?: MermaidConfig;
15
+ }): RenderResult | null;
@@ -4,7 +4,7 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
- import { useMemo } from 'react';
7
+ import { useState, useEffect, useMemo, useRef } from 'react';
8
8
  import { useColorMode, useThemeConfig } from '@docusaurus/theme-common';
9
9
  import mermaid from 'mermaid';
10
10
  // Stable className to allow users to easily target with CSS
@@ -19,42 +19,65 @@ export function useMermaidConfig() {
19
19
  const { options } = mermaidThemeConfig;
20
20
  return useMemo(() => ({ startOnLoad: false, ...options, theme }), [theme, options]);
21
21
  }
22
- export function useMermaidSvg(txt, mermaidConfigParam) {
22
+ function useMermaidId() {
23
+ /*
24
+ Random client-only id, we don't care much but mermaid want an id so...
25
+ Note: Mermaid doesn't like values provided by Rect.useId() and throws
26
+ */
27
+ // return useId(); // tried that, doesn't work ('#d:re:' is not a valid selector.)
28
+ return useRef(`mermaid-svg-${Math.round(Math.random() * 10000000)}`).current;
29
+ }
30
+ async function renderMermaid({ id, text, config, }) {
31
+ /*
32
+ Mermaid API is really weird :s
33
+ It is a big mutable singleton with multiple config levels
34
+ Note: most recent API type definitions are missing
35
+
36
+ There are 2 kind of configs:
37
+
38
+ - siteConfig: some kind of global/protected shared config
39
+ you can only set with "initialize"
40
+
41
+ - config/currentConfig
42
+ the config the renderer will use
43
+ it is reset to siteConfig before each render
44
+ but it can be altered by the mermaid txt content itself through directives
45
+
46
+ To use a new mermaid config (on colorMode change for example) we should
47
+ update siteConfig, and it can only be done with initialize()
48
+ */
49
+ mermaid.mermaidAPI.initialize(config);
50
+ try {
51
+ return await mermaid.render(id, text);
52
+ }
53
+ catch (e) {
54
+ // Because Mermaid add a weird SVG/Message to the DOM on error
55
+ // https://github.com/mermaid-js/mermaid/issues/3205#issuecomment-1719620183
56
+ document.querySelector(`#d${id}`)?.remove();
57
+ throw e;
58
+ }
59
+ }
60
+ export function useMermaidRenderResult({ text, config: providedConfig, }) {
61
+ const [result, setResult] = useState(null);
62
+ const id = useMermaidId();
23
63
  /*
24
64
  For flexibility, we allow the hook to receive a custom Mermaid config
25
65
  The user could inject a modified version of the default config for example
26
66
  */
27
67
  const defaultMermaidConfig = useMermaidConfig();
28
- const mermaidConfig = mermaidConfigParam ?? defaultMermaidConfig;
29
- return useMemo(() => {
30
- /*
31
- Mermaid API is really weird :s
32
- It is a big mutable singleton with multiple config levels
33
- Note: most recent API type definitions are missing
34
-
35
- There are 2 kind of configs:
36
-
37
- - siteConfig: some kind of global/protected shared config
38
- you can only set with "initialize"
39
-
40
- - config/currentConfig
41
- the config the renderer will use
42
- it is reset to siteConfig before each render
43
- but it can be altered by the mermaid txt content itself through directives
44
-
45
- To use a new mermaid config (on colorMode change for example) we should
46
- update siteConfig, and it can only be done with initialize()
47
- */
48
- mermaid.mermaidAPI.initialize(mermaidConfig);
49
- /*
50
- Random client-only id, we don't care much about it
51
- But mermaid want an id so...
52
- */
53
- const mermaidId = `mermaid-svg-${Math.round(Math.random() * 10000000)}`;
54
- /*
55
- Not even documented: mermaid.render returns the svg string
56
- Using the documented form is un-necessary
57
- */
58
- return mermaid.render(mermaidId, txt);
59
- }, [txt, mermaidConfig]);
68
+ const config = providedConfig ?? defaultMermaidConfig;
69
+ useEffect(() => {
70
+ renderMermaid({ id, text, config })
71
+ // TODO maybe try to use Suspense here and throw the promise?
72
+ // See also https://github.com/pmndrs/suspend-react
73
+ .then(setResult)
74
+ .catch((e) => {
75
+ // Funky way to trigger parent React error boundary
76
+ // See https://twitter.com/sebastienlorber/status/1628340871899893768
77
+ setResult(() => {
78
+ throw e;
79
+ });
80
+ });
81
+ }, [id, text, config]);
82
+ return result;
60
83
  }
@@ -4,23 +4,41 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
- import React from 'react';
8
- import BrowserOnly from '@docusaurus/BrowserOnly';
7
+ import React, {useEffect, useRef} from 'react';
8
+ import ErrorBoundary from '@docusaurus/ErrorBoundary';
9
+ import {ErrorBoundaryErrorMessageFallback} from '@docusaurus/theme-common';
9
10
  import {
10
11
  MermaidContainerClassName,
11
- useMermaidSvg,
12
+ useMermaidRenderResult,
12
13
  } from '@docusaurus/theme-mermaid/client';
13
14
  import styles from './styles.module.css';
14
- function MermaidDiagram({value}) {
15
- const svg = useMermaidSvg(value);
15
+ function MermaidRenderResult({renderResult}) {
16
+ const ref = useRef(null);
17
+ useEffect(() => {
18
+ const div = ref.current;
19
+ renderResult.bindFunctions?.(div);
20
+ }, [renderResult]);
16
21
  return (
17
22
  <div
23
+ ref={ref}
18
24
  className={`${MermaidContainerClassName} ${styles.container}`}
19
25
  // eslint-disable-next-line react/no-danger
20
- dangerouslySetInnerHTML={{__html: svg}}
26
+ dangerouslySetInnerHTML={{__html: renderResult.svg}}
21
27
  />
22
28
  );
23
29
  }
30
+ function MermaidRenderer({value}) {
31
+ const renderResult = useMermaidRenderResult({text: value});
32
+ if (renderResult === null) {
33
+ return null;
34
+ }
35
+ return <MermaidRenderResult renderResult={renderResult} />;
36
+ }
24
37
  export default function Mermaid(props) {
25
- return <BrowserOnly>{() => <MermaidDiagram {...props} />}</BrowserOnly>;
38
+ return (
39
+ <ErrorBoundary
40
+ fallback={(params) => <ErrorBoundaryErrorMessageFallback {...params} />}>
41
+ <MermaidRenderer {...props} />
42
+ </ErrorBoundary>
43
+ );
26
44
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docusaurus/theme-mermaid",
3
- "version": "3.0.0-alpha.0",
3
+ "version": "3.0.0-beta.0",
4
4
  "description": "Mermaid components for Docusaurus.",
5
5
  "main": "lib/index.js",
6
6
  "types": "src/theme-mermaid.d.ts",
@@ -33,13 +33,13 @@
33
33
  "copy:watch": "node ../../admin/scripts/copyUntypedFiles.js --watch"
34
34
  },
35
35
  "dependencies": {
36
- "@docusaurus/core": "3.0.0-alpha.0",
37
- "@docusaurus/module-type-aliases": "3.0.0-alpha.0",
38
- "@docusaurus/theme-common": "3.0.0-alpha.0",
39
- "@docusaurus/types": "3.0.0-alpha.0",
40
- "@docusaurus/utils-validation": "3.0.0-alpha.0",
41
- "mermaid": "^9.4.3",
42
- "tslib": "^2.5.0"
36
+ "@docusaurus/core": "3.0.0-beta.0",
37
+ "@docusaurus/module-type-aliases": "3.0.0-beta.0",
38
+ "@docusaurus/theme-common": "3.0.0-beta.0",
39
+ "@docusaurus/types": "3.0.0-beta.0",
40
+ "@docusaurus/utils-validation": "3.0.0-beta.0",
41
+ "mermaid": "^10.4.0",
42
+ "tslib": "^2.6.0"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/mdx-js__react": "^1.5.5",
@@ -52,5 +52,5 @@
52
52
  "engines": {
53
53
  "node": ">=16.14"
54
54
  },
55
- "gitHead": "7327f7ff880ed97ad7855744e59c9c55d467a950"
55
+ "gitHead": "27a1e90d9fff88af90ecad35bea16d4d7230482a"
56
56
  }
@@ -5,9 +5,10 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
- import {useMemo} from 'react';
8
+ import {useState, useEffect, useMemo, useRef} from 'react';
9
9
  import {useColorMode, useThemeConfig} from '@docusaurus/theme-common';
10
- import mermaid, {type MermaidConfig} from 'mermaid';
10
+ import mermaid from 'mermaid';
11
+ import type {RenderResult, MermaidConfig} from 'mermaid';
11
12
  import type {ThemeConfig} from '@docusaurus/theme-mermaid';
12
13
 
13
14
  // Stable className to allow users to easily target with CSS
@@ -30,48 +31,84 @@ export function useMermaidConfig(): MermaidConfig {
30
31
  );
31
32
  }
32
33
 
33
- export function useMermaidSvg(
34
- txt: string,
35
- mermaidConfigParam?: MermaidConfig,
36
- ): string {
34
+ function useMermaidId(): string {
35
+ /*
36
+ Random client-only id, we don't care much but mermaid want an id so...
37
+ Note: Mermaid doesn't like values provided by Rect.useId() and throws
38
+ */
39
+ // return useId(); // tried that, doesn't work ('#d:re:' is not a valid selector.)
40
+ return useRef(`mermaid-svg-${Math.round(Math.random() * 10000000)}`).current!;
41
+ }
42
+
43
+ async function renderMermaid({
44
+ id,
45
+ text,
46
+ config,
47
+ }: {
48
+ id: string;
49
+ text: string;
50
+ config: MermaidConfig;
51
+ }): Promise<RenderResult> {
52
+ /*
53
+ Mermaid API is really weird :s
54
+ It is a big mutable singleton with multiple config levels
55
+ Note: most recent API type definitions are missing
56
+
57
+ There are 2 kind of configs:
58
+
59
+ - siteConfig: some kind of global/protected shared config
60
+ you can only set with "initialize"
61
+
62
+ - config/currentConfig
63
+ the config the renderer will use
64
+ it is reset to siteConfig before each render
65
+ but it can be altered by the mermaid txt content itself through directives
66
+
67
+ To use a new mermaid config (on colorMode change for example) we should
68
+ update siteConfig, and it can only be done with initialize()
69
+ */
70
+ mermaid.mermaidAPI.initialize(config);
71
+
72
+ try {
73
+ return await mermaid.render(id, text);
74
+ } catch (e) {
75
+ // Because Mermaid add a weird SVG/Message to the DOM on error
76
+ // https://github.com/mermaid-js/mermaid/issues/3205#issuecomment-1719620183
77
+ document.querySelector(`#d${id}`)?.remove();
78
+ throw e;
79
+ }
80
+ }
81
+
82
+ export function useMermaidRenderResult({
83
+ text,
84
+ config: providedConfig,
85
+ }: {
86
+ text: string;
87
+ config?: MermaidConfig;
88
+ }): RenderResult | null {
89
+ const [result, setResult] = useState<RenderResult | null>(null);
90
+ const id = useMermaidId();
91
+
37
92
  /*
38
93
  For flexibility, we allow the hook to receive a custom Mermaid config
39
94
  The user could inject a modified version of the default config for example
40
95
  */
41
96
  const defaultMermaidConfig = useMermaidConfig();
42
- const mermaidConfig = mermaidConfigParam ?? defaultMermaidConfig;
43
-
44
- return useMemo(() => {
45
- /*
46
- Mermaid API is really weird :s
47
- It is a big mutable singleton with multiple config levels
48
- Note: most recent API type definitions are missing
49
-
50
- There are 2 kind of configs:
51
-
52
- - siteConfig: some kind of global/protected shared config
53
- you can only set with "initialize"
54
-
55
- - config/currentConfig
56
- the config the renderer will use
57
- it is reset to siteConfig before each render
58
- but it can be altered by the mermaid txt content itself through directives
59
-
60
- To use a new mermaid config (on colorMode change for example) we should
61
- update siteConfig, and it can only be done with initialize()
62
- */
63
- mermaid.mermaidAPI.initialize(mermaidConfig);
64
-
65
- /*
66
- Random client-only id, we don't care much about it
67
- But mermaid want an id so...
68
- */
69
- const mermaidId = `mermaid-svg-${Math.round(Math.random() * 10000000)}`;
70
-
71
- /*
72
- Not even documented: mermaid.render returns the svg string
73
- Using the documented form is un-necessary
74
- */
75
- return mermaid.render(mermaidId, txt);
76
- }, [txt, mermaidConfig]);
97
+ const config = providedConfig ?? defaultMermaidConfig;
98
+
99
+ useEffect(() => {
100
+ renderMermaid({id, text, config})
101
+ // TODO maybe try to use Suspense here and throw the promise?
102
+ // See also https://github.com/pmndrs/suspend-react
103
+ .then(setResult)
104
+ .catch((e) => {
105
+ // Funky way to trigger parent React error boundary
106
+ // See https://twitter.com/sebastienlorber/status/1628340871899893768
107
+ setResult(() => {
108
+ throw e;
109
+ });
110
+ });
111
+ }, [id, text, config]);
112
+
113
+ return result;
77
114
  }
@@ -5,28 +5,54 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
- import React from 'react';
9
- import BrowserOnly from '@docusaurus/BrowserOnly';
8
+ import React, {useEffect, useRef} from 'react';
9
+ import type {ReactNode} from 'react';
10
+ import ErrorBoundary from '@docusaurus/ErrorBoundary';
11
+ import {ErrorBoundaryErrorMessageFallback} from '@docusaurus/theme-common';
10
12
  import {
11
13
  MermaidContainerClassName,
12
- useMermaidSvg,
14
+ useMermaidRenderResult,
13
15
  } from '@docusaurus/theme-mermaid/client';
14
-
15
16
  import type {Props} from '@theme/Mermaid';
17
+ import type {RenderResult} from 'mermaid';
16
18
 
17
19
  import styles from './styles.module.css';
18
20
 
19
- function MermaidDiagram({value}: Props): JSX.Element {
20
- const svg = useMermaidSvg(value);
21
+ function MermaidRenderResult({
22
+ renderResult,
23
+ }: {
24
+ renderResult: RenderResult;
25
+ }): JSX.Element {
26
+ const ref = useRef<HTMLDivElement>(null);
27
+
28
+ useEffect(() => {
29
+ const div = ref.current!;
30
+ renderResult.bindFunctions?.(div);
31
+ }, [renderResult]);
32
+
21
33
  return (
22
34
  <div
35
+ ref={ref}
23
36
  className={`${MermaidContainerClassName} ${styles.container}`}
24
37
  // eslint-disable-next-line react/no-danger
25
- dangerouslySetInnerHTML={{__html: svg}}
38
+ dangerouslySetInnerHTML={{__html: renderResult.svg}}
26
39
  />
27
40
  );
28
41
  }
29
42
 
43
+ function MermaidRenderer({value}: Props): ReactNode {
44
+ const renderResult = useMermaidRenderResult({text: value});
45
+ if (renderResult === null) {
46
+ return null;
47
+ }
48
+ return <MermaidRenderResult renderResult={renderResult} />;
49
+ }
50
+
30
51
  export default function Mermaid(props: Props): JSX.Element {
31
- return <BrowserOnly>{() => <MermaidDiagram {...props} />}</BrowserOnly>;
52
+ return (
53
+ <ErrorBoundary
54
+ fallback={(params) => <ErrorBoundaryErrorMessageFallback {...params} />}>
55
+ <MermaidRenderer {...props} />
56
+ </ErrorBoundary>
57
+ );
32
58
  }