@simon_he/vue-tui 1.1.4 → 1.1.6
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/README.md +18 -18
- package/dist/agent/mermaid.d.cts +1 -1
- package/dist/agent/mermaid.d.ts +1 -1
- package/dist/agent.cjs +482 -148
- package/dist/agent.js +3 -3
- package/dist/{block-source-D3YGTErb.js → block-source-DByORvKG.js} +244 -51
- package/dist/cli.cjs +38 -19
- package/dist/cli.js +54 -14
- package/dist/experimental.cjs +6 -1
- package/dist/experimental.js +2 -2
- package/dist/markdown.cjs +475 -146
- package/dist/markdown.js +2 -2
- package/dist/{mermaid-uEfRItDe.js → mermaid-CWy4wtCL.js} +739 -163
- package/dist/mermaid.cjs +615 -176
- package/dist/mermaid.d.cts +1 -1
- package/dist/mermaid.d.ts +1 -1
- package/dist/mermaid.js +3 -3
- package/dist/renderer/terminal-graphics.d.ts +17 -0
- package/dist/{terminal-graphics-DWZrl3oh.js → terminal-graphics-D6KErRBY.js} +8 -3
- package/dist/{tlog-theme-DqnF_zNe.js → tlog-theme-DIrlCw6m.js} +1 -1
- package/dist/vue/components/TAgentTerminalGraphic.d.ts +3 -0
- package/dist/vue/components/TMermaidImage.d.ts +187 -0
- package/dist/vue/components/TVirtualMarkdown.d.ts +10 -0
- package/dist/vue/markdown/render.d.ts +18 -1
- package/dist/vue/mermaid/beautiful-mermaid.d.ts +282 -238
- package/dist/vue/mermaid/mermaid-image.d.ts +8 -0
- package/package.json +22 -10
|
@@ -1,128 +1,9 @@
|
|
|
1
1
|
import { EventZIndexContextKey, intersectRect, padEndByCells$1 as padEndByCells, repeatChar, sanitizeInlineText, sanitizeTextBlock, sliceByCells, sliceByCellsRange, spaces, textCellWidth$1 as textCellWidth, translateRect, useLayout, useRenderNode, useTerminal, useTerminalNode, useVisibility, withTextWidthProvider } from "./rect-JzWMJxxc.js";
|
|
2
2
|
import { TMermaidText$1 as TMermaidText, isSimpleMermaidFlowchartSource, markMermaidRenderErrorFatal, tMermaidTextProps } from "./TMermaidText-wLnMMvVS.js";
|
|
3
|
-
import { createTerminalGraphicPngSequence, getTerminalGraphicsOutput, getTerminalGraphicsOutputVersion, stableTerminalGraphicNumericId, subscribeTerminalGraphicsOutput } from "./terminal-graphics-
|
|
3
|
+
import { createTerminalGraphicPngSequence, getTerminalGraphicsOutput, getTerminalGraphicsOutputVersion, stableTerminalGraphicNumericId, subscribeTerminalGraphicsOutput } from "./terminal-graphics-D6KErRBY.js";
|
|
4
4
|
import { resolveMarkdownMathColor } from "./math-image-BZhmfUeu.js";
|
|
5
|
-
import { computed, defineComponent, getCurrentInstance, h, inject, onBeforeUnmount, shallowRef, watch } from "vue";
|
|
5
|
+
import { computed, defineComponent, getCurrentInstance, h, inject, markRaw, onBeforeUnmount, shallowRef, watch } from "vue";
|
|
6
6
|
|
|
7
|
-
//#region src/vue/mermaid/beautiful-mermaid.ts
|
|
8
|
-
const BEAUTIFUL_MERMAID_INSTALL_HINT = "Install beautiful-mermaid and use TMermaidText from @simon_he/vue-tui/mermaid or @simon_he/vue-tui/agent/mermaid, or pass a custom renderer prop.";
|
|
9
|
-
const MISSING_BEAUTIFUL_MERMAID_ERROR_CODE = "VUE_TUI_MISSING_BEAUTIFUL_MERMAID";
|
|
10
|
-
const INVALID_BEAUTIFUL_MERMAID_EXPORT_ERROR_CODE = "VUE_TUI_INVALID_BEAUTIFUL_MERMAID_EXPORT";
|
|
11
|
-
let cachedBeautifulMermaid = null;
|
|
12
|
-
function functionValue(value) {
|
|
13
|
-
return typeof value === "function" ? value : null;
|
|
14
|
-
}
|
|
15
|
-
function functionProp(target, key) {
|
|
16
|
-
if (!target || typeof target !== "object") return null;
|
|
17
|
-
if (!(key in target)) return null;
|
|
18
|
-
const value = target[key];
|
|
19
|
-
return typeof value === "function" ? value : null;
|
|
20
|
-
}
|
|
21
|
-
function errorMessage(error) {
|
|
22
|
-
return error instanceof Error ? error.message : String(error);
|
|
23
|
-
}
|
|
24
|
-
function codedError(message, code) {
|
|
25
|
-
const error = new Error(message);
|
|
26
|
-
error.code = code;
|
|
27
|
-
return error;
|
|
28
|
-
}
|
|
29
|
-
function missingBeautifulMermaidError(error) {
|
|
30
|
-
if (error && typeof error === "object" && errorCode(error) === MISSING_BEAUTIFUL_MERMAID_ERROR_CODE) return markMermaidRenderErrorFatal(error);
|
|
31
|
-
const detail = errorMessage(error);
|
|
32
|
-
return markMermaidRenderErrorFatal(codedError(`${BEAUTIFUL_MERMAID_INSTALL_HINT} (${detail})`, MISSING_BEAUTIFUL_MERMAID_ERROR_CODE));
|
|
33
|
-
}
|
|
34
|
-
function errorCode(error) {
|
|
35
|
-
if (!error || typeof error !== "object") return "";
|
|
36
|
-
const value = error.code;
|
|
37
|
-
return typeof value === "string" ? value : "";
|
|
38
|
-
}
|
|
39
|
-
function errorCause(error) {
|
|
40
|
-
if (!error || typeof error !== "object") return void 0;
|
|
41
|
-
return error.cause;
|
|
42
|
-
}
|
|
43
|
-
function isMissingBeautifulMermaid(error) {
|
|
44
|
-
return isMissingBeautifulMermaidWithSeen(error, new WeakSet());
|
|
45
|
-
}
|
|
46
|
-
function isMissingBeautifulMermaidWithSeen(error, seen) {
|
|
47
|
-
if (error && typeof error === "object") {
|
|
48
|
-
if (seen.has(error)) return false;
|
|
49
|
-
seen.add(error);
|
|
50
|
-
}
|
|
51
|
-
const message = errorMessage(error);
|
|
52
|
-
const code = errorCode(error);
|
|
53
|
-
if (code === MISSING_BEAUTIFUL_MERMAID_ERROR_CODE) return true;
|
|
54
|
-
const mentionsBeautifulMermaid = /Cannot find package ['"]beautiful-mermaid['"]/.test(message) || /Cannot find module ['"]beautiful-mermaid['"]/.test(message) || /Cannot resolve module ['"]beautiful-mermaid['"]/.test(message) || /Can't resolve ['"]beautiful-mermaid['"]/.test(message) || /Failed to resolve module specifier ['"]beautiful-mermaid['"]/.test(message) || /Failed to resolve import ['"]beautiful-mermaid['"]/.test(message) || /Could not resolve ['"]beautiful-mermaid['"]/.test(message);
|
|
55
|
-
if (mentionsBeautifulMermaid && (code === "" || code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND" || code === "UNRESOLVED_IMPORT")) return true;
|
|
56
|
-
const cause = errorCause(error);
|
|
57
|
-
return cause !== void 0 && isMissingBeautifulMermaidWithSeen(cause, seen);
|
|
58
|
-
}
|
|
59
|
-
function resolveBeautifulMermaidRenderer(mod) {
|
|
60
|
-
const renderer = functionProp(mod, "renderMermaidASCII") ?? functionProp(mod, "renderMermaidAscii") ?? functionValue(mod.default) ?? functionProp(mod.default, "renderMermaidASCII") ?? functionProp(mod.default, "renderMermaidAscii");
|
|
61
|
-
if (!renderer) throw markMermaidRenderErrorFatal(codedError("beautiful-mermaid is installed but does not export renderMermaidASCII.", INVALID_BEAUTIFUL_MERMAID_EXPORT_ERROR_CODE));
|
|
62
|
-
return renderer;
|
|
63
|
-
}
|
|
64
|
-
function hasErrorCode(error, code, seen = new WeakSet()) {
|
|
65
|
-
if (!error || typeof error !== "object") return false;
|
|
66
|
-
if (seen.has(error)) return false;
|
|
67
|
-
seen.add(error);
|
|
68
|
-
if (errorCode(error) === code) return true;
|
|
69
|
-
const cause = errorCause(error);
|
|
70
|
-
return cause !== void 0 && hasErrorCode(cause, code, seen);
|
|
71
|
-
}
|
|
72
|
-
function isBeautifulMermaidExportError(error) {
|
|
73
|
-
return hasErrorCode(error, INVALID_BEAUTIFUL_MERMAID_EXPORT_ERROR_CODE);
|
|
74
|
-
}
|
|
75
|
-
const isTransientBeautifulMermaidRenderError = (error) => {
|
|
76
|
-
if (isMissingBeautifulMermaid(error)) return false;
|
|
77
|
-
if (isBeautifulMermaidExportError(error)) return false;
|
|
78
|
-
return true;
|
|
79
|
-
};
|
|
80
|
-
async function loadBeautifulMermaid() {
|
|
81
|
-
if (!cachedBeautifulMermaid) cachedBeautifulMermaid = import("beautiful-mermaid").then((mod) => mod).catch((error) => {
|
|
82
|
-
cachedBeautifulMermaid = null;
|
|
83
|
-
if (isMissingBeautifulMermaid(error)) throw missingBeautifulMermaidError(error);
|
|
84
|
-
throw markMermaidRenderErrorFatal(error);
|
|
85
|
-
});
|
|
86
|
-
return cachedBeautifulMermaid;
|
|
87
|
-
}
|
|
88
|
-
const beautifulMermaidRenderer = async (code, options) => {
|
|
89
|
-
try {
|
|
90
|
-
const mod = await loadBeautifulMermaid();
|
|
91
|
-
const renderer = resolveBeautifulMermaidRenderer(mod);
|
|
92
|
-
return await renderer(code, options);
|
|
93
|
-
} catch (error) {
|
|
94
|
-
if (isMissingBeautifulMermaid(error)) throw missingBeautifulMermaidError(error);
|
|
95
|
-
throw error;
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
function createBeautifulMermaidRenderer() {
|
|
99
|
-
return beautifulMermaidRenderer;
|
|
100
|
-
}
|
|
101
|
-
const TMermaidText$1 = defineComponent({
|
|
102
|
-
name: "TMermaidText",
|
|
103
|
-
inheritAttrs: false,
|
|
104
|
-
props: tMermaidTextProps,
|
|
105
|
-
emits: { copy: (_payload) => true },
|
|
106
|
-
setup(props, { attrs, emit, slots }) {
|
|
107
|
-
return () => {
|
|
108
|
-
const renderer = props.renderer ?? beautifulMermaidRenderer;
|
|
109
|
-
const shouldRenderSource = props.shouldRenderSource ?? isSimpleMermaidFlowchartSource;
|
|
110
|
-
return h(TMermaidText, {
|
|
111
|
-
...attrs,
|
|
112
|
-
...props,
|
|
113
|
-
renderer,
|
|
114
|
-
shouldRenderSource,
|
|
115
|
-
isTransientError: props.isTransientError ?? isTransientBeautifulMermaidRenderError,
|
|
116
|
-
onCopy: (payload) => emit("copy", payload)
|
|
117
|
-
}, slots);
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
const TMermaid = TMermaidText$1;
|
|
122
|
-
const TBeautifulMermaidText = TMermaidText$1;
|
|
123
|
-
const TBeautifulMermaid = TMermaidText$1;
|
|
124
|
-
|
|
125
|
-
//#endregion
|
|
126
7
|
//#region src/vue/mermaid/mermaid-image.ts
|
|
127
8
|
const DEFAULT_MERMAID_IMAGE_OPTIONS = Object.freeze({
|
|
128
9
|
cellWidthPx: 8,
|
|
@@ -323,6 +204,223 @@ function svgRenderFunction(mod) {
|
|
|
323
204
|
for (const candidate of candidates) if (typeof candidate === "function") return candidate;
|
|
324
205
|
return null;
|
|
325
206
|
}
|
|
207
|
+
/**
|
|
208
|
+
* @resvg/resvg-js (usvg) does not support CSS custom properties (`var()`) or
|
|
209
|
+
* `color-mix()`. beautiful-mermaid 1.x emits both, so feeding the raw SVG to
|
|
210
|
+
* resvg renders every shape black. This resolver inlines all `var(--x)` /
|
|
211
|
+
* `color-mix(in srgb, ...)` color expressions into concrete hex values before
|
|
212
|
+
* rasterization. Exported for tests.
|
|
213
|
+
*/
|
|
214
|
+
function resolveMermaidSvgForResvg(svg, fallbackBg, fallbackFg) {
|
|
215
|
+
const { bg, fg } = extractSvgRootStyleColors(svg, fallbackBg, fallbackFg);
|
|
216
|
+
const variables = extractSvgCssVariables(svg);
|
|
217
|
+
const root = {
|
|
218
|
+
bg,
|
|
219
|
+
fg
|
|
220
|
+
};
|
|
221
|
+
let resolved = svg;
|
|
222
|
+
let guard = 0;
|
|
223
|
+
while ((resolved.includes("var(") || resolved.includes("color-mix(")) && guard++ < 500) {
|
|
224
|
+
const varIndex = resolved.lastIndexOf("var(");
|
|
225
|
+
const mixIndex = resolved.lastIndexOf("color-mix(");
|
|
226
|
+
const at = Math.max(varIndex, mixIndex);
|
|
227
|
+
if (at < 0) break;
|
|
228
|
+
const open = resolved.indexOf("(", at);
|
|
229
|
+
const close = findMatchingSvgParen(resolved, open);
|
|
230
|
+
if (open < 0 || close < 0) break;
|
|
231
|
+
const expr = resolved.slice(at, close + 1);
|
|
232
|
+
const color = resolveSvgColorExpr(expr, root, variables);
|
|
233
|
+
resolved = resolved.slice(0, at) + color + resolved.slice(close + 1);
|
|
234
|
+
}
|
|
235
|
+
return stripSvgStyleBlocks(resolved);
|
|
236
|
+
}
|
|
237
|
+
function extractSvgRootStyleColors(svg, fallbackBg, fallbackFg) {
|
|
238
|
+
let bg = fallbackBg;
|
|
239
|
+
let fg = fallbackFg;
|
|
240
|
+
const styleMatch = /<svg\b[^>]*\bstyle="([^"]*)"/i.exec(svg);
|
|
241
|
+
if (styleMatch?.[1]) {
|
|
242
|
+
const bgMatch = /(?:^|;)\s*--bg\s*:\s*([^;]+)/i.exec(styleMatch[1]);
|
|
243
|
+
const fgMatch = /(?:^|;)\s*--fg\s*:\s*([^;]+)/i.exec(styleMatch[1]);
|
|
244
|
+
if (bgMatch?.[1]) bg = bgMatch[1].trim();
|
|
245
|
+
if (fgMatch?.[1]) fg = fgMatch[1].trim();
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
bg,
|
|
249
|
+
fg
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function isSvgVarNameChar(ch) {
|
|
253
|
+
const code = ch.charCodeAt(0);
|
|
254
|
+
return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122 || ch === "_" || ch === "-";
|
|
255
|
+
}
|
|
256
|
+
function isSvgWhitespace(ch) {
|
|
257
|
+
return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f";
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Find the next `<style ...>...</style>` block starting at or after `from`.
|
|
261
|
+
* Uses `indexOf` scans only (no regex backtracking), so untrusted SVG input
|
|
262
|
+
* cannot trigger ReDoS. Returns the outer range plus the inner content range,
|
|
263
|
+
* or null when no complete style block exists.
|
|
264
|
+
*/
|
|
265
|
+
function findSvgStyleBlock(svg, from = 0) {
|
|
266
|
+
const lower = svg.toLowerCase();
|
|
267
|
+
const open = lower.indexOf("<style", from);
|
|
268
|
+
if (open < 0) return null;
|
|
269
|
+
const tagEnd = svg.indexOf(">", open);
|
|
270
|
+
if (tagEnd < 0) return null;
|
|
271
|
+
const close = lower.indexOf("</style", tagEnd + 1);
|
|
272
|
+
if (close < 0) return null;
|
|
273
|
+
const closeEnd = svg.indexOf(">", close);
|
|
274
|
+
if (closeEnd < 0) return null;
|
|
275
|
+
return {
|
|
276
|
+
start: open,
|
|
277
|
+
contentStart: tagEnd + 1,
|
|
278
|
+
contentEnd: close,
|
|
279
|
+
end: closeEnd + 1
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
function extractSvgCssVariables(svg) {
|
|
283
|
+
const map = new Map();
|
|
284
|
+
const block = findSvgStyleBlock(svg);
|
|
285
|
+
if (!block) return map;
|
|
286
|
+
const content = svg.slice(block.contentStart, block.contentEnd);
|
|
287
|
+
let cursor = 0;
|
|
288
|
+
while (cursor < content.length) {
|
|
289
|
+
const semi = content.indexOf(";", cursor);
|
|
290
|
+
if (semi < 0) break;
|
|
291
|
+
const statement = content.slice(cursor, semi);
|
|
292
|
+
let pos = 0;
|
|
293
|
+
while (pos < statement.length) {
|
|
294
|
+
const open = statement.indexOf("--", pos);
|
|
295
|
+
if (open < 0) break;
|
|
296
|
+
let nameEnd = open + 2;
|
|
297
|
+
while (nameEnd < statement.length && isSvgVarNameChar(statement[nameEnd])) nameEnd++;
|
|
298
|
+
if (nameEnd === open + 2) {
|
|
299
|
+
pos = open + 2;
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
let colonAt = nameEnd;
|
|
303
|
+
while (colonAt < statement.length && isSvgWhitespace(statement[colonAt])) colonAt++;
|
|
304
|
+
if (statement[colonAt] !== ":") {
|
|
305
|
+
pos = open + 2;
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
const value = statement.slice(colonAt + 1).trim();
|
|
309
|
+
if (value !== "" && !value.includes("{") && !value.includes("}")) map.set(statement.slice(open, nameEnd), value);
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
cursor = semi + 1;
|
|
313
|
+
}
|
|
314
|
+
return map;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* True when the `<style` at `open` looks like a real element opener: the tag
|
|
318
|
+
* name must be followed by whitespace, "/" or ">".
|
|
319
|
+
*/
|
|
320
|
+
function isSvgStyleOpener(svg, open) {
|
|
321
|
+
const after = svg[open + 6];
|
|
322
|
+
return after === ">" || after === "/" || isSvgWhitespace(after);
|
|
323
|
+
}
|
|
324
|
+
function stripSvgStyleBlocks(svg) {
|
|
325
|
+
let result = svg;
|
|
326
|
+
let block = findSvgStyleBlock(result);
|
|
327
|
+
while (block) {
|
|
328
|
+
result = result.slice(0, block.start) + result.slice(block.end);
|
|
329
|
+
block = findSvgStyleBlock(result, block.start);
|
|
330
|
+
}
|
|
331
|
+
let open = result.toLowerCase().indexOf("<style");
|
|
332
|
+
while (open >= 0) {
|
|
333
|
+
if (!isSvgStyleOpener(result, open)) {
|
|
334
|
+
open = result.toLowerCase().indexOf("<style", open + 6);
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
const nextLt = result.indexOf("<", open + 6);
|
|
338
|
+
const tagEnd = result.indexOf(">", open);
|
|
339
|
+
if (tagEnd < 0 || nextLt >= 0 && nextLt < tagEnd) {
|
|
340
|
+
open = result.toLowerCase().indexOf("<style", open + 6);
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
result = result.slice(0, open) + result.slice(tagEnd + 1);
|
|
344
|
+
open = result.toLowerCase().indexOf("<style", open);
|
|
345
|
+
}
|
|
346
|
+
return result;
|
|
347
|
+
}
|
|
348
|
+
function findMatchingSvgParen(input, openIndex) {
|
|
349
|
+
let depth = 0;
|
|
350
|
+
for (let i = openIndex; i < input.length; i++) {
|
|
351
|
+
const ch = input[i];
|
|
352
|
+
if (ch === "(") depth++;
|
|
353
|
+
else if (ch === ")") {
|
|
354
|
+
depth--;
|
|
355
|
+
if (depth === 0) return i;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return -1;
|
|
359
|
+
}
|
|
360
|
+
function findTopLevelSvgComma(input) {
|
|
361
|
+
let depth = 0;
|
|
362
|
+
for (let i = 0; i < input.length; i++) {
|
|
363
|
+
const ch = input[i];
|
|
364
|
+
if (ch === "(") depth++;
|
|
365
|
+
else if (ch === ")") depth--;
|
|
366
|
+
else if (ch === "," && depth === 0) return i;
|
|
367
|
+
}
|
|
368
|
+
return -1;
|
|
369
|
+
}
|
|
370
|
+
function normalizeHexColor(value) {
|
|
371
|
+
const match = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(value.trim());
|
|
372
|
+
if (!match) return null;
|
|
373
|
+
let hex = match[1];
|
|
374
|
+
if (hex.length === 3) hex = hex.split("").map((ch) => ch + ch).join("");
|
|
375
|
+
return `#${hex.toLowerCase()}`;
|
|
376
|
+
}
|
|
377
|
+
function mixSrgbColors(aHex, bHex, percent) {
|
|
378
|
+
const a = parseHexRgb(aHex);
|
|
379
|
+
const b = parseHexRgb(bHex);
|
|
380
|
+
const p = Math.max(0, Math.min(100, percent)) / 100;
|
|
381
|
+
const mix = (x, y) => Math.round(x * p + y * (1 - p));
|
|
382
|
+
const toHex = (value) => value.toString(16).padStart(2, "0");
|
|
383
|
+
return `#${toHex(mix(a[0], b[0]))}${toHex(mix(a[1], b[1]))}${toHex(mix(a[2], b[2]))}`;
|
|
384
|
+
}
|
|
385
|
+
function parseHexRgb(hex) {
|
|
386
|
+
return [
|
|
387
|
+
parseInt(hex.slice(1, 3), 16),
|
|
388
|
+
parseInt(hex.slice(3, 5), 16),
|
|
389
|
+
parseInt(hex.slice(5, 7), 16)
|
|
390
|
+
];
|
|
391
|
+
}
|
|
392
|
+
function resolveSvgColorExpr(expr, root, variables) {
|
|
393
|
+
const value = expr.trim();
|
|
394
|
+
if (value.startsWith("var(")) {
|
|
395
|
+
const close = findMatchingSvgParen(value, 3);
|
|
396
|
+
if (close < 0) return value;
|
|
397
|
+
const inner = value.slice(4, close).trim();
|
|
398
|
+
const comma = findTopLevelSvgComma(inner);
|
|
399
|
+
const name = (comma < 0 ? inner : inner.slice(0, comma)).trim();
|
|
400
|
+
const fallback = comma < 0 ? "" : inner.slice(comma + 1).trim();
|
|
401
|
+
const direct = name === "--fg" ? root.fg : name === "--bg" ? root.bg : variables.get(name);
|
|
402
|
+
if (direct != null) return resolveSvgColorExpr(direct, root, variables);
|
|
403
|
+
if (fallback) return resolveSvgColorExpr(fallback, root, variables);
|
|
404
|
+
return value;
|
|
405
|
+
}
|
|
406
|
+
if (value.startsWith("color-mix(")) {
|
|
407
|
+
const close = findMatchingSvgParen(value, 9);
|
|
408
|
+
if (close < 0) return value;
|
|
409
|
+
const inner = value.slice(10, close).trim();
|
|
410
|
+
const afterMethod = inner.replace(/^in\s+srgb\s*,\s*/i, "");
|
|
411
|
+
const comma = findTopLevelSvgComma(afterMethod);
|
|
412
|
+
if (comma < 0) return value;
|
|
413
|
+
const left = afterMethod.slice(0, comma).trim();
|
|
414
|
+
const right = afterMethod.slice(comma + 1).trim();
|
|
415
|
+
const leftMatch = /^([^%\s]+)\s+([\d.]+)%$/.exec(left) ?? /^([\d.]+)%\s+([^%\s]+)$/.exec(left);
|
|
416
|
+
if (!leftMatch) return value;
|
|
417
|
+
const a = normalizeHexColor(resolveSvgColorExpr(leftMatch[1], root, variables));
|
|
418
|
+
const b = normalizeHexColor(resolveSvgColorExpr(right, root, variables));
|
|
419
|
+
if (!a || !b) return value;
|
|
420
|
+
return mixSrgbColors(a, b, Number(leftMatch[2]));
|
|
421
|
+
}
|
|
422
|
+
return normalizeHexColor(value) ?? value;
|
|
423
|
+
}
|
|
326
424
|
function loadBuiltinMermaidRasterizer() {
|
|
327
425
|
if (!builtinRasterizerLoad) builtinRasterizerLoad = (async () => {
|
|
328
426
|
try {
|
|
@@ -339,7 +437,8 @@ function loadBuiltinMermaidRasterizer() {
|
|
|
339
437
|
});
|
|
340
438
|
const svgText = String(svg ?? "");
|
|
341
439
|
if (!svgText.includes("<svg")) return null;
|
|
342
|
-
const
|
|
440
|
+
const resolvedSvg = resolveMermaidSvgForResvg(svgText, options.bg, options.fg);
|
|
441
|
+
const rendered = new Resvg(resolvedSvg, { background: "rgba(0,0,0,0)" }).render();
|
|
343
442
|
const naturalWidth = positiveInt(rendered?.width);
|
|
344
443
|
const naturalHeight = positiveInt(rendered?.height);
|
|
345
444
|
const png = typeof rendered?.asPng === "function" ? rendered.asPng() : void 0;
|
|
@@ -514,6 +613,18 @@ const tMermaidImageProps = {
|
|
|
514
613
|
type: Number,
|
|
515
614
|
default: void 0
|
|
516
615
|
},
|
|
616
|
+
textRenderer: {
|
|
617
|
+
type: Function,
|
|
618
|
+
default: void 0
|
|
619
|
+
},
|
|
620
|
+
shouldRenderSource: {
|
|
621
|
+
type: Function,
|
|
622
|
+
default: void 0
|
|
623
|
+
},
|
|
624
|
+
textOptions: {
|
|
625
|
+
type: Object,
|
|
626
|
+
default: void 0
|
|
627
|
+
},
|
|
517
628
|
box: {
|
|
518
629
|
type: Boolean,
|
|
519
630
|
default: true
|
|
@@ -557,6 +668,26 @@ const tMermaidImageProps = {
|
|
|
557
668
|
ascii: {
|
|
558
669
|
type: Boolean,
|
|
559
670
|
default: false
|
|
671
|
+
},
|
|
672
|
+
zoomOnWheel: {
|
|
673
|
+
type: Boolean,
|
|
674
|
+
default: true
|
|
675
|
+
},
|
|
676
|
+
zoomModifier: {
|
|
677
|
+
type: String,
|
|
678
|
+
default: "metaCtrl"
|
|
679
|
+
},
|
|
680
|
+
minZoom: {
|
|
681
|
+
type: Number,
|
|
682
|
+
default: 1
|
|
683
|
+
},
|
|
684
|
+
maxZoom: {
|
|
685
|
+
type: Number,
|
|
686
|
+
default: 6
|
|
687
|
+
},
|
|
688
|
+
zoomSensitivity: {
|
|
689
|
+
type: Number,
|
|
690
|
+
default: .002
|
|
560
691
|
}
|
|
561
692
|
};
|
|
562
693
|
const TMermaidImage = defineComponent({
|
|
@@ -571,8 +702,16 @@ const TMermaidImage = defineComponent({
|
|
|
571
702
|
const { visible, rootProps } = useVisibility();
|
|
572
703
|
const parentEventZ = inject(EventZIndexContextKey, computed(() => 0));
|
|
573
704
|
const imageCells = shallowRef(null);
|
|
705
|
+
const textSnapshot = shallowRef(null);
|
|
706
|
+
const zoomScale = shallowRef(1);
|
|
707
|
+
const panX = shallowRef(0);
|
|
708
|
+
const panY = shallowRef(0);
|
|
574
709
|
const documentVersion = shallowRef(0);
|
|
575
710
|
const copied = shallowRef(false);
|
|
711
|
+
let dragState = null;
|
|
712
|
+
let dragMoved = false;
|
|
713
|
+
let lastDragAt = 0;
|
|
714
|
+
const DRAG_CLICK_SUPPRESS_MS = 250;
|
|
576
715
|
let builtOnce = false;
|
|
577
716
|
let renderVersion = 0;
|
|
578
717
|
let alive = true;
|
|
@@ -581,6 +720,7 @@ const TMermaidImage = defineComponent({
|
|
|
581
720
|
let copiedTimer = null;
|
|
582
721
|
let copyRequestVersion = 0;
|
|
583
722
|
let activeGraphicId = null;
|
|
723
|
+
let activeImageSignature = "";
|
|
584
724
|
const source = computed(() => props.code ?? props.content ?? "");
|
|
585
725
|
const waitingForStreamingSourceToFinish = computed(() => props.streaming && !props.final);
|
|
586
726
|
const graphicsOutputVersion = shallowRef(getTerminalGraphicsOutputVersion(terminal));
|
|
@@ -635,6 +775,73 @@ const TMermaidImage = defineComponent({
|
|
|
635
775
|
graphicsOutput.value?.clear?.(id);
|
|
636
776
|
} catch {}
|
|
637
777
|
}
|
|
778
|
+
function clampZoom(zoom) {
|
|
779
|
+
const min = Number.isFinite(Number(props.minZoom)) ? Math.max(.05, Number(props.minZoom)) : 1;
|
|
780
|
+
const max = Number.isFinite(Number(props.maxZoom)) ? Math.max(min, Number(props.maxZoom)) : Math.max(min, 6);
|
|
781
|
+
return Math.min(max, Math.max(min, zoom));
|
|
782
|
+
}
|
|
783
|
+
function zoomSensitivity() {
|
|
784
|
+
const value = Number(props.zoomSensitivity);
|
|
785
|
+
return Number.isFinite(value) && value > 0 ? value : .002;
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Wheel delta -> multiplicative zoom factor. Discrete notches (CLI wheel
|
|
789
|
+
* reporting and browser line-mode deltas) map to one step each; trackpad
|
|
790
|
+
* pixel deltas use a continuous exponential so small movements stay smooth.
|
|
791
|
+
*/
|
|
792
|
+
function wheelZoomFactor(deltaY, deltaMode) {
|
|
793
|
+
const sign = deltaY > 0 ? -1 : 1;
|
|
794
|
+
const sensitivity = zoomSensitivity();
|
|
795
|
+
const abs = Math.abs(deltaY);
|
|
796
|
+
const mode = deltaMode ?? 0;
|
|
797
|
+
const isNotch = mode === 1 || abs > 0 && abs <= 3 && Number.isInteger(deltaY);
|
|
798
|
+
const magnitude = isNotch ? 100 : abs;
|
|
799
|
+
return Math.exp(sign * magnitude * sensitivity);
|
|
800
|
+
}
|
|
801
|
+
function resetZoomState() {
|
|
802
|
+
if (zoomScale.value === 1 && panX.value === 0 && panY.value === 0) return;
|
|
803
|
+
zoomScale.value = 1;
|
|
804
|
+
panX.value = 0;
|
|
805
|
+
panY.value = 0;
|
|
806
|
+
dragState = null;
|
|
807
|
+
}
|
|
808
|
+
function hasZoomModifier(event) {
|
|
809
|
+
const modifier = props.zoomModifier ?? "metaCtrl";
|
|
810
|
+
if (modifier === "none") return true;
|
|
811
|
+
if (modifier === "meta") return event.metaKey === true;
|
|
812
|
+
if (modifier === "ctrl") return event.ctrlKey === true;
|
|
813
|
+
return event.ctrlKey === true || event.metaKey === true;
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Clamp the pan so the zoomed image always covers the container area
|
|
817
|
+
* (no empty gaps, no over-scroll beyond the image edges).
|
|
818
|
+
*/
|
|
819
|
+
function clampPan() {
|
|
820
|
+
const inner = innerRectFrom(fullRect.value);
|
|
821
|
+
const fit = displayFit.value;
|
|
822
|
+
if (!fit || inner.w <= 0 || inner.h <= 0) return;
|
|
823
|
+
const zoom = clampZoom(zoomScale.value);
|
|
824
|
+
const fullW = fit.displayW * zoom;
|
|
825
|
+
const fullH = fit.displayH * zoom;
|
|
826
|
+
const fitOriginX = inner.x + (inner.w > fit.displayW ? Math.floor((inner.w - fit.displayW) / 2) : 0);
|
|
827
|
+
let nextX = panX.value;
|
|
828
|
+
let nextY = panY.value;
|
|
829
|
+
if (fullW <= inner.w) nextX = 0;
|
|
830
|
+
else nextX = Math.min(inner.x - fitOriginX, Math.max(inner.x + inner.w - fullW - fitOriginX, nextX));
|
|
831
|
+
if (fullH <= inner.h) nextY = 0;
|
|
832
|
+
else nextY = Math.min(0, Math.max(inner.h - fullH, nextY));
|
|
833
|
+
if (nextX !== panX.value) panX.value = nextX;
|
|
834
|
+
if (nextY !== panY.value) panY.value = nextY;
|
|
835
|
+
}
|
|
836
|
+
/** Set the current image, resetting wheel zoom whenever the PNG changes. */
|
|
837
|
+
function setImageCells(cells) {
|
|
838
|
+
const signature = cells ? cells.base64 : "";
|
|
839
|
+
if (signature !== activeImageSignature) {
|
|
840
|
+
activeImageSignature = signature;
|
|
841
|
+
resetZoomState();
|
|
842
|
+
}
|
|
843
|
+
imageCells.value = cells;
|
|
844
|
+
}
|
|
638
845
|
const hasBox = computed(() => props.box !== false);
|
|
639
846
|
const normalizedWidth = computed(() => {
|
|
640
847
|
const width = Math.floor(Number(props.w));
|
|
@@ -643,6 +850,11 @@ const TMermaidImage = defineComponent({
|
|
|
643
850
|
const reservesBoxRows = computed(() => hasBox.value && normalizedWidth.value >= 2);
|
|
644
851
|
const boxChars = computed(() => props.ascii ? ASCII_MERMAID_IMAGE_BOX_CHARS : UNICODE_MERMAID_IMAGE_BOX_CHARS);
|
|
645
852
|
const sourceLines = computed(() => splitRenderedOutput(source.value));
|
|
853
|
+
const displayLines = computed(() => {
|
|
854
|
+
const snapshot = textSnapshot.value;
|
|
855
|
+
if (snapshot && snapshot.source === source.value && hasVisibleTextOutput(snapshot.lines)) return snapshot.lines;
|
|
856
|
+
return sourceLines.value;
|
|
857
|
+
});
|
|
646
858
|
const currentStyle = computed(() => props.style ?? defaultStyle.value);
|
|
647
859
|
const innerWidth = computed(() => {
|
|
648
860
|
const w = normalizedWidth.value;
|
|
@@ -664,7 +876,7 @@ const TMermaidImage = defineComponent({
|
|
|
664
876
|
});
|
|
665
877
|
const contentHeight = computed(() => {
|
|
666
878
|
if (!autoHeight.value) return fixedInnerHeight.value;
|
|
667
|
-
return displayFit.value?.displayH ?? Math.max(1,
|
|
879
|
+
return displayFit.value?.displayH ?? Math.max(1, displayLines.value.length);
|
|
668
880
|
});
|
|
669
881
|
const fullHeight = computed(() => {
|
|
670
882
|
if (props.h != null) {
|
|
@@ -717,6 +929,53 @@ const TMermaidImage = defineComponent({
|
|
|
717
929
|
padding: props.padding
|
|
718
930
|
});
|
|
719
931
|
}
|
|
932
|
+
function hasVisibleTextOutput(lines) {
|
|
933
|
+
return lines.some((line) => line.trim().length > 0);
|
|
934
|
+
}
|
|
935
|
+
function resolveTextOptions() {
|
|
936
|
+
const base = props.textOptions ?? {};
|
|
937
|
+
return {
|
|
938
|
+
...base,
|
|
939
|
+
useAscii: props.ascii,
|
|
940
|
+
colorMode: "none"
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
function textRenderSignature(code) {
|
|
944
|
+
return [
|
|
945
|
+
code,
|
|
946
|
+
props.ascii ? "a" : "",
|
|
947
|
+
JSON.stringify(props.textOptions ?? null),
|
|
948
|
+
props.textRenderer != null ? "r" : "",
|
|
949
|
+
props.shouldRenderSource != null ? "g" : ""
|
|
950
|
+
].join("");
|
|
951
|
+
}
|
|
952
|
+
async function renderTextWithTimeout(renderer, code, options) {
|
|
953
|
+
const timeoutMs = normalizeNonNegativeInt(props.renderTimeoutMs, 0);
|
|
954
|
+
if (timeoutMs <= 0) return await renderer(code, options);
|
|
955
|
+
let timer = null;
|
|
956
|
+
try {
|
|
957
|
+
return await Promise.race([Promise.resolve().then(() => renderer(code, options)), new Promise((_resolve, reject) => {
|
|
958
|
+
timer = setTimeout(() => reject(new Error("Mermaid text render timeout")), timeoutMs);
|
|
959
|
+
})]);
|
|
960
|
+
} finally {
|
|
961
|
+
if (timer != null) clearTimeout(timer);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
async function runCustomRasterizer(code, options) {
|
|
965
|
+
const renderer = props.renderer;
|
|
966
|
+
if (!renderer) return null;
|
|
967
|
+
const timeoutMs = normalizeNonNegativeInt(props.renderTimeoutMs, 0);
|
|
968
|
+
const run = () => renderer(code, options);
|
|
969
|
+
if (timeoutMs <= 0) return await run() ?? null;
|
|
970
|
+
let timer = null;
|
|
971
|
+
try {
|
|
972
|
+
return await Promise.race([run(), new Promise((resolve) => {
|
|
973
|
+
timer = setTimeout(() => resolve(null), timeoutMs);
|
|
974
|
+
})]) ?? null;
|
|
975
|
+
} finally {
|
|
976
|
+
if (timer != null) clearTimeout(timer);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
720
979
|
function shouldSkipRenderForSize(code) {
|
|
721
980
|
const maxChars = normalizeNonNegativeInt(props.maxRenderSourceChars, DEFAULT_MERMAID_IMAGE_MAX_RENDER_SOURCE_CHARS);
|
|
722
981
|
if (maxChars > 0 && code.length > maxChars) return true;
|
|
@@ -736,48 +995,107 @@ const TMermaidImage = defineComponent({
|
|
|
736
995
|
if (timer != null) clearTimeout(timer);
|
|
737
996
|
}
|
|
738
997
|
}
|
|
739
|
-
async function
|
|
998
|
+
async function renderTextFallback(version) {
|
|
999
|
+
const code = source.value;
|
|
1000
|
+
const renderer = props.textRenderer;
|
|
1001
|
+
if (!renderer) return;
|
|
1002
|
+
const guard = props.shouldRenderSource;
|
|
1003
|
+
if (guard) {
|
|
1004
|
+
let eligible = false;
|
|
1005
|
+
try {
|
|
1006
|
+
eligible = guard(code, {
|
|
1007
|
+
final: props.final,
|
|
1008
|
+
streaming: props.streaming
|
|
1009
|
+
});
|
|
1010
|
+
} catch {
|
|
1011
|
+
eligible = false;
|
|
1012
|
+
}
|
|
1013
|
+
if (!eligible) return;
|
|
1014
|
+
}
|
|
1015
|
+
try {
|
|
1016
|
+
const rendered = await renderTextWithTimeout(renderer, code, resolveTextOptions());
|
|
1017
|
+
if (!alive || version !== renderVersion) return;
|
|
1018
|
+
const lines = splitRenderedOutput(rendered);
|
|
1019
|
+
if (!hasVisibleTextOutput(lines)) return;
|
|
1020
|
+
textSnapshot.value = markRaw({
|
|
1021
|
+
source: code,
|
|
1022
|
+
signature: textRenderSignature(code),
|
|
1023
|
+
lines: markRaw(lines)
|
|
1024
|
+
});
|
|
1025
|
+
bump();
|
|
1026
|
+
} catch {}
|
|
1027
|
+
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Adaptive presentation state machine:
|
|
1030
|
+
*
|
|
1031
|
+
* raw source
|
|
1032
|
+
* -> image (terminal supports Kitty/iTerm2 graphics and the PNG
|
|
1033
|
+
* rasterizer succeeds), else
|
|
1034
|
+
* -> ANSI text (a textRenderer is available and the source passes the
|
|
1035
|
+
* eligibility guard), else
|
|
1036
|
+
* -> raw source
|
|
1037
|
+
*
|
|
1038
|
+
* While any pipeline is pending the raw source stays visible (source-first).
|
|
1039
|
+
*/
|
|
1040
|
+
async function renderPresentation(version) {
|
|
740
1041
|
const code = source.value;
|
|
741
1042
|
if (!code.trim()) {
|
|
742
1043
|
if (!alive || version !== renderVersion) return;
|
|
743
|
-
|
|
1044
|
+
setImageCells(null);
|
|
1045
|
+
textSnapshot.value = null;
|
|
744
1046
|
bump();
|
|
745
1047
|
return;
|
|
746
1048
|
}
|
|
747
1049
|
if (waitingForStreamingSourceToFinish.value || shouldSkipRenderForSize(code)) {
|
|
748
1050
|
if (!alive || version !== renderVersion) return;
|
|
749
|
-
|
|
1051
|
+
setImageCells(null);
|
|
1052
|
+
textSnapshot.value = null;
|
|
750
1053
|
bump();
|
|
751
1054
|
return;
|
|
752
1055
|
}
|
|
753
|
-
imageCells.value = null;
|
|
754
|
-
bump();
|
|
755
1056
|
const protocol = graphicsProtocol.value;
|
|
756
|
-
if (!protocol) return;
|
|
757
1057
|
const options = resolveImageOptions();
|
|
758
|
-
const cached = getCachedMermaidImage(code, options);
|
|
1058
|
+
const cached = protocol && !props.renderer ? getCachedMermaidImage(code, options) : null;
|
|
759
1059
|
if (cached) {
|
|
760
1060
|
if (!alive || version !== renderVersion) return;
|
|
761
|
-
|
|
1061
|
+
setImageCells(cached);
|
|
1062
|
+
textSnapshot.value = null;
|
|
762
1063
|
bump();
|
|
763
1064
|
return;
|
|
764
1065
|
}
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
1066
|
+
const currentSignature = textRenderSignature(code);
|
|
1067
|
+
const keepText = textSnapshot.value != null && textSnapshot.value.source === code && textSnapshot.value.signature === currentSignature;
|
|
1068
|
+
if (!keepText) {
|
|
1069
|
+
setImageCells(null);
|
|
1070
|
+
textSnapshot.value = null;
|
|
1071
|
+
bump();
|
|
771
1072
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
1073
|
+
if (protocol) {
|
|
1074
|
+
let result = null;
|
|
1075
|
+
if (props.renderer) result = await runCustomRasterizer(code, options);
|
|
1076
|
+
else if (isMermaidImageRendererReady()) result = await rasterizeWithTimeout(code, options);
|
|
1077
|
+
else {
|
|
1078
|
+
loadMermaidImageRenderer().then((ready) => {
|
|
1079
|
+
if (!alive || version !== renderVersion || !ready) return;
|
|
1080
|
+
renderPresentation(version);
|
|
1081
|
+
});
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
if (!alive || version !== renderVersion) return;
|
|
1085
|
+
if (result) {
|
|
1086
|
+
setImageCells(result);
|
|
1087
|
+
textSnapshot.value = null;
|
|
1088
|
+
bump();
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
if (keepText) return;
|
|
1093
|
+
await renderTextFallback(version);
|
|
776
1094
|
}
|
|
777
1095
|
function scheduleRender() {
|
|
778
1096
|
const version = ++renderVersion;
|
|
779
1097
|
resetCopyFeedback(false);
|
|
780
|
-
|
|
1098
|
+
setImageCells(null);
|
|
781
1099
|
if (waitingForStreamingSourceToFinish.value) {
|
|
782
1100
|
builtOnce = true;
|
|
783
1101
|
scheduler.cancelFrameTask?.(frameTaskId);
|
|
@@ -786,7 +1104,7 @@ const TMermaidImage = defineComponent({
|
|
|
786
1104
|
}
|
|
787
1105
|
if (!builtOnce || !props.streaming) {
|
|
788
1106
|
builtOnce = true;
|
|
789
|
-
|
|
1107
|
+
renderPresentation(version);
|
|
790
1108
|
return;
|
|
791
1109
|
}
|
|
792
1110
|
const accepted = scheduler.queueFrameTask({
|
|
@@ -797,10 +1115,10 @@ const TMermaidImage = defineComponent({
|
|
|
797
1115
|
run: () => {
|
|
798
1116
|
if (!alive) return;
|
|
799
1117
|
if (version !== renderVersion) return;
|
|
800
|
-
|
|
1118
|
+
renderPresentation(version);
|
|
801
1119
|
}
|
|
802
1120
|
});
|
|
803
|
-
if (accepted === false)
|
|
1121
|
+
if (accepted === false) renderPresentation(version);
|
|
804
1122
|
}
|
|
805
1123
|
watch([
|
|
806
1124
|
source,
|
|
@@ -817,7 +1135,11 @@ const TMermaidImage = defineComponent({
|
|
|
817
1135
|
() => props.final,
|
|
818
1136
|
() => props.maxRenderSourceChars,
|
|
819
1137
|
() => props.maxRenderSourceLines,
|
|
820
|
-
|
|
1138
|
+
() => props.textRenderer,
|
|
1139
|
+
() => props.shouldRenderSource,
|
|
1140
|
+
() => props.textOptions,
|
|
1141
|
+
() => props.ascii,
|
|
1142
|
+
() => props.zoomModifier
|
|
821
1143
|
], () => {
|
|
822
1144
|
scheduleRender();
|
|
823
1145
|
}, {
|
|
@@ -826,7 +1148,13 @@ const TMermaidImage = defineComponent({
|
|
|
826
1148
|
});
|
|
827
1149
|
const unsubscribeMermaidImage = subscribeMermaidImage(() => {
|
|
828
1150
|
if (!alive) return;
|
|
829
|
-
|
|
1151
|
+
if (!graphicsProtocol.value || props.renderer) return;
|
|
1152
|
+
const cached = getCachedMermaidImage(source.value, resolveImageOptions());
|
|
1153
|
+
if (!cached) return;
|
|
1154
|
+
if (!alive) return;
|
|
1155
|
+
setImageCells(cached);
|
|
1156
|
+
textSnapshot.value = null;
|
|
1157
|
+
bump();
|
|
830
1158
|
});
|
|
831
1159
|
const unsubscribeGraphicsOutput = subscribeTerminalGraphicsOutput(terminal, () => {
|
|
832
1160
|
if (!alive) return;
|
|
@@ -909,7 +1237,7 @@ const TMermaidImage = defineComponent({
|
|
|
909
1237
|
}
|
|
910
1238
|
function contentLine(rowIndex, width, pad) {
|
|
911
1239
|
const showImage = imageCells.value != null && displayFit.value != null && graphicsProtocol.value != null;
|
|
912
|
-
const src = showImage ? spaces(width) :
|
|
1240
|
+
const src = showImage ? spaces(width) : displayLines.value[rowIndex] ?? "";
|
|
913
1241
|
const clipped = sliceCells(src, width);
|
|
914
1242
|
return pad ? padCells(clipped, width) : clipped;
|
|
915
1243
|
}
|
|
@@ -937,12 +1265,28 @@ const TMermaidImage = defineComponent({
|
|
|
937
1265
|
queueClearGraphic();
|
|
938
1266
|
return;
|
|
939
1267
|
}
|
|
940
|
-
const
|
|
1268
|
+
const zoom = clampZoom(zoomScale.value);
|
|
1269
|
+
const fitW = fit.displayW;
|
|
1270
|
+
const fitH = fit.displayH;
|
|
1271
|
+
const fitOriginX = inner.x + (inner.w > fitW ? Math.floor((inner.w - fitW) / 2) : 0);
|
|
1272
|
+
const fullRect$1 = {
|
|
1273
|
+
x: fitOriginX + panX.value,
|
|
1274
|
+
y: inner.y + panY.value,
|
|
1275
|
+
w: fitW * zoom,
|
|
1276
|
+
h: fitH * zoom
|
|
1277
|
+
};
|
|
1278
|
+
const visible$1 = intersectRect(fullRect$1, inner);
|
|
1279
|
+
if (!visible$1 || visible$1.w < 1 || visible$1.h < 1) {
|
|
1280
|
+
queueClearGraphic();
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1283
|
+
const rectX = Math.max(inner.x, Math.round(visible$1.x));
|
|
1284
|
+
const rectY = Math.max(inner.y, Math.round(visible$1.y));
|
|
941
1285
|
const rect = {
|
|
942
|
-
x:
|
|
943
|
-
y:
|
|
944
|
-
w:
|
|
945
|
-
h:
|
|
1286
|
+
x: rectX,
|
|
1287
|
+
y: rectY,
|
|
1288
|
+
w: Math.max(1, Math.min(inner.x + inner.w, rectX + Math.round(visible$1.w)) - rectX),
|
|
1289
|
+
h: Math.max(1, Math.min(inner.y + inner.h, rectY + Math.round(visible$1.h)) - rectY)
|
|
946
1290
|
};
|
|
947
1291
|
const imageId = stableTerminalGraphicNumericId(`mermaid-image:${uid}:${renderVersion}:img`);
|
|
948
1292
|
const placementId = stableTerminalGraphicNumericId(`mermaid-image:${uid}:${renderVersion}:plc`);
|
|
@@ -955,10 +1299,13 @@ const TMermaidImage = defineComponent({
|
|
|
955
1299
|
rows: cells.heightCells,
|
|
956
1300
|
sourceWidth: cells.naturalWidth,
|
|
957
1301
|
sourceHeight: cells.naturalHeight,
|
|
958
|
-
placementColumns:
|
|
959
|
-
placementRows:
|
|
1302
|
+
placementColumns: rect.w,
|
|
1303
|
+
placementRows: rect.h,
|
|
1304
|
+
transmissionColumns: fitW,
|
|
1305
|
+
transmissionRows: fitH,
|
|
1306
|
+
cropTransmission: false,
|
|
960
1307
|
rect,
|
|
961
|
-
fullRect:
|
|
1308
|
+
fullRect: fullRect$1,
|
|
962
1309
|
zIndex: -1,
|
|
963
1310
|
fallback: source.value
|
|
964
1311
|
});
|
|
@@ -982,13 +1329,14 @@ const TMermaidImage = defineComponent({
|
|
|
982
1329
|
id,
|
|
983
1330
|
x: rect.x,
|
|
984
1331
|
y: rect.y,
|
|
985
|
-
w:
|
|
986
|
-
h:
|
|
1332
|
+
w: rect.w,
|
|
1333
|
+
h: rect.h,
|
|
987
1334
|
protocol,
|
|
988
1335
|
sequence: built.sequence,
|
|
989
1336
|
resizeSequence: built.resizeSequence,
|
|
990
1337
|
clearSequence: built.clearSequence,
|
|
991
|
-
fallbackText: source.value
|
|
1338
|
+
fallbackText: source.value,
|
|
1339
|
+
placementMoveWithoutClear: protocol === "kitty"
|
|
992
1340
|
});
|
|
993
1341
|
if (accepted) activeGraphicId = id;
|
|
994
1342
|
else queueClearGraphic();
|
|
@@ -1023,7 +1371,7 @@ const TMermaidImage = defineComponent({
|
|
|
1023
1371
|
};
|
|
1024
1372
|
});
|
|
1025
1373
|
const contentHitRect = computed(() => {
|
|
1026
|
-
if (!visible.value
|
|
1374
|
+
if (!visible.value) return {
|
|
1027
1375
|
x: 0,
|
|
1028
1376
|
y: 0,
|
|
1029
1377
|
w: 0,
|
|
@@ -1095,6 +1443,7 @@ const TMermaidImage = defineComponent({
|
|
|
1095
1443
|
function onCopyClick(event) {
|
|
1096
1444
|
event.preventDefault();
|
|
1097
1445
|
event.stopPropagation();
|
|
1446
|
+
if (Date.now() - lastDragAt < DRAG_CLICK_SUPPRESS_MS) return;
|
|
1098
1447
|
copySource();
|
|
1099
1448
|
}
|
|
1100
1449
|
function onCopyKeydown(event) {
|
|
@@ -1103,6 +1452,78 @@ const TMermaidImage = defineComponent({
|
|
|
1103
1452
|
event.stopPropagation();
|
|
1104
1453
|
copySource();
|
|
1105
1454
|
}
|
|
1455
|
+
const zoomEnabled = computed(() => props.zoomOnWheel !== false && imageCells.value != null && displayFit.value != null && graphicsProtocol.value === "kitty" && contentHitRect.value.w > 0 && contentHitRect.value.h > 0);
|
|
1456
|
+
const dragEnabled = computed(() => props.zoomOnWheel !== false && imageCells.value != null && displayFit.value != null && graphicsProtocol.value === "kitty" && zoomScale.value > 1 && contentHitRect.value.w > 0 && contentHitRect.value.h > 0);
|
|
1457
|
+
function onWheel(event) {
|
|
1458
|
+
if (!zoomEnabled.value) return;
|
|
1459
|
+
if (!hasZoomModifier(event)) return;
|
|
1460
|
+
event.preventDefault();
|
|
1461
|
+
event.stopPropagation();
|
|
1462
|
+
const deltaY = event.deltaY ?? 0;
|
|
1463
|
+
if (!Number.isFinite(deltaY) || deltaY === 0) return;
|
|
1464
|
+
const inner = innerRectFrom(fullRect.value);
|
|
1465
|
+
const fit = displayFit.value;
|
|
1466
|
+
if (!fit || inner.w <= 0 || inner.h <= 0) return;
|
|
1467
|
+
const currentZoom = clampZoom(zoomScale.value);
|
|
1468
|
+
const fitW = fit.displayW;
|
|
1469
|
+
const fitH = fit.displayH;
|
|
1470
|
+
const fitOriginX = inner.x + (inner.w > fitW ? Math.floor((inner.w - fitW) / 2) : 0);
|
|
1471
|
+
const fullX = fitOriginX + panX.value;
|
|
1472
|
+
const fullY = inner.y + panY.value;
|
|
1473
|
+
const mouseX = event.cellX;
|
|
1474
|
+
const mouseY = event.cellY;
|
|
1475
|
+
const fx = currentZoom > 0 ? (mouseX - fullX) / (fitW * currentZoom) : .5;
|
|
1476
|
+
const fy = currentZoom > 0 ? (mouseY - fullY) / (fitH * currentZoom) : .5;
|
|
1477
|
+
const nextZoom = clampZoom(currentZoom * wheelZoomFactor(deltaY, event.deltaMode));
|
|
1478
|
+
if (nextZoom === currentZoom) return;
|
|
1479
|
+
const nextW = fitW * nextZoom;
|
|
1480
|
+
const nextH = fitH * nextZoom;
|
|
1481
|
+
let nextX = mouseX - fx * nextW;
|
|
1482
|
+
let nextY = mouseY - fy * nextH;
|
|
1483
|
+
if (nextW <= inner.w) nextX = inner.x + (inner.w - nextW) / 2;
|
|
1484
|
+
else nextX = Math.min(inner.x, Math.max(inner.x + inner.w - nextW, nextX));
|
|
1485
|
+
if (nextH <= inner.h) nextY = inner.y + (inner.h - nextH) / 2;
|
|
1486
|
+
else nextY = Math.min(inner.y, Math.max(inner.y + inner.h - nextH, nextY));
|
|
1487
|
+
zoomScale.value = nextZoom;
|
|
1488
|
+
panX.value = nextX - fitOriginX;
|
|
1489
|
+
panY.value = nextY - inner.y;
|
|
1490
|
+
clampPan();
|
|
1491
|
+
bump();
|
|
1492
|
+
}
|
|
1493
|
+
function onPointerDown(event) {
|
|
1494
|
+
if (!dragEnabled.value) return;
|
|
1495
|
+
event.preventDefault();
|
|
1496
|
+
event.stopPropagation();
|
|
1497
|
+
dragState = {
|
|
1498
|
+
startX: event.cellX,
|
|
1499
|
+
startY: event.cellY,
|
|
1500
|
+
panStartX: panX.value,
|
|
1501
|
+
panStartY: panY.value
|
|
1502
|
+
};
|
|
1503
|
+
dragMoved = false;
|
|
1504
|
+
}
|
|
1505
|
+
function onPointerMove(event) {
|
|
1506
|
+
const drag = dragState;
|
|
1507
|
+
if (!drag) return;
|
|
1508
|
+
event.preventDefault();
|
|
1509
|
+
event.stopPropagation();
|
|
1510
|
+
const dx = event.cellX - drag.startX;
|
|
1511
|
+
const dy = event.cellY - drag.startY;
|
|
1512
|
+
if (!dragMoved && Math.abs(dx) + Math.abs(dy) >= 1) dragMoved = true;
|
|
1513
|
+
if (!dragMoved) return;
|
|
1514
|
+
panX.value = drag.panStartX + dx;
|
|
1515
|
+
panY.value = drag.panStartY + dy;
|
|
1516
|
+
clampPan();
|
|
1517
|
+
bump();
|
|
1518
|
+
}
|
|
1519
|
+
function onPointerUp(event) {
|
|
1520
|
+
const drag = dragState;
|
|
1521
|
+
if (!drag) return;
|
|
1522
|
+
event.preventDefault();
|
|
1523
|
+
event.stopPropagation();
|
|
1524
|
+
dragState = null;
|
|
1525
|
+
if (dragMoved) lastDragAt = Date.now();
|
|
1526
|
+
}
|
|
1106
1527
|
const copyNodeActive = computed(() => visible.value && props.copyButton && copyHitRect.value.w > 0 && copyHitRect.value.h > 0);
|
|
1107
1528
|
useTerminalNode(() => ({
|
|
1108
1529
|
rect: copyHitRect.value,
|
|
@@ -1115,14 +1536,20 @@ const TMermaidImage = defineComponent({
|
|
|
1115
1536
|
keydown: onCopyKeydown
|
|
1116
1537
|
} : {}
|
|
1117
1538
|
}));
|
|
1118
|
-
const contentNodeActive = computed(() => visible.value && props.copyOnClick && contentHitRect.value.w > 0 && contentHitRect.value.h > 0);
|
|
1539
|
+
const contentNodeActive = computed(() => visible.value && (props.copyOnClick || zoomEnabled.value || dragEnabled.value) && contentHitRect.value.w > 0 && contentHitRect.value.h > 0);
|
|
1119
1540
|
useTerminalNode(() => ({
|
|
1120
1541
|
rect: contentHitRect.value,
|
|
1121
1542
|
zIndex: (parentEventZ.value ?? 0) + props.zIndex,
|
|
1122
1543
|
visible: contentNodeActive.value,
|
|
1123
1544
|
focusable: false,
|
|
1124
1545
|
selectable: false,
|
|
1125
|
-
handlers: contentNodeActive.value ? {
|
|
1546
|
+
handlers: contentNodeActive.value ? {
|
|
1547
|
+
click: props.copyOnClick ? onCopyClick : void 0,
|
|
1548
|
+
wheel: zoomEnabled.value ? onWheel : void 0,
|
|
1549
|
+
pointerdown: dragEnabled.value ? onPointerDown : void 0,
|
|
1550
|
+
pointermove: dragEnabled.value ? onPointerMove : void 0,
|
|
1551
|
+
pointerup: dragEnabled.value ? onPointerUp : void 0
|
|
1552
|
+
} : {}
|
|
1126
1553
|
}));
|
|
1127
1554
|
useRenderNode(() => ({
|
|
1128
1555
|
zIndex: props.zIndex,
|
|
@@ -1138,6 +1565,11 @@ const TMermaidImage = defineComponent({
|
|
|
1138
1565
|
fullRect.value,
|
|
1139
1566
|
imageCells.value,
|
|
1140
1567
|
displayFit.value,
|
|
1568
|
+
zoomScale.value,
|
|
1569
|
+
panX.value,
|
|
1570
|
+
panY.value,
|
|
1571
|
+
textSnapshot.value,
|
|
1572
|
+
displayLines.value,
|
|
1141
1573
|
sourceLines.value,
|
|
1142
1574
|
currentStyle.value,
|
|
1143
1575
|
props.clear,
|
|
@@ -1202,4 +1634,148 @@ const TMermaidImage = defineComponent({
|
|
|
1202
1634
|
});
|
|
1203
1635
|
|
|
1204
1636
|
//#endregion
|
|
1205
|
-
|
|
1637
|
+
//#region src/vue/mermaid/beautiful-mermaid.ts
|
|
1638
|
+
const BEAUTIFUL_MERMAID_INSTALL_HINT = "Install beautiful-mermaid and use TMermaidText from @simon_he/vue-tui/mermaid or @simon_he/vue-tui/agent/mermaid, or pass a custom renderer prop.";
|
|
1639
|
+
const MISSING_BEAUTIFUL_MERMAID_ERROR_CODE = "VUE_TUI_MISSING_BEAUTIFUL_MERMAID";
|
|
1640
|
+
const INVALID_BEAUTIFUL_MERMAID_EXPORT_ERROR_CODE = "VUE_TUI_INVALID_BEAUTIFUL_MERMAID_EXPORT";
|
|
1641
|
+
let cachedBeautifulMermaid = null;
|
|
1642
|
+
function functionValue(value) {
|
|
1643
|
+
return typeof value === "function" ? value : null;
|
|
1644
|
+
}
|
|
1645
|
+
function functionProp(target, key) {
|
|
1646
|
+
if (!target || typeof target !== "object") return null;
|
|
1647
|
+
if (!(key in target)) return null;
|
|
1648
|
+
const value = target[key];
|
|
1649
|
+
return typeof value === "function" ? value : null;
|
|
1650
|
+
}
|
|
1651
|
+
function errorMessage(error) {
|
|
1652
|
+
return error instanceof Error ? error.message : String(error);
|
|
1653
|
+
}
|
|
1654
|
+
function codedError(message, code) {
|
|
1655
|
+
const error = new Error(message);
|
|
1656
|
+
error.code = code;
|
|
1657
|
+
return error;
|
|
1658
|
+
}
|
|
1659
|
+
function missingBeautifulMermaidError(error) {
|
|
1660
|
+
if (error && typeof error === "object" && errorCode(error) === MISSING_BEAUTIFUL_MERMAID_ERROR_CODE) return markMermaidRenderErrorFatal(error);
|
|
1661
|
+
const detail = errorMessage(error);
|
|
1662
|
+
return markMermaidRenderErrorFatal(codedError(`${BEAUTIFUL_MERMAID_INSTALL_HINT} (${detail})`, MISSING_BEAUTIFUL_MERMAID_ERROR_CODE));
|
|
1663
|
+
}
|
|
1664
|
+
function errorCode(error) {
|
|
1665
|
+
if (!error || typeof error !== "object") return "";
|
|
1666
|
+
const value = error.code;
|
|
1667
|
+
return typeof value === "string" ? value : "";
|
|
1668
|
+
}
|
|
1669
|
+
function errorCause(error) {
|
|
1670
|
+
if (!error || typeof error !== "object") return void 0;
|
|
1671
|
+
return error.cause;
|
|
1672
|
+
}
|
|
1673
|
+
function isMissingBeautifulMermaid(error) {
|
|
1674
|
+
return isMissingBeautifulMermaidWithSeen(error, new WeakSet());
|
|
1675
|
+
}
|
|
1676
|
+
function isMissingBeautifulMermaidWithSeen(error, seen) {
|
|
1677
|
+
if (error && typeof error === "object") {
|
|
1678
|
+
if (seen.has(error)) return false;
|
|
1679
|
+
seen.add(error);
|
|
1680
|
+
}
|
|
1681
|
+
const message = errorMessage(error);
|
|
1682
|
+
const code = errorCode(error);
|
|
1683
|
+
if (code === MISSING_BEAUTIFUL_MERMAID_ERROR_CODE) return true;
|
|
1684
|
+
const mentionsBeautifulMermaid = /Cannot find package ['"]beautiful-mermaid['"]/.test(message) || /Cannot find module ['"]beautiful-mermaid['"]/.test(message) || /Cannot resolve module ['"]beautiful-mermaid['"]/.test(message) || /Can't resolve ['"]beautiful-mermaid['"]/.test(message) || /Failed to resolve module specifier ['"]beautiful-mermaid['"]/.test(message) || /Failed to resolve import ['"]beautiful-mermaid['"]/.test(message) || /Could not resolve ['"]beautiful-mermaid['"]/.test(message);
|
|
1685
|
+
if (mentionsBeautifulMermaid && (code === "" || code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND" || code === "UNRESOLVED_IMPORT")) return true;
|
|
1686
|
+
const cause = errorCause(error);
|
|
1687
|
+
return cause !== void 0 && isMissingBeautifulMermaidWithSeen(cause, seen);
|
|
1688
|
+
}
|
|
1689
|
+
function resolveBeautifulMermaidRenderer(mod) {
|
|
1690
|
+
const renderer = functionProp(mod, "renderMermaidASCII") ?? functionProp(mod, "renderMermaidAscii") ?? functionValue(mod.default) ?? functionProp(mod.default, "renderMermaidASCII") ?? functionProp(mod.default, "renderMermaidAscii");
|
|
1691
|
+
if (!renderer) throw markMermaidRenderErrorFatal(codedError("beautiful-mermaid is installed but does not export renderMermaidASCII.", INVALID_BEAUTIFUL_MERMAID_EXPORT_ERROR_CODE));
|
|
1692
|
+
return renderer;
|
|
1693
|
+
}
|
|
1694
|
+
function hasErrorCode(error, code, seen = new WeakSet()) {
|
|
1695
|
+
if (!error || typeof error !== "object") return false;
|
|
1696
|
+
if (seen.has(error)) return false;
|
|
1697
|
+
seen.add(error);
|
|
1698
|
+
if (errorCode(error) === code) return true;
|
|
1699
|
+
const cause = errorCause(error);
|
|
1700
|
+
return cause !== void 0 && hasErrorCode(cause, code, seen);
|
|
1701
|
+
}
|
|
1702
|
+
function isBeautifulMermaidExportError(error) {
|
|
1703
|
+
return hasErrorCode(error, INVALID_BEAUTIFUL_MERMAID_EXPORT_ERROR_CODE);
|
|
1704
|
+
}
|
|
1705
|
+
const isTransientBeautifulMermaidRenderError = (error) => {
|
|
1706
|
+
if (isMissingBeautifulMermaid(error)) return false;
|
|
1707
|
+
if (isBeautifulMermaidExportError(error)) return false;
|
|
1708
|
+
return true;
|
|
1709
|
+
};
|
|
1710
|
+
async function loadBeautifulMermaid() {
|
|
1711
|
+
if (!cachedBeautifulMermaid) cachedBeautifulMermaid = import("beautiful-mermaid").then((mod) => mod).catch((error) => {
|
|
1712
|
+
cachedBeautifulMermaid = null;
|
|
1713
|
+
if (isMissingBeautifulMermaid(error)) throw missingBeautifulMermaidError(error);
|
|
1714
|
+
throw markMermaidRenderErrorFatal(error);
|
|
1715
|
+
});
|
|
1716
|
+
return cachedBeautifulMermaid;
|
|
1717
|
+
}
|
|
1718
|
+
const beautifulMermaidRenderer = async (code, options) => {
|
|
1719
|
+
try {
|
|
1720
|
+
const mod = await loadBeautifulMermaid();
|
|
1721
|
+
const renderer = resolveBeautifulMermaidRenderer(mod);
|
|
1722
|
+
return await renderer(code, options);
|
|
1723
|
+
} catch (error) {
|
|
1724
|
+
if (isMissingBeautifulMermaid(error)) throw missingBeautifulMermaidError(error);
|
|
1725
|
+
throw error;
|
|
1726
|
+
}
|
|
1727
|
+
};
|
|
1728
|
+
function createBeautifulMermaidRenderer() {
|
|
1729
|
+
return beautifulMermaidRenderer;
|
|
1730
|
+
}
|
|
1731
|
+
const TMermaidText$1 = defineComponent({
|
|
1732
|
+
name: "TMermaidText",
|
|
1733
|
+
inheritAttrs: false,
|
|
1734
|
+
props: tMermaidTextProps,
|
|
1735
|
+
emits: { copy: (_payload) => true },
|
|
1736
|
+
setup(props, { attrs, emit, slots }) {
|
|
1737
|
+
return () => {
|
|
1738
|
+
const renderer = props.renderer ?? beautifulMermaidRenderer;
|
|
1739
|
+
const shouldRenderSource = props.shouldRenderSource ?? isSimpleMermaidFlowchartSource;
|
|
1740
|
+
return h(TMermaidText, {
|
|
1741
|
+
...attrs,
|
|
1742
|
+
...props,
|
|
1743
|
+
renderer,
|
|
1744
|
+
shouldRenderSource,
|
|
1745
|
+
isTransientError: props.isTransientError ?? isTransientBeautifulMermaidRenderError,
|
|
1746
|
+
onCopy: (payload) => emit("copy", payload)
|
|
1747
|
+
}, slots);
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
});
|
|
1751
|
+
/**
|
|
1752
|
+
* Adaptive default mermaid component: renders the diagram as a terminal image
|
|
1753
|
+
* (Kitty/iTerm2 graphics) when the terminal supports it, otherwise falls back
|
|
1754
|
+
* to the beautiful-mermaid ASCII renderer, otherwise shows the raw source.
|
|
1755
|
+
* While any pipeline is pending the raw source stays visible (source-first);
|
|
1756
|
+
* streaming sources are only rendered once `final` is true.
|
|
1757
|
+
*/
|
|
1758
|
+
const TMermaid = defineComponent({
|
|
1759
|
+
name: "TMermaid",
|
|
1760
|
+
inheritAttrs: false,
|
|
1761
|
+
props: tMermaidImageProps,
|
|
1762
|
+
emits: { copy: (_payload) => true },
|
|
1763
|
+
setup(props, { attrs, emit, slots }) {
|
|
1764
|
+
return () => {
|
|
1765
|
+
const { renderer: _legacyTextRenderer,...rest } = props;
|
|
1766
|
+
const textRenderer = props.textRenderer ?? props.renderer ?? beautifulMermaidRenderer;
|
|
1767
|
+
return h(TMermaidImage, {
|
|
1768
|
+
...attrs,
|
|
1769
|
+
...rest,
|
|
1770
|
+
textRenderer,
|
|
1771
|
+
shouldRenderSource: props.shouldRenderSource ?? isSimpleMermaidFlowchartSource,
|
|
1772
|
+
onCopy: (payload) => emit("copy", payload)
|
|
1773
|
+
}, slots);
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
});
|
|
1777
|
+
const TBeautifulMermaidText = TMermaidText$1;
|
|
1778
|
+
const TBeautifulMermaid = TMermaid;
|
|
1779
|
+
|
|
1780
|
+
//#endregion
|
|
1781
|
+
export { TBeautifulMermaid, TBeautifulMermaidText, TMermaid, TMermaidImage, TMermaidText$1 as TMermaidText, beautifulMermaidRenderer, clearMermaidImageCache, createBeautifulMermaidRenderer, getCachedMermaidImage, getMermaidImage, isMermaidImageRendererReady, loadMermaidImageRenderer, normalizeMermaidImageOptions, resolveMermaidImageColor, resolveMermaidSvgForResvg, setMermaidImageRasterizer, subscribeMermaidImage };
|