@stream-mdx/mermaid 0.0.3

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 ADDED
@@ -0,0 +1,11 @@
1
+ # @stream-mdx/mermaid
2
+
3
+ ## 0.0.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 47d1374: Add opt-in streaming format anticipation and an optional Mermaid diagram addon for ` ```mermaid ` code blocks.
8
+
9
+ ## Unreleased
10
+
11
+ - Initial release.
package/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # @stream-mdx/mermaid
2
+
3
+ Mermaid diagram rendering addon for StreamMDX.
4
+
5
+ This package is **opt-in**. Install it and register the `mermaid` block component so ` ```mermaid ` code fences render as diagrams (with a Diagram/Code toggle).
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @stream-mdx/mermaid
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```tsx
16
+ import { StreamingMarkdown } from "@stream-mdx/react";
17
+ import { MermaidBlock } from "@stream-mdx/mermaid";
18
+
19
+ export function Demo() {
20
+ return (
21
+ <StreamingMarkdown
22
+ text={"```mermaid\\ngraph TD; A-->B;\\n```"}
23
+ components={{
24
+ mermaid: MermaidBlock,
25
+ }}
26
+ />
27
+ );
28
+ }
29
+ ```
30
+
31
+ ## Customization
32
+
33
+ If you want to tune debounce or default view, wrap the component:
34
+
35
+ ```tsx
36
+ components={{
37
+ mermaid: (props) => <MermaidBlock {...props} debounceMs={250} defaultView="diagram" />,
38
+ }}
39
+ ```
package/dist/index.cjs ADDED
@@ -0,0 +1,172 @@
1
+ "use client";
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // src/index.ts
32
+ var index_exports = {};
33
+ __export(index_exports, {
34
+ MermaidBlock: () => MermaidBlock
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+
38
+ // src/mermaid-block.tsx
39
+ var import_react = __toESM(require("react"), 1);
40
+ var import_jsx_runtime = require("react/jsx-runtime");
41
+ var mermaidPromise = null;
42
+ var mermaidInitialized = false;
43
+ var mermaidIdCounter = 0;
44
+ async function loadMermaid() {
45
+ if (!mermaidPromise) {
46
+ mermaidPromise = import("mermaid").then((mod) => mod.default ?? mod);
47
+ }
48
+ const mermaid = await mermaidPromise;
49
+ if (!mermaidInitialized && typeof mermaid.initialize === "function") {
50
+ mermaid.initialize({ startOnLoad: false });
51
+ mermaidInitialized = true;
52
+ }
53
+ return mermaid;
54
+ }
55
+ function nextMermaidId() {
56
+ mermaidIdCounter += 1;
57
+ return `stream-mdx-mermaid-${mermaidIdCounter}`;
58
+ }
59
+ var MermaidBlock = ({ code, renderCode, defaultView = "diagram", debounceMs = 200 }) => {
60
+ const [view, setView] = import_react.default.useState(defaultView);
61
+ const [svg, setSvg] = import_react.default.useState("");
62
+ const [error, setError] = import_react.default.useState(null);
63
+ const containerRef = import_react.default.useRef(null);
64
+ const bindRef = import_react.default.useRef(void 0);
65
+ const lastValidSvgRef = import_react.default.useRef("");
66
+ const generationRef = import_react.default.useRef(0);
67
+ import_react.default.useEffect(() => {
68
+ setView(defaultView);
69
+ }, [defaultView]);
70
+ import_react.default.useEffect(() => {
71
+ if (view !== "diagram") return;
72
+ const trimmed = typeof code === "string" ? code.trim() : "";
73
+ if (!trimmed) {
74
+ setError(null);
75
+ setSvg("");
76
+ lastValidSvgRef.current = "";
77
+ return;
78
+ }
79
+ generationRef.current += 1;
80
+ const generation = generationRef.current;
81
+ const timeout = setTimeout(async () => {
82
+ try {
83
+ const mermaid = await loadMermaid();
84
+ if (typeof mermaid.render !== "function") {
85
+ throw new Error("Mermaid runtime missing render()");
86
+ }
87
+ const result = await mermaid.render(nextMermaidId(), trimmed);
88
+ if (generation !== generationRef.current) return;
89
+ const svgText = typeof result === "string" ? result : result?.svg ?? "";
90
+ const bindFunctions = typeof result === "string" ? void 0 : result?.bindFunctions;
91
+ if (!svgText || svgText.trim().length === 0) {
92
+ throw new Error("Mermaid render returned empty SVG");
93
+ }
94
+ lastValidSvgRef.current = svgText;
95
+ bindRef.current = typeof bindFunctions === "function" ? bindFunctions : void 0;
96
+ setSvg(svgText);
97
+ setError(null);
98
+ } catch (err) {
99
+ if (generation !== generationRef.current) return;
100
+ const message = err instanceof Error ? err.message : String(err);
101
+ setError(message);
102
+ if (lastValidSvgRef.current) {
103
+ setSvg(lastValidSvgRef.current);
104
+ }
105
+ }
106
+ }, Math.max(0, debounceMs));
107
+ return () => clearTimeout(timeout);
108
+ }, [code, debounceMs, view]);
109
+ import_react.default.useEffect(() => {
110
+ if (view !== "diagram") return;
111
+ if (!svg) return;
112
+ const bind = bindRef.current;
113
+ const el = containerRef.current;
114
+ if (!el || typeof bind !== "function") return;
115
+ try {
116
+ bind(el);
117
+ } catch {
118
+ }
119
+ }, [svg, view]);
120
+ const toolbarStyle = {
121
+ display: "flex",
122
+ gap: 8,
123
+ alignItems: "center",
124
+ justifyContent: "space-between",
125
+ padding: "6px 10px",
126
+ borderBottom: "1px solid rgba(0,0,0,0.08)"
127
+ };
128
+ const wrapperStyle = {
129
+ border: "1px solid rgba(0,0,0,0.12)",
130
+ borderRadius: 10,
131
+ overflow: "hidden",
132
+ margin: "12px 0"
133
+ };
134
+ const buttonStyle = (active) => ({
135
+ border: "1px solid rgba(0,0,0,0.18)",
136
+ borderRadius: 8,
137
+ padding: "3px 8px",
138
+ fontSize: 12,
139
+ background: active ? "rgba(0,0,0,0.06)" : "transparent",
140
+ cursor: "pointer"
141
+ });
142
+ const toolbar = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: toolbarStyle, className: "stream-mdx-mermaid-toolbar", children: [
143
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 12, opacity: 0.8 }, children: "mermaid" }),
144
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 6 }, children: [
145
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: () => setView("diagram"), style: buttonStyle(view === "diagram"), children: "Diagram" }),
146
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: () => setView("code"), style: buttonStyle(view === "code"), children: "Code" })
147
+ ] })
148
+ ] });
149
+ if (view === "code") {
150
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: wrapperStyle, className: "stream-mdx-mermaid-block", children: [
151
+ toolbar,
152
+ renderCode
153
+ ] });
154
+ }
155
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: wrapperStyle, className: "stream-mdx-mermaid-block", children: [
156
+ toolbar,
157
+ error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 12, padding: "6px 10px", opacity: 0.8 }, className: "stream-mdx-mermaid-error", children: error }) : null,
158
+ svg ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
159
+ "div",
160
+ {
161
+ ref: containerRef,
162
+ className: "stream-mdx-mermaid-diagram",
163
+ style: { overflowX: "auto", padding: 10 },
164
+ dangerouslySetInnerHTML: { __html: svg }
165
+ }
166
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 12, padding: "10px", opacity: 0.7 }, className: "stream-mdx-mermaid-placeholder", children: "Waiting for a valid diagram\u2026" })
167
+ ] });
168
+ };
169
+ // Annotate the CommonJS export names for ESM import in node:
170
+ 0 && (module.exports = {
171
+ MermaidBlock
172
+ });
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+
3
+ type MermaidBlockProps = {
4
+ code: string;
5
+ renderCode: React.ReactNode;
6
+ meta?: Record<string, unknown>;
7
+ isFinalized?: boolean;
8
+ defaultView?: "diagram" | "code";
9
+ debounceMs?: number;
10
+ };
11
+ declare const MermaidBlock: React.FC<MermaidBlockProps>;
12
+
13
+ export { MermaidBlock, type MermaidBlockProps };
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+
3
+ type MermaidBlockProps = {
4
+ code: string;
5
+ renderCode: React.ReactNode;
6
+ meta?: Record<string, unknown>;
7
+ isFinalized?: boolean;
8
+ defaultView?: "diagram" | "code";
9
+ debounceMs?: number;
10
+ };
11
+ declare const MermaidBlock: React.FC<MermaidBlockProps>;
12
+
13
+ export { MermaidBlock, type MermaidBlockProps };
package/dist/index.mjs ADDED
@@ -0,0 +1,136 @@
1
+ "use client";
2
+
3
+ // src/mermaid-block.tsx
4
+ import React from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ var mermaidPromise = null;
7
+ var mermaidInitialized = false;
8
+ var mermaidIdCounter = 0;
9
+ async function loadMermaid() {
10
+ if (!mermaidPromise) {
11
+ mermaidPromise = import("mermaid").then((mod) => mod.default ?? mod);
12
+ }
13
+ const mermaid = await mermaidPromise;
14
+ if (!mermaidInitialized && typeof mermaid.initialize === "function") {
15
+ mermaid.initialize({ startOnLoad: false });
16
+ mermaidInitialized = true;
17
+ }
18
+ return mermaid;
19
+ }
20
+ function nextMermaidId() {
21
+ mermaidIdCounter += 1;
22
+ return `stream-mdx-mermaid-${mermaidIdCounter}`;
23
+ }
24
+ var MermaidBlock = ({ code, renderCode, defaultView = "diagram", debounceMs = 200 }) => {
25
+ const [view, setView] = React.useState(defaultView);
26
+ const [svg, setSvg] = React.useState("");
27
+ const [error, setError] = React.useState(null);
28
+ const containerRef = React.useRef(null);
29
+ const bindRef = React.useRef(void 0);
30
+ const lastValidSvgRef = React.useRef("");
31
+ const generationRef = React.useRef(0);
32
+ React.useEffect(() => {
33
+ setView(defaultView);
34
+ }, [defaultView]);
35
+ React.useEffect(() => {
36
+ if (view !== "diagram") return;
37
+ const trimmed = typeof code === "string" ? code.trim() : "";
38
+ if (!trimmed) {
39
+ setError(null);
40
+ setSvg("");
41
+ lastValidSvgRef.current = "";
42
+ return;
43
+ }
44
+ generationRef.current += 1;
45
+ const generation = generationRef.current;
46
+ const timeout = setTimeout(async () => {
47
+ try {
48
+ const mermaid = await loadMermaid();
49
+ if (typeof mermaid.render !== "function") {
50
+ throw new Error("Mermaid runtime missing render()");
51
+ }
52
+ const result = await mermaid.render(nextMermaidId(), trimmed);
53
+ if (generation !== generationRef.current) return;
54
+ const svgText = typeof result === "string" ? result : result?.svg ?? "";
55
+ const bindFunctions = typeof result === "string" ? void 0 : result?.bindFunctions;
56
+ if (!svgText || svgText.trim().length === 0) {
57
+ throw new Error("Mermaid render returned empty SVG");
58
+ }
59
+ lastValidSvgRef.current = svgText;
60
+ bindRef.current = typeof bindFunctions === "function" ? bindFunctions : void 0;
61
+ setSvg(svgText);
62
+ setError(null);
63
+ } catch (err) {
64
+ if (generation !== generationRef.current) return;
65
+ const message = err instanceof Error ? err.message : String(err);
66
+ setError(message);
67
+ if (lastValidSvgRef.current) {
68
+ setSvg(lastValidSvgRef.current);
69
+ }
70
+ }
71
+ }, Math.max(0, debounceMs));
72
+ return () => clearTimeout(timeout);
73
+ }, [code, debounceMs, view]);
74
+ React.useEffect(() => {
75
+ if (view !== "diagram") return;
76
+ if (!svg) return;
77
+ const bind = bindRef.current;
78
+ const el = containerRef.current;
79
+ if (!el || typeof bind !== "function") return;
80
+ try {
81
+ bind(el);
82
+ } catch {
83
+ }
84
+ }, [svg, view]);
85
+ const toolbarStyle = {
86
+ display: "flex",
87
+ gap: 8,
88
+ alignItems: "center",
89
+ justifyContent: "space-between",
90
+ padding: "6px 10px",
91
+ borderBottom: "1px solid rgba(0,0,0,0.08)"
92
+ };
93
+ const wrapperStyle = {
94
+ border: "1px solid rgba(0,0,0,0.12)",
95
+ borderRadius: 10,
96
+ overflow: "hidden",
97
+ margin: "12px 0"
98
+ };
99
+ const buttonStyle = (active) => ({
100
+ border: "1px solid rgba(0,0,0,0.18)",
101
+ borderRadius: 8,
102
+ padding: "3px 8px",
103
+ fontSize: 12,
104
+ background: active ? "rgba(0,0,0,0.06)" : "transparent",
105
+ cursor: "pointer"
106
+ });
107
+ const toolbar = /* @__PURE__ */ jsxs("div", { style: toolbarStyle, className: "stream-mdx-mermaid-toolbar", children: [
108
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 12, opacity: 0.8 }, children: "mermaid" }),
109
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 6 }, children: [
110
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setView("diagram"), style: buttonStyle(view === "diagram"), children: "Diagram" }),
111
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setView("code"), style: buttonStyle(view === "code"), children: "Code" })
112
+ ] })
113
+ ] });
114
+ if (view === "code") {
115
+ return /* @__PURE__ */ jsxs("div", { style: wrapperStyle, className: "stream-mdx-mermaid-block", children: [
116
+ toolbar,
117
+ renderCode
118
+ ] });
119
+ }
120
+ return /* @__PURE__ */ jsxs("div", { style: wrapperStyle, className: "stream-mdx-mermaid-block", children: [
121
+ toolbar,
122
+ error ? /* @__PURE__ */ jsx("div", { style: { fontSize: 12, padding: "6px 10px", opacity: 0.8 }, className: "stream-mdx-mermaid-error", children: error }) : null,
123
+ svg ? /* @__PURE__ */ jsx(
124
+ "div",
125
+ {
126
+ ref: containerRef,
127
+ className: "stream-mdx-mermaid-diagram",
128
+ style: { overflowX: "auto", padding: 10 },
129
+ dangerouslySetInnerHTML: { __html: svg }
130
+ }
131
+ ) : /* @__PURE__ */ jsx("div", { style: { fontSize: 12, padding: "10px", opacity: 0.7 }, className: "stream-mdx-mermaid-placeholder", children: "Waiting for a valid diagram\u2026" })
132
+ ] });
133
+ };
134
+ export {
135
+ MermaidBlock
136
+ };
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@stream-mdx/mermaid",
3
+ "version": "0.0.3",
4
+ "description": "Mermaid diagram rendering addon for StreamMDX (Streaming Markdown V2)",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/kmccleary3301/stream-mdx.git",
9
+ "directory": "packages/markdown-v2-mermaid"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "type": "module",
15
+ "main": "./dist/index.cjs",
16
+ "module": "./dist/index.mjs",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.mjs",
22
+ "require": "./dist/index.cjs"
23
+ }
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "README.md",
28
+ "CHANGELOG.md"
29
+ ],
30
+ "sideEffects": false,
31
+ "scripts": {
32
+ "build": "tsup",
33
+ "test": "tsx __tests__/*.test.ts",
34
+ "clean": "rm -rf dist",
35
+ "prepack": "npm run build"
36
+ },
37
+ "dependencies": {
38
+ "mermaid": "^11.12.2"
39
+ },
40
+ "peerDependencies": {
41
+ "react": ">=18.2.0",
42
+ "react-dom": ">=18.2.0"
43
+ }
44
+ }