@blaaiz/docs-core 0.1.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/LICENSE +21 -0
- package/README.md +103 -0
- package/dist/chunk-3ZX4WIE3.js +2984 -0
- package/dist/chunk-3ZX4WIE3.js.map +1 -0
- package/dist/chunk-JCYR6RPE.js +31 -0
- package/dist/chunk-JCYR6RPE.js.map +1 -0
- package/dist/chunk-ZKOOKLZ3.js +124 -0
- package/dist/chunk-ZKOOKLZ3.js.map +1 -0
- package/dist/cli.js +534 -0
- package/dist/cli.js.map +1 -0
- package/dist/generator.cjs +508 -0
- package/dist/generator.cjs.map +1 -0
- package/dist/generator.d.cts +122 -0
- package/dist/generator.d.ts +122 -0
- package/dist/generator.js +177 -0
- package/dist/generator.js.map +1 -0
- package/dist/index.cjs +3024 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1182 -0
- package/dist/index.d.ts +1182 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/navigation-CGqFIPlP.d.cts +498 -0
- package/dist/navigation-CGqFIPlP.d.ts +498 -0
- package/dist/openapi-types-CJ6p5Cux.d.cts +78 -0
- package/dist/openapi-types-CJ6p5Cux.d.ts +78 -0
- package/dist/ui/api-try-it.cjs +654 -0
- package/dist/ui/api-try-it.cjs.map +1 -0
- package/dist/ui/api-try-it.d.cts +78 -0
- package/dist/ui/api-try-it.d.ts +78 -0
- package/dist/ui/api-try-it.js +509 -0
- package/dist/ui/api-try-it.js.map +1 -0
- package/dist/ui/ask-ai.cjs +810 -0
- package/dist/ui/ask-ai.cjs.map +1 -0
- package/dist/ui/ask-ai.d.cts +57 -0
- package/dist/ui/ask-ai.d.ts +57 -0
- package/dist/ui/ask-ai.js +808 -0
- package/dist/ui/ask-ai.js.map +1 -0
- package/dist/ui/copy-page.cjs +312 -0
- package/dist/ui/copy-page.cjs.map +1 -0
- package/dist/ui/copy-page.d.cts +33 -0
- package/dist/ui/copy-page.d.ts +33 -0
- package/dist/ui/copy-page.js +183 -0
- package/dist/ui/copy-page.js.map +1 -0
- package/dist/ui/mermaid.cjs +363 -0
- package/dist/ui/mermaid.cjs.map +1 -0
- package/dist/ui/mermaid.d.cts +13 -0
- package/dist/ui/mermaid.d.ts +13 -0
- package/dist/ui/mermaid.js +361 -0
- package/dist/ui/mermaid.js.map +1 -0
- package/dist/ui.cjs +661 -0
- package/dist/ui.cjs.map +1 -0
- package/dist/ui.d.cts +428 -0
- package/dist/ui.d.ts +428 -0
- package/dist/ui.js +537 -0
- package/dist/ui.js.map +1 -0
- package/package.json +145 -0
- package/patches/fumadocs-openapi.patch +173 -0
- package/skills/AGENTS-section.md +36 -0
- package/skills/SKILL.md +363 -0
- package/styles/api-reference.css +1417 -0
- package/styles/ask-ai.css +563 -0
- package/styles/auth.css +462 -0
- package/styles/docs.css +247 -0
- package/styles/home.css +376 -0
- package/templates/init/content/docs/index.mdx.tmpl +52 -0
- package/templates/init/content/docs/meta.json.tmpl +3 -0
- package/templates/init/content/docs.json.tmpl +12 -0
- package/templates/init/content/nav.json.tmpl +7 -0
- package/templates/init/docs.config.ts.tmpl +36 -0
- package/templates/init/env.example.tmpl +15 -0
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useState, useEffect, useId, useRef, useCallback } from 'react';
|
|
3
|
+
import { createPortal } from 'react-dom';
|
|
4
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
var MIN_SCALE = 0.4;
|
|
7
|
+
var MAX_SCALE = 8;
|
|
8
|
+
var MAX_FIT_SCALE = 2.5;
|
|
9
|
+
var FIT_PADDING = 0.9;
|
|
10
|
+
var IDENTITY = { scale: 1, x: 0, y: 0 };
|
|
11
|
+
function clamp(value, min, max) {
|
|
12
|
+
return Math.min(max, Math.max(min, value));
|
|
13
|
+
}
|
|
14
|
+
function isDarkTheme() {
|
|
15
|
+
return document.documentElement.classList.contains("dark");
|
|
16
|
+
}
|
|
17
|
+
function useMermaidSvg(chart) {
|
|
18
|
+
const rawId = useId().replace(/[^a-zA-Z0-9]/g, "");
|
|
19
|
+
const [svg, setSvg] = useState("");
|
|
20
|
+
const [dark, setDark] = useState(false);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
setDark(isDarkTheme());
|
|
23
|
+
const observer = new MutationObserver(() => {
|
|
24
|
+
setDark(isDarkTheme());
|
|
25
|
+
});
|
|
26
|
+
observer.observe(document.documentElement, { attributes: true, attributeFilter: ["class"] });
|
|
27
|
+
return () => {
|
|
28
|
+
observer.disconnect();
|
|
29
|
+
};
|
|
30
|
+
}, []);
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
let active = true;
|
|
33
|
+
void (async () => {
|
|
34
|
+
const mermaid = (await import('mermaid')).default;
|
|
35
|
+
mermaid.initialize({
|
|
36
|
+
startOnLoad: false,
|
|
37
|
+
theme: dark ? "dark" : "neutral",
|
|
38
|
+
securityLevel: "loose",
|
|
39
|
+
fontFamily: "inherit",
|
|
40
|
+
themeVariables: { fontSize: "14px" }
|
|
41
|
+
});
|
|
42
|
+
try {
|
|
43
|
+
const result = await mermaid.render(`mmd-${rawId}`, chart);
|
|
44
|
+
if (active) {
|
|
45
|
+
setSvg(result.svg);
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
if (active) {
|
|
49
|
+
setSvg("");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
})();
|
|
53
|
+
return () => {
|
|
54
|
+
active = false;
|
|
55
|
+
};
|
|
56
|
+
}, [chart, dark, rawId]);
|
|
57
|
+
return svg;
|
|
58
|
+
}
|
|
59
|
+
var ICONS = {
|
|
60
|
+
zoomIn: /* @__PURE__ */ jsx("path", { d: "M11 8v6M8 11h6M21 21l-4.35-4.35M11 18a7 7 0 1 0 0-14 7 7 0 0 0 0 14Z" }),
|
|
61
|
+
zoomOut: /* @__PURE__ */ jsx("path", { d: "M8 11h6M21 21l-4.35-4.35M11 18a7 7 0 1 0 0-14 7 7 0 0 0 0 14Z" }),
|
|
62
|
+
reset: /* @__PURE__ */ jsx("path", { d: "M3 12a9 9 0 1 0 3-6.7L3 8m0-5v5h5" }),
|
|
63
|
+
expand: /* @__PURE__ */ jsx("path", { d: "M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3M3 16v3a2 2 0 0 0 2 2h3m8 0h3a2 2 0 0 0 2-2v-3" }),
|
|
64
|
+
close: /* @__PURE__ */ jsx("path", { d: "M18 6 6 18M6 6l12 12" })
|
|
65
|
+
};
|
|
66
|
+
function ControlButton({
|
|
67
|
+
label,
|
|
68
|
+
onClick,
|
|
69
|
+
children
|
|
70
|
+
}) {
|
|
71
|
+
return /* @__PURE__ */ jsx(
|
|
72
|
+
"button",
|
|
73
|
+
{
|
|
74
|
+
type: "button",
|
|
75
|
+
"aria-label": label,
|
|
76
|
+
onClick,
|
|
77
|
+
style: {
|
|
78
|
+
display: "inline-flex",
|
|
79
|
+
alignItems: "center",
|
|
80
|
+
justifyContent: "center",
|
|
81
|
+
width: 32,
|
|
82
|
+
height: 32,
|
|
83
|
+
padding: 0,
|
|
84
|
+
border: "1px solid var(--color-fd-border)",
|
|
85
|
+
borderRadius: 8,
|
|
86
|
+
background: "var(--color-fd-card)",
|
|
87
|
+
color: "var(--color-fd-muted-foreground)",
|
|
88
|
+
cursor: "pointer"
|
|
89
|
+
},
|
|
90
|
+
onMouseEnter: (event) => {
|
|
91
|
+
event.currentTarget.style.color = "var(--color-fd-foreground)";
|
|
92
|
+
event.currentTarget.style.background = "color-mix(in oklab, var(--color-fd-primary) 10%, var(--color-fd-card))";
|
|
93
|
+
},
|
|
94
|
+
onMouseLeave: (event) => {
|
|
95
|
+
event.currentTarget.style.color = "var(--color-fd-muted-foreground)";
|
|
96
|
+
event.currentTarget.style.background = "var(--color-fd-card)";
|
|
97
|
+
},
|
|
98
|
+
children: /* @__PURE__ */ jsx(
|
|
99
|
+
"svg",
|
|
100
|
+
{
|
|
101
|
+
width: 16,
|
|
102
|
+
height: 16,
|
|
103
|
+
viewBox: "0 0 24 24",
|
|
104
|
+
fill: "none",
|
|
105
|
+
stroke: "currentColor",
|
|
106
|
+
strokeWidth: 1.8,
|
|
107
|
+
strokeLinecap: "round",
|
|
108
|
+
strokeLinejoin: "round",
|
|
109
|
+
"aria-hidden": "true",
|
|
110
|
+
children
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
function Viewer({ svg, fullscreen, onToggleFullscreen }) {
|
|
117
|
+
const stageRef = useRef(null);
|
|
118
|
+
const contentRef = useRef(null);
|
|
119
|
+
const [view, setView] = useState(IDENTITY);
|
|
120
|
+
const drag = useRef(null);
|
|
121
|
+
const zoomAt = useCallback((clientX, clientY, factor) => {
|
|
122
|
+
const stage = stageRef.current;
|
|
123
|
+
if (!stage) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const rect = stage.getBoundingClientRect();
|
|
127
|
+
const px = clientX - rect.left;
|
|
128
|
+
const py = clientY - rect.top;
|
|
129
|
+
setView((current) => {
|
|
130
|
+
const scale = clamp(current.scale * factor, MIN_SCALE, MAX_SCALE);
|
|
131
|
+
const ratio = scale / current.scale;
|
|
132
|
+
return { scale, x: px - (px - current.x) * ratio, y: py - (py - current.y) * ratio };
|
|
133
|
+
});
|
|
134
|
+
}, []);
|
|
135
|
+
const zoomFromCenter = useCallback(
|
|
136
|
+
(factor) => {
|
|
137
|
+
const stage = stageRef.current;
|
|
138
|
+
if (!stage) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const rect = stage.getBoundingClientRect();
|
|
142
|
+
zoomAt(rect.left + rect.width / 2, rect.top + rect.height / 2, factor);
|
|
143
|
+
},
|
|
144
|
+
[zoomAt]
|
|
145
|
+
);
|
|
146
|
+
const fit = useCallback(() => {
|
|
147
|
+
const stage = stageRef.current;
|
|
148
|
+
const content = contentRef.current;
|
|
149
|
+
if (!stage || !content) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const w = content.offsetWidth;
|
|
153
|
+
const h = content.offsetHeight;
|
|
154
|
+
if (w === 0 || h === 0) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const sw = stage.clientWidth;
|
|
158
|
+
const sh = stage.clientHeight;
|
|
159
|
+
const scale = clamp(Math.min(sw / w, sh / h) * FIT_PADDING, MIN_SCALE, MAX_FIT_SCALE);
|
|
160
|
+
setView({ scale, x: (sw - w * scale) / 2, y: (sh - h * scale) / 2 });
|
|
161
|
+
}, []);
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
const stage = stageRef.current;
|
|
164
|
+
if (!stage) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const onWheel = (event) => {
|
|
168
|
+
event.preventDefault();
|
|
169
|
+
zoomAt(event.clientX, event.clientY, event.deltaY < 0 ? 1.12 : 1 / 1.12);
|
|
170
|
+
};
|
|
171
|
+
stage.addEventListener("wheel", onWheel, { passive: false });
|
|
172
|
+
return () => {
|
|
173
|
+
stage.removeEventListener("wheel", onWheel);
|
|
174
|
+
};
|
|
175
|
+
}, [zoomAt]);
|
|
176
|
+
useEffect(() => {
|
|
177
|
+
fit();
|
|
178
|
+
}, [svg, fullscreen, fit]);
|
|
179
|
+
const onPointerDown = (event) => {
|
|
180
|
+
drag.current = { x: event.clientX, y: event.clientY };
|
|
181
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
182
|
+
};
|
|
183
|
+
const onPointerMove = (event) => {
|
|
184
|
+
if (!drag.current) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const dx = event.clientX - drag.current.x;
|
|
188
|
+
const dy = event.clientY - drag.current.y;
|
|
189
|
+
drag.current = { x: event.clientX, y: event.clientY };
|
|
190
|
+
setView((current) => ({ ...current, x: current.x + dx, y: current.y + dy }));
|
|
191
|
+
};
|
|
192
|
+
const onPointerUp = (event) => {
|
|
193
|
+
drag.current = null;
|
|
194
|
+
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
|
|
195
|
+
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
return /* @__PURE__ */ jsxs("div", { style: { position: "relative", width: "100%", height: fullscreen ? "100%" : 420 }, children: [
|
|
199
|
+
/* @__PURE__ */ jsx(
|
|
200
|
+
"div",
|
|
201
|
+
{
|
|
202
|
+
ref: stageRef,
|
|
203
|
+
onPointerDown,
|
|
204
|
+
onPointerMove,
|
|
205
|
+
onPointerUp,
|
|
206
|
+
onPointerLeave: onPointerUp,
|
|
207
|
+
style: {
|
|
208
|
+
position: "absolute",
|
|
209
|
+
inset: 0,
|
|
210
|
+
overflow: "hidden",
|
|
211
|
+
cursor: "grab",
|
|
212
|
+
touchAction: "none"
|
|
213
|
+
},
|
|
214
|
+
children: /* @__PURE__ */ jsx(
|
|
215
|
+
"div",
|
|
216
|
+
{
|
|
217
|
+
ref: contentRef,
|
|
218
|
+
style: {
|
|
219
|
+
transform: `translate(${String(view.x)}px, ${String(view.y)}px) scale(${String(view.scale)})`,
|
|
220
|
+
transformOrigin: "0 0",
|
|
221
|
+
display: "inline-block",
|
|
222
|
+
willChange: "transform"
|
|
223
|
+
},
|
|
224
|
+
dangerouslySetInnerHTML: { __html: svg }
|
|
225
|
+
}
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
),
|
|
229
|
+
/* @__PURE__ */ jsxs(
|
|
230
|
+
"div",
|
|
231
|
+
{
|
|
232
|
+
style: {
|
|
233
|
+
position: "absolute",
|
|
234
|
+
bottom: 12,
|
|
235
|
+
right: 12,
|
|
236
|
+
display: "flex",
|
|
237
|
+
flexDirection: "column",
|
|
238
|
+
gap: 6
|
|
239
|
+
},
|
|
240
|
+
children: [
|
|
241
|
+
/* @__PURE__ */ jsx(
|
|
242
|
+
ControlButton,
|
|
243
|
+
{
|
|
244
|
+
label: fullscreen ? "Exit full screen" : "Full screen",
|
|
245
|
+
onClick: onToggleFullscreen,
|
|
246
|
+
children: fullscreen ? ICONS.close : ICONS.expand
|
|
247
|
+
}
|
|
248
|
+
),
|
|
249
|
+
/* @__PURE__ */ jsx(
|
|
250
|
+
ControlButton,
|
|
251
|
+
{
|
|
252
|
+
label: "Zoom in",
|
|
253
|
+
onClick: () => {
|
|
254
|
+
zoomFromCenter(1.3);
|
|
255
|
+
},
|
|
256
|
+
children: ICONS.zoomIn
|
|
257
|
+
}
|
|
258
|
+
),
|
|
259
|
+
/* @__PURE__ */ jsx(
|
|
260
|
+
ControlButton,
|
|
261
|
+
{
|
|
262
|
+
label: "Zoom out",
|
|
263
|
+
onClick: () => {
|
|
264
|
+
zoomFromCenter(1 / 1.3);
|
|
265
|
+
},
|
|
266
|
+
children: ICONS.zoomOut
|
|
267
|
+
}
|
|
268
|
+
),
|
|
269
|
+
/* @__PURE__ */ jsx(ControlButton, { label: "Reset view", onClick: fit, children: ICONS.reset })
|
|
270
|
+
]
|
|
271
|
+
}
|
|
272
|
+
)
|
|
273
|
+
] });
|
|
274
|
+
}
|
|
275
|
+
function Mermaid({ chart }) {
|
|
276
|
+
const svg = useMermaidSvg(chart);
|
|
277
|
+
const [full, setFull] = useState(false);
|
|
278
|
+
const [mounted, setMounted] = useState(false);
|
|
279
|
+
useEffect(() => {
|
|
280
|
+
setMounted(true);
|
|
281
|
+
}, []);
|
|
282
|
+
useEffect(() => {
|
|
283
|
+
if (!full) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const onKey = (event) => {
|
|
287
|
+
if (event.key === "Escape") {
|
|
288
|
+
setFull(false);
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
document.addEventListener("keydown", onKey);
|
|
292
|
+
return () => {
|
|
293
|
+
document.removeEventListener("keydown", onKey);
|
|
294
|
+
};
|
|
295
|
+
}, [full]);
|
|
296
|
+
if (svg === "") {
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
return /* @__PURE__ */ jsxs("div", { style: { margin: "1.5rem 0", borderRadius: 12, overflow: "hidden" }, children: [
|
|
300
|
+
/* @__PURE__ */ jsx(
|
|
301
|
+
Viewer,
|
|
302
|
+
{
|
|
303
|
+
svg,
|
|
304
|
+
fullscreen: false,
|
|
305
|
+
onToggleFullscreen: () => {
|
|
306
|
+
setFull(true);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
),
|
|
310
|
+
mounted && full ? createPortal(
|
|
311
|
+
/* @__PURE__ */ jsx(
|
|
312
|
+
"div",
|
|
313
|
+
{
|
|
314
|
+
onClick: (event) => {
|
|
315
|
+
if (event.target === event.currentTarget) {
|
|
316
|
+
setFull(false);
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
style: {
|
|
320
|
+
position: "fixed",
|
|
321
|
+
inset: 0,
|
|
322
|
+
zIndex: 9999,
|
|
323
|
+
background: "color-mix(in oklab, black 55%, transparent)",
|
|
324
|
+
backdropFilter: "blur(2px)"
|
|
325
|
+
},
|
|
326
|
+
children: /* @__PURE__ */ jsx(
|
|
327
|
+
"div",
|
|
328
|
+
{
|
|
329
|
+
style: {
|
|
330
|
+
position: "absolute",
|
|
331
|
+
inset: "clamp(20px, 3.5vw, 48px)",
|
|
332
|
+
borderRadius: 16,
|
|
333
|
+
border: "1px solid var(--color-fd-border)",
|
|
334
|
+
overflow: "hidden",
|
|
335
|
+
background: "var(--color-fd-background, #0a0a0b)",
|
|
336
|
+
backgroundImage: "radial-gradient(circle, color-mix(in oklab, var(--color-fd-muted-foreground) 22%, transparent) 1px, transparent 1px)",
|
|
337
|
+
backgroundSize: "22px 22px",
|
|
338
|
+
boxShadow: "0 24px 70px -12px rgba(0, 0, 0, 0.5)"
|
|
339
|
+
},
|
|
340
|
+
children: /* @__PURE__ */ jsx(
|
|
341
|
+
Viewer,
|
|
342
|
+
{
|
|
343
|
+
svg,
|
|
344
|
+
fullscreen: true,
|
|
345
|
+
onToggleFullscreen: () => {
|
|
346
|
+
setFull(false);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
)
|
|
350
|
+
}
|
|
351
|
+
)
|
|
352
|
+
}
|
|
353
|
+
),
|
|
354
|
+
document.body
|
|
355
|
+
) : null
|
|
356
|
+
] });
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export { Mermaid };
|
|
360
|
+
//# sourceMappingURL=mermaid.js.map
|
|
361
|
+
//# sourceMappingURL=mermaid.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/ui/mermaid.tsx"],"names":[],"mappings":"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","file":"mermaid.js","sourcesContent":["'use client';\n\nimport {\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n type PointerEvent as ReactPointerEvent,\n type ReactNode,\n} from 'react';\nimport { createPortal } from 'react-dom';\n\n/**\n * The Mermaid diagram viewer — renders a chart to SVG and wraps it in an\n * interactive stage: wheel to zoom, drag to pan, a control cluster, and a\n * full-screen lightbox on a dot-grid backdrop. Matches the premium viewer a\n * reader expects from a modern docs site.\n *\n * Client-only and self-contained: it dynamically imports `mermaid` (an optional\n * peer dependency), styles itself with inline styles keyed to the framework's\n * `--color-fd-*` theme variables, and re-renders when the site toggles dark\n * mode. It lives on its own entry (`@blaaiz/docs-core/ui/mermaid`) so the heavy\n * `mermaid` import never reaches the server-compatible `ui` barrel.\n *\n * @packageDocumentation\n */\n\nconst MIN_SCALE = 0.4;\nconst MAX_SCALE = 8;\nconst MAX_FIT_SCALE = 2.5;\nconst FIT_PADDING = 0.9;\n\ninterface View {\n readonly scale: number;\n readonly x: number;\n readonly y: number;\n}\n\nconst IDENTITY: View = { scale: 1, x: 0, y: 0 };\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value));\n}\n\nfunction isDarkTheme(): boolean {\n return document.documentElement.classList.contains('dark');\n}\n\n/**\n * Render a Mermaid chart to an SVG string, re-rendering when the chart changes\n * or the site switches between light and dark themes.\n */\nfunction useMermaidSvg(chart: string): string {\n const rawId = useId().replace(/[^a-zA-Z0-9]/g, '');\n const [svg, setSvg] = useState('');\n const [dark, setDark] = useState(false);\n\n useEffect(() => {\n setDark(isDarkTheme());\n const observer = new MutationObserver(() => {\n setDark(isDarkTheme());\n });\n observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });\n return () => {\n observer.disconnect();\n };\n }, []);\n\n useEffect(() => {\n let active = true;\n void (async () => {\n const mermaid = (await import('mermaid')).default;\n mermaid.initialize({\n startOnLoad: false,\n theme: dark ? 'dark' : 'neutral',\n securityLevel: 'loose',\n fontFamily: 'inherit',\n themeVariables: { fontSize: '14px' },\n });\n try {\n const result = await mermaid.render(`mmd-${rawId}`, chart);\n if (active) {\n setSvg(result.svg);\n }\n } catch {\n if (active) {\n setSvg('');\n }\n }\n })();\n return () => {\n active = false;\n };\n }, [chart, dark, rawId]);\n\n return svg;\n}\n\nconst ICONS = {\n zoomIn: <path d=\"M11 8v6M8 11h6M21 21l-4.35-4.35M11 18a7 7 0 1 0 0-14 7 7 0 0 0 0 14Z\" />,\n zoomOut: <path d=\"M8 11h6M21 21l-4.35-4.35M11 18a7 7 0 1 0 0-14 7 7 0 0 0 0 14Z\" />,\n reset: <path d=\"M3 12a9 9 0 1 0 3-6.7L3 8m0-5v5h5\" />,\n expand: (\n <path d=\"M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3M3 16v3a2 2 0 0 0 2 2h3m8 0h3a2 2 0 0 0 2-2v-3\" />\n ),\n close: <path d=\"M18 6 6 18M6 6l12 12\" />,\n};\n\nfunction ControlButton({\n label,\n onClick,\n children,\n}: {\n label: string;\n onClick: () => void;\n children: ReactNode;\n}) {\n return (\n <button\n type=\"button\"\n aria-label={label}\n onClick={onClick}\n style={{\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: 32,\n height: 32,\n padding: 0,\n border: '1px solid var(--color-fd-border)',\n borderRadius: 8,\n background: 'var(--color-fd-card)',\n color: 'var(--color-fd-muted-foreground)',\n cursor: 'pointer',\n }}\n onMouseEnter={(event) => {\n event.currentTarget.style.color = 'var(--color-fd-foreground)';\n event.currentTarget.style.background =\n 'color-mix(in oklab, var(--color-fd-primary) 10%, var(--color-fd-card))';\n }}\n onMouseLeave={(event) => {\n event.currentTarget.style.color = 'var(--color-fd-muted-foreground)';\n event.currentTarget.style.background = 'var(--color-fd-card)';\n }}\n >\n <svg\n width={16}\n height={16}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={1.8}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n {children}\n </svg>\n </button>\n );\n}\n\ninterface ViewerProps {\n readonly svg: string;\n readonly fullscreen: boolean;\n readonly onToggleFullscreen: () => void;\n}\n\n/**\n * The interactive diagram surface: the transformed SVG plus its control cluster.\n * The controls sit as a sibling of the draggable stage (not a child), so a click\n * on a control never triggers the stage's pan/pointer-capture. Reused for both\n * the inline card and the full-screen lightbox.\n */\nfunction Viewer({ svg, fullscreen, onToggleFullscreen }: ViewerProps) {\n const stageRef = useRef<HTMLDivElement>(null);\n const contentRef = useRef<HTMLDivElement>(null);\n const [view, setView] = useState<View>(IDENTITY);\n const drag = useRef<{ x: number; y: number } | null>(null);\n\n const zoomAt = useCallback((clientX: number, clientY: number, factor: number) => {\n const stage = stageRef.current;\n if (!stage) {\n return;\n }\n const rect = stage.getBoundingClientRect();\n const px = clientX - rect.left;\n const py = clientY - rect.top;\n setView((current) => {\n const scale = clamp(current.scale * factor, MIN_SCALE, MAX_SCALE);\n const ratio = scale / current.scale;\n return { scale, x: px - (px - current.x) * ratio, y: py - (py - current.y) * ratio };\n });\n }, []);\n\n const zoomFromCenter = useCallback(\n (factor: number) => {\n const stage = stageRef.current;\n if (!stage) {\n return;\n }\n const rect = stage.getBoundingClientRect();\n zoomAt(rect.left + rect.width / 2, rect.top + rect.height / 2, factor);\n },\n [zoomAt],\n );\n\n // Scale the diagram to fit the stage and centre it. `offsetWidth/Height` are\n // the untransformed layout size, so this is independent of the current zoom.\n const fit = useCallback(() => {\n const stage = stageRef.current;\n const content = contentRef.current;\n if (!stage || !content) {\n return;\n }\n const w = content.offsetWidth;\n const h = content.offsetHeight;\n if (w === 0 || h === 0) {\n return;\n }\n const sw = stage.clientWidth;\n const sh = stage.clientHeight;\n const scale = clamp(Math.min(sw / w, sh / h) * FIT_PADDING, MIN_SCALE, MAX_FIT_SCALE);\n setView({ scale, x: (sw - w * scale) / 2, y: (sh - h * scale) / 2 });\n }, []);\n\n // Wheel-zoom needs a non-passive listener; React attaches `onWheel` as passive\n // and would warn when we call preventDefault.\n useEffect(() => {\n const stage = stageRef.current;\n if (!stage) {\n return;\n }\n const onWheel = (event: WheelEvent) => {\n event.preventDefault();\n zoomAt(event.clientX, event.clientY, event.deltaY < 0 ? 1.12 : 1 / 1.12);\n };\n stage.addEventListener('wheel', onWheel, { passive: false });\n return () => {\n stage.removeEventListener('wheel', onWheel);\n };\n }, [zoomAt]);\n\n // Fit whenever the diagram or the surface size changes.\n useEffect(() => {\n fit();\n }, [svg, fullscreen, fit]);\n\n const onPointerDown = (event: ReactPointerEvent<HTMLDivElement>) => {\n drag.current = { x: event.clientX, y: event.clientY };\n event.currentTarget.setPointerCapture(event.pointerId);\n };\n\n const onPointerMove = (event: ReactPointerEvent<HTMLDivElement>) => {\n if (!drag.current) {\n return;\n }\n const dx = event.clientX - drag.current.x;\n const dy = event.clientY - drag.current.y;\n drag.current = { x: event.clientX, y: event.clientY };\n setView((current) => ({ ...current, x: current.x + dx, y: current.y + dy }));\n };\n\n const onPointerUp = (event: ReactPointerEvent<HTMLDivElement>) => {\n drag.current = null;\n if (event.currentTarget.hasPointerCapture(event.pointerId)) {\n event.currentTarget.releasePointerCapture(event.pointerId);\n }\n };\n\n return (\n <div style={{ position: 'relative', width: '100%', height: fullscreen ? '100%' : 420 }}>\n <div\n ref={stageRef}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={onPointerUp}\n onPointerLeave={onPointerUp}\n style={{\n position: 'absolute',\n inset: 0,\n overflow: 'hidden',\n cursor: 'grab',\n touchAction: 'none',\n }}\n >\n <div\n ref={contentRef}\n style={{\n transform: `translate(${String(view.x)}px, ${String(view.y)}px) scale(${String(view.scale)})`,\n transformOrigin: '0 0',\n display: 'inline-block',\n willChange: 'transform',\n }}\n dangerouslySetInnerHTML={{ __html: svg }}\n />\n </div>\n <div\n style={{\n position: 'absolute',\n bottom: 12,\n right: 12,\n display: 'flex',\n flexDirection: 'column',\n gap: 6,\n }}\n >\n <ControlButton\n label={fullscreen ? 'Exit full screen' : 'Full screen'}\n onClick={onToggleFullscreen}\n >\n {fullscreen ? ICONS.close : ICONS.expand}\n </ControlButton>\n <ControlButton\n label=\"Zoom in\"\n onClick={() => {\n zoomFromCenter(1.3);\n }}\n >\n {ICONS.zoomIn}\n </ControlButton>\n <ControlButton\n label=\"Zoom out\"\n onClick={() => {\n zoomFromCenter(1 / 1.3);\n }}\n >\n {ICONS.zoomOut}\n </ControlButton>\n <ControlButton label=\"Reset view\" onClick={fit}>\n {ICONS.reset}\n </ControlButton>\n </div>\n </div>\n );\n}\n\n/**\n * Render a Mermaid chart in an interactive, zoomable viewer.\n *\n * @param props - the Mermaid source in `chart`\n * @public\n */\nexport function Mermaid({ chart }: { chart: string }) {\n const svg = useMermaidSvg(chart);\n const [full, setFull] = useState(false);\n const [mounted, setMounted] = useState(false);\n\n useEffect(() => {\n setMounted(true);\n }, []);\n\n useEffect(() => {\n if (!full) {\n return;\n }\n const onKey = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n setFull(false);\n }\n };\n document.addEventListener('keydown', onKey);\n return () => {\n document.removeEventListener('keydown', onKey);\n };\n }, [full]);\n\n if (svg === '') {\n return null;\n }\n\n return (\n <div style={{ margin: '1.5rem 0', borderRadius: 12, overflow: 'hidden' }}>\n <Viewer\n svg={svg}\n fullscreen={false}\n onToggleFullscreen={() => {\n setFull(true);\n }}\n />\n {mounted && full\n ? createPortal(\n <div\n onClick={(event) => {\n // Click the dimmed gap around the panel to close.\n if (event.target === event.currentTarget) {\n setFull(false);\n }\n }}\n style={{\n position: 'fixed',\n inset: 0,\n zIndex: 9999,\n background: 'color-mix(in oklab, black 55%, transparent)',\n backdropFilter: 'blur(2px)',\n }}\n >\n <div\n style={{\n position: 'absolute',\n inset: 'clamp(20px, 3.5vw, 48px)',\n borderRadius: 16,\n border: '1px solid var(--color-fd-border)',\n overflow: 'hidden',\n background: 'var(--color-fd-background, #0a0a0b)',\n backgroundImage:\n 'radial-gradient(circle, color-mix(in oklab, var(--color-fd-muted-foreground) 22%, transparent) 1px, transparent 1px)',\n backgroundSize: '22px 22px',\n boxShadow: '0 24px 70px -12px rgba(0, 0, 0, 0.5)',\n }}\n >\n <Viewer\n svg={svg}\n fullscreen\n onToggleFullscreen={() => {\n setFull(false);\n }}\n />\n </div>\n </div>,\n document.body,\n )\n : null}\n </div>\n );\n}\n"]}
|