@simon_he/vue-tui 1.1.4 → 1.1.5

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.
@@ -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-DWZrl3oh.js";
3
+ import { createTerminalGraphicPngSequence, getTerminalGraphicsOutput, getTerminalGraphicsOutputVersion, stableTerminalGraphicNumericId, subscribeTerminalGraphicsOutput } from "./terminal-graphics-2FTRg9x3.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,139 @@ 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 extractSvgCssVariables(svg) {
253
+ const map = new Map();
254
+ const styleMatch = /<style[^>]*>([\s\S]*?)<\/style>/i.exec(svg);
255
+ if (!styleMatch?.[1]) return map;
256
+ const declaration = /(--[A-Za-z0-9_-]+)\s*:\s*([^;{}]+);/g;
257
+ let match;
258
+ while ((match = declaration.exec(styleMatch[1])) !== null) map.set(match[1], match[2].trim());
259
+ return map;
260
+ }
261
+ function stripSvgStyleBlocks(svg) {
262
+ return svg.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
263
+ }
264
+ function findMatchingSvgParen(input, openIndex) {
265
+ let depth = 0;
266
+ for (let i = openIndex; i < input.length; i++) {
267
+ const ch = input[i];
268
+ if (ch === "(") depth++;
269
+ else if (ch === ")") {
270
+ depth--;
271
+ if (depth === 0) return i;
272
+ }
273
+ }
274
+ return -1;
275
+ }
276
+ function findTopLevelSvgComma(input) {
277
+ let depth = 0;
278
+ for (let i = 0; i < input.length; i++) {
279
+ const ch = input[i];
280
+ if (ch === "(") depth++;
281
+ else if (ch === ")") depth--;
282
+ else if (ch === "," && depth === 0) return i;
283
+ }
284
+ return -1;
285
+ }
286
+ function normalizeHexColor(value) {
287
+ const match = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(value.trim());
288
+ if (!match) return null;
289
+ let hex = match[1];
290
+ if (hex.length === 3) hex = hex.split("").map((ch) => ch + ch).join("");
291
+ return `#${hex.toLowerCase()}`;
292
+ }
293
+ function mixSrgbColors(aHex, bHex, percent) {
294
+ const a = parseHexRgb(aHex);
295
+ const b = parseHexRgb(bHex);
296
+ const p = Math.max(0, Math.min(100, percent)) / 100;
297
+ const mix = (x, y) => Math.round(x * p + y * (1 - p));
298
+ const toHex = (value) => value.toString(16).padStart(2, "0");
299
+ return `#${toHex(mix(a[0], b[0]))}${toHex(mix(a[1], b[1]))}${toHex(mix(a[2], b[2]))}`;
300
+ }
301
+ function parseHexRgb(hex) {
302
+ return [
303
+ parseInt(hex.slice(1, 3), 16),
304
+ parseInt(hex.slice(3, 5), 16),
305
+ parseInt(hex.slice(5, 7), 16)
306
+ ];
307
+ }
308
+ function resolveSvgColorExpr(expr, root, variables) {
309
+ const value = expr.trim();
310
+ if (value.startsWith("var(")) {
311
+ const close = findMatchingSvgParen(value, 3);
312
+ if (close < 0) return value;
313
+ const inner = value.slice(4, close).trim();
314
+ const comma = findTopLevelSvgComma(inner);
315
+ const name = (comma < 0 ? inner : inner.slice(0, comma)).trim();
316
+ const fallback = comma < 0 ? "" : inner.slice(comma + 1).trim();
317
+ const direct = name === "--fg" ? root.fg : name === "--bg" ? root.bg : variables.get(name);
318
+ if (direct != null) return resolveSvgColorExpr(direct, root, variables);
319
+ if (fallback) return resolveSvgColorExpr(fallback, root, variables);
320
+ return value;
321
+ }
322
+ if (value.startsWith("color-mix(")) {
323
+ const close = findMatchingSvgParen(value, 9);
324
+ if (close < 0) return value;
325
+ const inner = value.slice(10, close).trim();
326
+ const afterMethod = inner.replace(/^in\s+srgb\s*,\s*/i, "");
327
+ const comma = findTopLevelSvgComma(afterMethod);
328
+ if (comma < 0) return value;
329
+ const left = afterMethod.slice(0, comma).trim();
330
+ const right = afterMethod.slice(comma + 1).trim();
331
+ const leftMatch = /^([^%\s]+)\s+([\d.]+)%$/.exec(left) ?? /^([\d.]+)%\s+([^%\s]+)$/.exec(left);
332
+ if (!leftMatch) return value;
333
+ const a = normalizeHexColor(resolveSvgColorExpr(leftMatch[1], root, variables));
334
+ const b = normalizeHexColor(resolveSvgColorExpr(right, root, variables));
335
+ if (!a || !b) return value;
336
+ return mixSrgbColors(a, b, Number(leftMatch[2]));
337
+ }
338
+ return normalizeHexColor(value) ?? value;
339
+ }
326
340
  function loadBuiltinMermaidRasterizer() {
327
341
  if (!builtinRasterizerLoad) builtinRasterizerLoad = (async () => {
328
342
  try {
@@ -339,7 +353,8 @@ function loadBuiltinMermaidRasterizer() {
339
353
  });
340
354
  const svgText = String(svg ?? "");
341
355
  if (!svgText.includes("<svg")) return null;
342
- const rendered = new Resvg(svgText, { background: "rgba(0,0,0,0)" }).render();
356
+ const resolvedSvg = resolveMermaidSvgForResvg(svgText, options.bg, options.fg);
357
+ const rendered = new Resvg(resolvedSvg, { background: "rgba(0,0,0,0)" }).render();
343
358
  const naturalWidth = positiveInt(rendered?.width);
344
359
  const naturalHeight = positiveInt(rendered?.height);
345
360
  const png = typeof rendered?.asPng === "function" ? rendered.asPng() : void 0;
@@ -514,6 +529,18 @@ const tMermaidImageProps = {
514
529
  type: Number,
515
530
  default: void 0
516
531
  },
532
+ textRenderer: {
533
+ type: Function,
534
+ default: void 0
535
+ },
536
+ shouldRenderSource: {
537
+ type: Function,
538
+ default: void 0
539
+ },
540
+ textOptions: {
541
+ type: Object,
542
+ default: void 0
543
+ },
517
544
  box: {
518
545
  type: Boolean,
519
546
  default: true
@@ -557,6 +584,26 @@ const tMermaidImageProps = {
557
584
  ascii: {
558
585
  type: Boolean,
559
586
  default: false
587
+ },
588
+ zoomOnWheel: {
589
+ type: Boolean,
590
+ default: true
591
+ },
592
+ zoomModifier: {
593
+ type: String,
594
+ default: "metaCtrl"
595
+ },
596
+ minZoom: {
597
+ type: Number,
598
+ default: 1
599
+ },
600
+ maxZoom: {
601
+ type: Number,
602
+ default: 6
603
+ },
604
+ zoomSensitivity: {
605
+ type: Number,
606
+ default: .002
560
607
  }
561
608
  };
562
609
  const TMermaidImage = defineComponent({
@@ -571,8 +618,16 @@ const TMermaidImage = defineComponent({
571
618
  const { visible, rootProps } = useVisibility();
572
619
  const parentEventZ = inject(EventZIndexContextKey, computed(() => 0));
573
620
  const imageCells = shallowRef(null);
621
+ const textSnapshot = shallowRef(null);
622
+ const zoomScale = shallowRef(1);
623
+ const panX = shallowRef(0);
624
+ const panY = shallowRef(0);
574
625
  const documentVersion = shallowRef(0);
575
626
  const copied = shallowRef(false);
627
+ let dragState = null;
628
+ let dragMoved = false;
629
+ let lastDragAt = 0;
630
+ const DRAG_CLICK_SUPPRESS_MS = 250;
576
631
  let builtOnce = false;
577
632
  let renderVersion = 0;
578
633
  let alive = true;
@@ -581,6 +636,7 @@ const TMermaidImage = defineComponent({
581
636
  let copiedTimer = null;
582
637
  let copyRequestVersion = 0;
583
638
  let activeGraphicId = null;
639
+ let activeImageSignature = "";
584
640
  const source = computed(() => props.code ?? props.content ?? "");
585
641
  const waitingForStreamingSourceToFinish = computed(() => props.streaming && !props.final);
586
642
  const graphicsOutputVersion = shallowRef(getTerminalGraphicsOutputVersion(terminal));
@@ -635,6 +691,73 @@ const TMermaidImage = defineComponent({
635
691
  graphicsOutput.value?.clear?.(id);
636
692
  } catch {}
637
693
  }
694
+ function clampZoom(zoom) {
695
+ const min = Number.isFinite(Number(props.minZoom)) ? Math.max(.05, Number(props.minZoom)) : 1;
696
+ const max = Number.isFinite(Number(props.maxZoom)) ? Math.max(min, Number(props.maxZoom)) : Math.max(min, 6);
697
+ return Math.min(max, Math.max(min, zoom));
698
+ }
699
+ function zoomSensitivity() {
700
+ const value = Number(props.zoomSensitivity);
701
+ return Number.isFinite(value) && value > 0 ? value : .002;
702
+ }
703
+ /**
704
+ * Wheel delta -> multiplicative zoom factor. Discrete notches (CLI wheel
705
+ * reporting and browser line-mode deltas) map to one step each; trackpad
706
+ * pixel deltas use a continuous exponential so small movements stay smooth.
707
+ */
708
+ function wheelZoomFactor(deltaY, deltaMode) {
709
+ const sign = deltaY > 0 ? -1 : 1;
710
+ const sensitivity = zoomSensitivity();
711
+ const abs = Math.abs(deltaY);
712
+ const mode = deltaMode ?? 0;
713
+ const isNotch = mode === 1 || abs > 0 && abs <= 3 && Number.isInteger(deltaY);
714
+ const magnitude = isNotch ? 100 : abs;
715
+ return Math.exp(sign * magnitude * sensitivity);
716
+ }
717
+ function resetZoomState() {
718
+ if (zoomScale.value === 1 && panX.value === 0 && panY.value === 0) return;
719
+ zoomScale.value = 1;
720
+ panX.value = 0;
721
+ panY.value = 0;
722
+ dragState = null;
723
+ }
724
+ function hasZoomModifier(event) {
725
+ const modifier = props.zoomModifier ?? "metaCtrl";
726
+ if (modifier === "none") return true;
727
+ if (modifier === "meta") return event.metaKey === true;
728
+ if (modifier === "ctrl") return event.ctrlKey === true;
729
+ return event.ctrlKey === true || event.metaKey === true;
730
+ }
731
+ /**
732
+ * Clamp the pan so the zoomed image always covers the container area
733
+ * (no empty gaps, no over-scroll beyond the image edges).
734
+ */
735
+ function clampPan() {
736
+ const inner = innerRectFrom(fullRect.value);
737
+ const fit = displayFit.value;
738
+ if (!fit || inner.w <= 0 || inner.h <= 0) return;
739
+ const zoom = clampZoom(zoomScale.value);
740
+ const fullW = fit.displayW * zoom;
741
+ const fullH = fit.displayH * zoom;
742
+ const fitOriginX = inner.x + (inner.w > fit.displayW ? Math.floor((inner.w - fit.displayW) / 2) : 0);
743
+ let nextX = panX.value;
744
+ let nextY = panY.value;
745
+ if (fullW <= inner.w) nextX = 0;
746
+ else nextX = Math.min(inner.x - fitOriginX, Math.max(inner.x + inner.w - fullW - fitOriginX, nextX));
747
+ if (fullH <= inner.h) nextY = 0;
748
+ else nextY = Math.min(0, Math.max(inner.h - fullH, nextY));
749
+ if (nextX !== panX.value) panX.value = nextX;
750
+ if (nextY !== panY.value) panY.value = nextY;
751
+ }
752
+ /** Set the current image, resetting wheel zoom whenever the PNG changes. */
753
+ function setImageCells(cells) {
754
+ const signature = cells ? cells.base64 : "";
755
+ if (signature !== activeImageSignature) {
756
+ activeImageSignature = signature;
757
+ resetZoomState();
758
+ }
759
+ imageCells.value = cells;
760
+ }
638
761
  const hasBox = computed(() => props.box !== false);
639
762
  const normalizedWidth = computed(() => {
640
763
  const width = Math.floor(Number(props.w));
@@ -643,6 +766,11 @@ const TMermaidImage = defineComponent({
643
766
  const reservesBoxRows = computed(() => hasBox.value && normalizedWidth.value >= 2);
644
767
  const boxChars = computed(() => props.ascii ? ASCII_MERMAID_IMAGE_BOX_CHARS : UNICODE_MERMAID_IMAGE_BOX_CHARS);
645
768
  const sourceLines = computed(() => splitRenderedOutput(source.value));
769
+ const displayLines = computed(() => {
770
+ const snapshot = textSnapshot.value;
771
+ if (snapshot && snapshot.source === source.value && hasVisibleTextOutput(snapshot.lines)) return snapshot.lines;
772
+ return sourceLines.value;
773
+ });
646
774
  const currentStyle = computed(() => props.style ?? defaultStyle.value);
647
775
  const innerWidth = computed(() => {
648
776
  const w = normalizedWidth.value;
@@ -664,7 +792,7 @@ const TMermaidImage = defineComponent({
664
792
  });
665
793
  const contentHeight = computed(() => {
666
794
  if (!autoHeight.value) return fixedInnerHeight.value;
667
- return displayFit.value?.displayH ?? Math.max(1, sourceLines.value.length);
795
+ return displayFit.value?.displayH ?? Math.max(1, displayLines.value.length);
668
796
  });
669
797
  const fullHeight = computed(() => {
670
798
  if (props.h != null) {
@@ -717,6 +845,53 @@ const TMermaidImage = defineComponent({
717
845
  padding: props.padding
718
846
  });
719
847
  }
848
+ function hasVisibleTextOutput(lines) {
849
+ return lines.some((line) => line.trim().length > 0);
850
+ }
851
+ function resolveTextOptions() {
852
+ const base = props.textOptions ?? {};
853
+ return {
854
+ ...base,
855
+ useAscii: props.ascii,
856
+ colorMode: "none"
857
+ };
858
+ }
859
+ function textRenderSignature(code) {
860
+ return [
861
+ code,
862
+ props.ascii ? "a" : "",
863
+ JSON.stringify(props.textOptions ?? null),
864
+ props.textRenderer != null ? "r" : "",
865
+ props.shouldRenderSource != null ? "g" : ""
866
+ ].join("");
867
+ }
868
+ async function renderTextWithTimeout(renderer, code, options) {
869
+ const timeoutMs = normalizeNonNegativeInt(props.renderTimeoutMs, 0);
870
+ if (timeoutMs <= 0) return await renderer(code, options);
871
+ let timer = null;
872
+ try {
873
+ return await Promise.race([Promise.resolve().then(() => renderer(code, options)), new Promise((_resolve, reject) => {
874
+ timer = setTimeout(() => reject(new Error("Mermaid text render timeout")), timeoutMs);
875
+ })]);
876
+ } finally {
877
+ if (timer != null) clearTimeout(timer);
878
+ }
879
+ }
880
+ async function runCustomRasterizer(code, options) {
881
+ const renderer = props.renderer;
882
+ if (!renderer) return null;
883
+ const timeoutMs = normalizeNonNegativeInt(props.renderTimeoutMs, 0);
884
+ const run = () => renderer(code, options);
885
+ if (timeoutMs <= 0) return await run() ?? null;
886
+ let timer = null;
887
+ try {
888
+ return await Promise.race([run(), new Promise((resolve) => {
889
+ timer = setTimeout(() => resolve(null), timeoutMs);
890
+ })]) ?? null;
891
+ } finally {
892
+ if (timer != null) clearTimeout(timer);
893
+ }
894
+ }
720
895
  function shouldSkipRenderForSize(code) {
721
896
  const maxChars = normalizeNonNegativeInt(props.maxRenderSourceChars, DEFAULT_MERMAID_IMAGE_MAX_RENDER_SOURCE_CHARS);
722
897
  if (maxChars > 0 && code.length > maxChars) return true;
@@ -736,48 +911,107 @@ const TMermaidImage = defineComponent({
736
911
  if (timer != null) clearTimeout(timer);
737
912
  }
738
913
  }
739
- async function renderImage(version) {
914
+ async function renderTextFallback(version) {
915
+ const code = source.value;
916
+ const renderer = props.textRenderer;
917
+ if (!renderer) return;
918
+ const guard = props.shouldRenderSource;
919
+ if (guard) {
920
+ let eligible = false;
921
+ try {
922
+ eligible = guard(code, {
923
+ final: props.final,
924
+ streaming: props.streaming
925
+ });
926
+ } catch {
927
+ eligible = false;
928
+ }
929
+ if (!eligible) return;
930
+ }
931
+ try {
932
+ const rendered = await renderTextWithTimeout(renderer, code, resolveTextOptions());
933
+ if (!alive || version !== renderVersion) return;
934
+ const lines = splitRenderedOutput(rendered);
935
+ if (!hasVisibleTextOutput(lines)) return;
936
+ textSnapshot.value = markRaw({
937
+ source: code,
938
+ signature: textRenderSignature(code),
939
+ lines: markRaw(lines)
940
+ });
941
+ bump();
942
+ } catch {}
943
+ }
944
+ /**
945
+ * Adaptive presentation state machine:
946
+ *
947
+ * raw source
948
+ * -> image (terminal supports Kitty/iTerm2 graphics and the PNG
949
+ * rasterizer succeeds), else
950
+ * -> ANSI text (a textRenderer is available and the source passes the
951
+ * eligibility guard), else
952
+ * -> raw source
953
+ *
954
+ * While any pipeline is pending the raw source stays visible (source-first).
955
+ */
956
+ async function renderPresentation(version) {
740
957
  const code = source.value;
741
958
  if (!code.trim()) {
742
959
  if (!alive || version !== renderVersion) return;
743
- imageCells.value = null;
960
+ setImageCells(null);
961
+ textSnapshot.value = null;
744
962
  bump();
745
963
  return;
746
964
  }
747
965
  if (waitingForStreamingSourceToFinish.value || shouldSkipRenderForSize(code)) {
748
966
  if (!alive || version !== renderVersion) return;
749
- imageCells.value = null;
967
+ setImageCells(null);
968
+ textSnapshot.value = null;
750
969
  bump();
751
970
  return;
752
971
  }
753
- imageCells.value = null;
754
- bump();
755
972
  const protocol = graphicsProtocol.value;
756
- if (!protocol) return;
757
973
  const options = resolveImageOptions();
758
- const cached = getCachedMermaidImage(code, options);
974
+ const cached = protocol && !props.renderer ? getCachedMermaidImage(code, options) : null;
759
975
  if (cached) {
760
976
  if (!alive || version !== renderVersion) return;
761
- imageCells.value = cached;
977
+ setImageCells(cached);
978
+ textSnapshot.value = null;
762
979
  bump();
763
980
  return;
764
981
  }
765
- if (!isMermaidImageRendererReady()) {
766
- loadMermaidImageRenderer().then((ready) => {
767
- if (!alive || version !== renderVersion || !ready) return;
768
- renderImage(version);
769
- });
770
- return;
982
+ const currentSignature = textRenderSignature(code);
983
+ const keepText = textSnapshot.value != null && textSnapshot.value.source === code && textSnapshot.value.signature === currentSignature;
984
+ if (!keepText) {
985
+ setImageCells(null);
986
+ textSnapshot.value = null;
987
+ bump();
771
988
  }
772
- const result = await rasterizeWithTimeout(code, options);
773
- if (!alive || version !== renderVersion) return;
774
- imageCells.value = result;
775
- bump();
989
+ if (protocol) {
990
+ let result = null;
991
+ if (props.renderer) result = await runCustomRasterizer(code, options);
992
+ else if (isMermaidImageRendererReady()) result = await rasterizeWithTimeout(code, options);
993
+ else {
994
+ loadMermaidImageRenderer().then((ready) => {
995
+ if (!alive || version !== renderVersion || !ready) return;
996
+ renderPresentation(version);
997
+ });
998
+ return;
999
+ }
1000
+ if (!alive || version !== renderVersion) return;
1001
+ if (result) {
1002
+ setImageCells(result);
1003
+ textSnapshot.value = null;
1004
+ bump();
1005
+ return;
1006
+ }
1007
+ }
1008
+ if (keepText) return;
1009
+ await renderTextFallback(version);
776
1010
  }
777
1011
  function scheduleRender() {
778
1012
  const version = ++renderVersion;
779
1013
  resetCopyFeedback(false);
780
- imageCells.value = null;
1014
+ setImageCells(null);
781
1015
  if (waitingForStreamingSourceToFinish.value) {
782
1016
  builtOnce = true;
783
1017
  scheduler.cancelFrameTask?.(frameTaskId);
@@ -786,7 +1020,7 @@ const TMermaidImage = defineComponent({
786
1020
  }
787
1021
  if (!builtOnce || !props.streaming) {
788
1022
  builtOnce = true;
789
- renderImage(version);
1023
+ renderPresentation(version);
790
1024
  return;
791
1025
  }
792
1026
  const accepted = scheduler.queueFrameTask({
@@ -797,10 +1031,10 @@ const TMermaidImage = defineComponent({
797
1031
  run: () => {
798
1032
  if (!alive) return;
799
1033
  if (version !== renderVersion) return;
800
- renderImage(version);
1034
+ renderPresentation(version);
801
1035
  }
802
1036
  });
803
- if (accepted === false) renderImage(version);
1037
+ if (accepted === false) renderPresentation(version);
804
1038
  }
805
1039
  watch([
806
1040
  source,
@@ -817,7 +1051,11 @@ const TMermaidImage = defineComponent({
817
1051
  () => props.final,
818
1052
  () => props.maxRenderSourceChars,
819
1053
  () => props.maxRenderSourceLines,
820
- graphicsOutputVersion
1054
+ () => props.textRenderer,
1055
+ () => props.shouldRenderSource,
1056
+ () => props.textOptions,
1057
+ () => props.ascii,
1058
+ () => props.zoomModifier
821
1059
  ], () => {
822
1060
  scheduleRender();
823
1061
  }, {
@@ -826,7 +1064,13 @@ const TMermaidImage = defineComponent({
826
1064
  });
827
1065
  const unsubscribeMermaidImage = subscribeMermaidImage(() => {
828
1066
  if (!alive) return;
829
- scheduleRender();
1067
+ if (!graphicsProtocol.value || props.renderer) return;
1068
+ const cached = getCachedMermaidImage(source.value, resolveImageOptions());
1069
+ if (!cached) return;
1070
+ if (!alive) return;
1071
+ setImageCells(cached);
1072
+ textSnapshot.value = null;
1073
+ bump();
830
1074
  });
831
1075
  const unsubscribeGraphicsOutput = subscribeTerminalGraphicsOutput(terminal, () => {
832
1076
  if (!alive) return;
@@ -909,7 +1153,7 @@ const TMermaidImage = defineComponent({
909
1153
  }
910
1154
  function contentLine(rowIndex, width, pad) {
911
1155
  const showImage = imageCells.value != null && displayFit.value != null && graphicsProtocol.value != null;
912
- const src = showImage ? spaces(width) : sourceLines.value[rowIndex] ?? "";
1156
+ const src = showImage ? spaces(width) : displayLines.value[rowIndex] ?? "";
913
1157
  const clipped = sliceCells(src, width);
914
1158
  return pad ? padCells(clipped, width) : clipped;
915
1159
  }
@@ -937,12 +1181,28 @@ const TMermaidImage = defineComponent({
937
1181
  queueClearGraphic();
938
1182
  return;
939
1183
  }
940
- const offsetX = inner.w > fit.displayW ? Math.floor((inner.w - fit.displayW) / 2) : 0;
1184
+ const zoom = clampZoom(zoomScale.value);
1185
+ const fitW = fit.displayW;
1186
+ const fitH = fit.displayH;
1187
+ const fitOriginX = inner.x + (inner.w > fitW ? Math.floor((inner.w - fitW) / 2) : 0);
1188
+ const fullRect$1 = {
1189
+ x: fitOriginX + panX.value,
1190
+ y: inner.y + panY.value,
1191
+ w: fitW * zoom,
1192
+ h: fitH * zoom
1193
+ };
1194
+ const visible$1 = intersectRect(fullRect$1, inner);
1195
+ if (!visible$1 || visible$1.w < 1 || visible$1.h < 1) {
1196
+ queueClearGraphic();
1197
+ return;
1198
+ }
1199
+ const rectX = Math.max(inner.x, Math.round(visible$1.x));
1200
+ const rectY = Math.max(inner.y, Math.round(visible$1.y));
941
1201
  const rect = {
942
- x: inner.x + offsetX,
943
- y: inner.y,
944
- w: fit.displayW,
945
- h: fit.displayH
1202
+ x: rectX,
1203
+ y: rectY,
1204
+ w: Math.max(1, Math.min(inner.x + inner.w, rectX + Math.round(visible$1.w)) - rectX),
1205
+ h: Math.max(1, Math.min(inner.y + inner.h, rectY + Math.round(visible$1.h)) - rectY)
946
1206
  };
947
1207
  const imageId = stableTerminalGraphicNumericId(`mermaid-image:${uid}:${renderVersion}:img`);
948
1208
  const placementId = stableTerminalGraphicNumericId(`mermaid-image:${uid}:${renderVersion}:plc`);
@@ -955,10 +1215,13 @@ const TMermaidImage = defineComponent({
955
1215
  rows: cells.heightCells,
956
1216
  sourceWidth: cells.naturalWidth,
957
1217
  sourceHeight: cells.naturalHeight,
958
- placementColumns: fit.displayW,
959
- placementRows: fit.displayH,
1218
+ placementColumns: rect.w,
1219
+ placementRows: rect.h,
1220
+ transmissionColumns: fitW,
1221
+ transmissionRows: fitH,
1222
+ cropTransmission: false,
960
1223
  rect,
961
- fullRect: rect,
1224
+ fullRect: fullRect$1,
962
1225
  zIndex: -1,
963
1226
  fallback: source.value
964
1227
  });
@@ -982,13 +1245,14 @@ const TMermaidImage = defineComponent({
982
1245
  id,
983
1246
  x: rect.x,
984
1247
  y: rect.y,
985
- w: fit.displayW,
986
- h: fit.displayH,
1248
+ w: rect.w,
1249
+ h: rect.h,
987
1250
  protocol,
988
1251
  sequence: built.sequence,
989
1252
  resizeSequence: built.resizeSequence,
990
1253
  clearSequence: built.clearSequence,
991
- fallbackText: source.value
1254
+ fallbackText: source.value,
1255
+ placementMoveWithoutClear: protocol === "kitty"
992
1256
  });
993
1257
  if (accepted) activeGraphicId = id;
994
1258
  else queueClearGraphic();
@@ -1023,7 +1287,7 @@ const TMermaidImage = defineComponent({
1023
1287
  };
1024
1288
  });
1025
1289
  const contentHitRect = computed(() => {
1026
- if (!visible.value || !props.copyOnClick) return {
1290
+ if (!visible.value) return {
1027
1291
  x: 0,
1028
1292
  y: 0,
1029
1293
  w: 0,
@@ -1095,6 +1359,7 @@ const TMermaidImage = defineComponent({
1095
1359
  function onCopyClick(event) {
1096
1360
  event.preventDefault();
1097
1361
  event.stopPropagation();
1362
+ if (Date.now() - lastDragAt < DRAG_CLICK_SUPPRESS_MS) return;
1098
1363
  copySource();
1099
1364
  }
1100
1365
  function onCopyKeydown(event) {
@@ -1103,6 +1368,78 @@ const TMermaidImage = defineComponent({
1103
1368
  event.stopPropagation();
1104
1369
  copySource();
1105
1370
  }
1371
+ const zoomEnabled = computed(() => props.zoomOnWheel !== false && imageCells.value != null && displayFit.value != null && graphicsProtocol.value === "kitty" && contentHitRect.value.w > 0 && contentHitRect.value.h > 0);
1372
+ 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);
1373
+ function onWheel(event) {
1374
+ if (!zoomEnabled.value) return;
1375
+ if (!hasZoomModifier(event)) return;
1376
+ event.preventDefault();
1377
+ event.stopPropagation();
1378
+ const deltaY = event.deltaY ?? 0;
1379
+ if (!Number.isFinite(deltaY) || deltaY === 0) return;
1380
+ const inner = innerRectFrom(fullRect.value);
1381
+ const fit = displayFit.value;
1382
+ if (!fit || inner.w <= 0 || inner.h <= 0) return;
1383
+ const currentZoom = clampZoom(zoomScale.value);
1384
+ const fitW = fit.displayW;
1385
+ const fitH = fit.displayH;
1386
+ const fitOriginX = inner.x + (inner.w > fitW ? Math.floor((inner.w - fitW) / 2) : 0);
1387
+ const fullX = fitOriginX + panX.value;
1388
+ const fullY = inner.y + panY.value;
1389
+ const mouseX = event.cellX;
1390
+ const mouseY = event.cellY;
1391
+ const fx = currentZoom > 0 ? (mouseX - fullX) / (fitW * currentZoom) : .5;
1392
+ const fy = currentZoom > 0 ? (mouseY - fullY) / (fitH * currentZoom) : .5;
1393
+ const nextZoom = clampZoom(currentZoom * wheelZoomFactor(deltaY, event.deltaMode));
1394
+ if (nextZoom === currentZoom) return;
1395
+ const nextW = fitW * nextZoom;
1396
+ const nextH = fitH * nextZoom;
1397
+ let nextX = mouseX - fx * nextW;
1398
+ let nextY = mouseY - fy * nextH;
1399
+ if (nextW <= inner.w) nextX = inner.x + (inner.w - nextW) / 2;
1400
+ else nextX = Math.min(inner.x, Math.max(inner.x + inner.w - nextW, nextX));
1401
+ if (nextH <= inner.h) nextY = inner.y + (inner.h - nextH) / 2;
1402
+ else nextY = Math.min(inner.y, Math.max(inner.y + inner.h - nextH, nextY));
1403
+ zoomScale.value = nextZoom;
1404
+ panX.value = nextX - fitOriginX;
1405
+ panY.value = nextY - inner.y;
1406
+ clampPan();
1407
+ bump();
1408
+ }
1409
+ function onPointerDown(event) {
1410
+ if (!dragEnabled.value) return;
1411
+ event.preventDefault();
1412
+ event.stopPropagation();
1413
+ dragState = {
1414
+ startX: event.cellX,
1415
+ startY: event.cellY,
1416
+ panStartX: panX.value,
1417
+ panStartY: panY.value
1418
+ };
1419
+ dragMoved = false;
1420
+ }
1421
+ function onPointerMove(event) {
1422
+ const drag = dragState;
1423
+ if (!drag) return;
1424
+ event.preventDefault();
1425
+ event.stopPropagation();
1426
+ const dx = event.cellX - drag.startX;
1427
+ const dy = event.cellY - drag.startY;
1428
+ if (!dragMoved && Math.abs(dx) + Math.abs(dy) >= 1) dragMoved = true;
1429
+ if (!dragMoved) return;
1430
+ panX.value = drag.panStartX + dx;
1431
+ panY.value = drag.panStartY + dy;
1432
+ clampPan();
1433
+ bump();
1434
+ }
1435
+ function onPointerUp(event) {
1436
+ const drag = dragState;
1437
+ if (!drag) return;
1438
+ event.preventDefault();
1439
+ event.stopPropagation();
1440
+ dragState = null;
1441
+ if (dragMoved) lastDragAt = Date.now();
1442
+ }
1106
1443
  const copyNodeActive = computed(() => visible.value && props.copyButton && copyHitRect.value.w > 0 && copyHitRect.value.h > 0);
1107
1444
  useTerminalNode(() => ({
1108
1445
  rect: copyHitRect.value,
@@ -1115,14 +1452,20 @@ const TMermaidImage = defineComponent({
1115
1452
  keydown: onCopyKeydown
1116
1453
  } : {}
1117
1454
  }));
1118
- const contentNodeActive = computed(() => visible.value && props.copyOnClick && contentHitRect.value.w > 0 && contentHitRect.value.h > 0);
1455
+ const contentNodeActive = computed(() => visible.value && (props.copyOnClick || zoomEnabled.value || dragEnabled.value) && contentHitRect.value.w > 0 && contentHitRect.value.h > 0);
1119
1456
  useTerminalNode(() => ({
1120
1457
  rect: contentHitRect.value,
1121
1458
  zIndex: (parentEventZ.value ?? 0) + props.zIndex,
1122
1459
  visible: contentNodeActive.value,
1123
1460
  focusable: false,
1124
1461
  selectable: false,
1125
- handlers: contentNodeActive.value ? { click: onCopyClick } : {}
1462
+ handlers: contentNodeActive.value ? {
1463
+ click: props.copyOnClick ? onCopyClick : void 0,
1464
+ wheel: zoomEnabled.value ? onWheel : void 0,
1465
+ pointerdown: dragEnabled.value ? onPointerDown : void 0,
1466
+ pointermove: dragEnabled.value ? onPointerMove : void 0,
1467
+ pointerup: dragEnabled.value ? onPointerUp : void 0
1468
+ } : {}
1126
1469
  }));
1127
1470
  useRenderNode(() => ({
1128
1471
  zIndex: props.zIndex,
@@ -1138,6 +1481,11 @@ const TMermaidImage = defineComponent({
1138
1481
  fullRect.value,
1139
1482
  imageCells.value,
1140
1483
  displayFit.value,
1484
+ zoomScale.value,
1485
+ panX.value,
1486
+ panY.value,
1487
+ textSnapshot.value,
1488
+ displayLines.value,
1141
1489
  sourceLines.value,
1142
1490
  currentStyle.value,
1143
1491
  props.clear,
@@ -1202,4 +1550,148 @@ const TMermaidImage = defineComponent({
1202
1550
  });
1203
1551
 
1204
1552
  //#endregion
1205
- export { TBeautifulMermaid, TBeautifulMermaidText, TMermaid, TMermaidImage, TMermaidText$1 as TMermaidText, beautifulMermaidRenderer, clearMermaidImageCache, createBeautifulMermaidRenderer, getCachedMermaidImage, getMermaidImage, isMermaidImageRendererReady, loadMermaidImageRenderer, normalizeMermaidImageOptions, resolveMermaidImageColor, setMermaidImageRasterizer, subscribeMermaidImage };
1553
+ //#region src/vue/mermaid/beautiful-mermaid.ts
1554
+ 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.";
1555
+ const MISSING_BEAUTIFUL_MERMAID_ERROR_CODE = "VUE_TUI_MISSING_BEAUTIFUL_MERMAID";
1556
+ const INVALID_BEAUTIFUL_MERMAID_EXPORT_ERROR_CODE = "VUE_TUI_INVALID_BEAUTIFUL_MERMAID_EXPORT";
1557
+ let cachedBeautifulMermaid = null;
1558
+ function functionValue(value) {
1559
+ return typeof value === "function" ? value : null;
1560
+ }
1561
+ function functionProp(target, key) {
1562
+ if (!target || typeof target !== "object") return null;
1563
+ if (!(key in target)) return null;
1564
+ const value = target[key];
1565
+ return typeof value === "function" ? value : null;
1566
+ }
1567
+ function errorMessage(error) {
1568
+ return error instanceof Error ? error.message : String(error);
1569
+ }
1570
+ function codedError(message, code) {
1571
+ const error = new Error(message);
1572
+ error.code = code;
1573
+ return error;
1574
+ }
1575
+ function missingBeautifulMermaidError(error) {
1576
+ if (error && typeof error === "object" && errorCode(error) === MISSING_BEAUTIFUL_MERMAID_ERROR_CODE) return markMermaidRenderErrorFatal(error);
1577
+ const detail = errorMessage(error);
1578
+ return markMermaidRenderErrorFatal(codedError(`${BEAUTIFUL_MERMAID_INSTALL_HINT} (${detail})`, MISSING_BEAUTIFUL_MERMAID_ERROR_CODE));
1579
+ }
1580
+ function errorCode(error) {
1581
+ if (!error || typeof error !== "object") return "";
1582
+ const value = error.code;
1583
+ return typeof value === "string" ? value : "";
1584
+ }
1585
+ function errorCause(error) {
1586
+ if (!error || typeof error !== "object") return void 0;
1587
+ return error.cause;
1588
+ }
1589
+ function isMissingBeautifulMermaid(error) {
1590
+ return isMissingBeautifulMermaidWithSeen(error, new WeakSet());
1591
+ }
1592
+ function isMissingBeautifulMermaidWithSeen(error, seen) {
1593
+ if (error && typeof error === "object") {
1594
+ if (seen.has(error)) return false;
1595
+ seen.add(error);
1596
+ }
1597
+ const message = errorMessage(error);
1598
+ const code = errorCode(error);
1599
+ if (code === MISSING_BEAUTIFUL_MERMAID_ERROR_CODE) return true;
1600
+ 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);
1601
+ if (mentionsBeautifulMermaid && (code === "" || code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND" || code === "UNRESOLVED_IMPORT")) return true;
1602
+ const cause = errorCause(error);
1603
+ return cause !== void 0 && isMissingBeautifulMermaidWithSeen(cause, seen);
1604
+ }
1605
+ function resolveBeautifulMermaidRenderer(mod) {
1606
+ const renderer = functionProp(mod, "renderMermaidASCII") ?? functionProp(mod, "renderMermaidAscii") ?? functionValue(mod.default) ?? functionProp(mod.default, "renderMermaidASCII") ?? functionProp(mod.default, "renderMermaidAscii");
1607
+ if (!renderer) throw markMermaidRenderErrorFatal(codedError("beautiful-mermaid is installed but does not export renderMermaidASCII.", INVALID_BEAUTIFUL_MERMAID_EXPORT_ERROR_CODE));
1608
+ return renderer;
1609
+ }
1610
+ function hasErrorCode(error, code, seen = new WeakSet()) {
1611
+ if (!error || typeof error !== "object") return false;
1612
+ if (seen.has(error)) return false;
1613
+ seen.add(error);
1614
+ if (errorCode(error) === code) return true;
1615
+ const cause = errorCause(error);
1616
+ return cause !== void 0 && hasErrorCode(cause, code, seen);
1617
+ }
1618
+ function isBeautifulMermaidExportError(error) {
1619
+ return hasErrorCode(error, INVALID_BEAUTIFUL_MERMAID_EXPORT_ERROR_CODE);
1620
+ }
1621
+ const isTransientBeautifulMermaidRenderError = (error) => {
1622
+ if (isMissingBeautifulMermaid(error)) return false;
1623
+ if (isBeautifulMermaidExportError(error)) return false;
1624
+ return true;
1625
+ };
1626
+ async function loadBeautifulMermaid() {
1627
+ if (!cachedBeautifulMermaid) cachedBeautifulMermaid = import("beautiful-mermaid").then((mod) => mod).catch((error) => {
1628
+ cachedBeautifulMermaid = null;
1629
+ if (isMissingBeautifulMermaid(error)) throw missingBeautifulMermaidError(error);
1630
+ throw markMermaidRenderErrorFatal(error);
1631
+ });
1632
+ return cachedBeautifulMermaid;
1633
+ }
1634
+ const beautifulMermaidRenderer = async (code, options) => {
1635
+ try {
1636
+ const mod = await loadBeautifulMermaid();
1637
+ const renderer = resolveBeautifulMermaidRenderer(mod);
1638
+ return await renderer(code, options);
1639
+ } catch (error) {
1640
+ if (isMissingBeautifulMermaid(error)) throw missingBeautifulMermaidError(error);
1641
+ throw error;
1642
+ }
1643
+ };
1644
+ function createBeautifulMermaidRenderer() {
1645
+ return beautifulMermaidRenderer;
1646
+ }
1647
+ const TMermaidText$1 = defineComponent({
1648
+ name: "TMermaidText",
1649
+ inheritAttrs: false,
1650
+ props: tMermaidTextProps,
1651
+ emits: { copy: (_payload) => true },
1652
+ setup(props, { attrs, emit, slots }) {
1653
+ return () => {
1654
+ const renderer = props.renderer ?? beautifulMermaidRenderer;
1655
+ const shouldRenderSource = props.shouldRenderSource ?? isSimpleMermaidFlowchartSource;
1656
+ return h(TMermaidText, {
1657
+ ...attrs,
1658
+ ...props,
1659
+ renderer,
1660
+ shouldRenderSource,
1661
+ isTransientError: props.isTransientError ?? isTransientBeautifulMermaidRenderError,
1662
+ onCopy: (payload) => emit("copy", payload)
1663
+ }, slots);
1664
+ };
1665
+ }
1666
+ });
1667
+ /**
1668
+ * Adaptive default mermaid component: renders the diagram as a terminal image
1669
+ * (Kitty/iTerm2 graphics) when the terminal supports it, otherwise falls back
1670
+ * to the beautiful-mermaid ASCII renderer, otherwise shows the raw source.
1671
+ * While any pipeline is pending the raw source stays visible (source-first);
1672
+ * streaming sources are only rendered once `final` is true.
1673
+ */
1674
+ const TMermaid = defineComponent({
1675
+ name: "TMermaid",
1676
+ inheritAttrs: false,
1677
+ props: tMermaidImageProps,
1678
+ emits: { copy: (_payload) => true },
1679
+ setup(props, { attrs, emit, slots }) {
1680
+ return () => {
1681
+ const { renderer: _legacyTextRenderer,...rest } = props;
1682
+ const textRenderer = props.textRenderer ?? props.renderer ?? beautifulMermaidRenderer;
1683
+ return h(TMermaidImage, {
1684
+ ...attrs,
1685
+ ...rest,
1686
+ textRenderer,
1687
+ shouldRenderSource: props.shouldRenderSource ?? isSimpleMermaidFlowchartSource,
1688
+ onCopy: (payload) => emit("copy", payload)
1689
+ }, slots);
1690
+ };
1691
+ }
1692
+ });
1693
+ const TBeautifulMermaidText = TMermaidText$1;
1694
+ const TBeautifulMermaid = TMermaid;
1695
+
1696
+ //#endregion
1697
+ export { TBeautifulMermaid, TBeautifulMermaidText, TMermaid, TMermaidImage, TMermaidText$1 as TMermaidText, beautifulMermaidRenderer, clearMermaidImageCache, createBeautifulMermaidRenderer, getCachedMermaidImage, getMermaidImage, isMermaidImageRendererReady, loadMermaidImageRenderer, normalizeMermaidImageOptions, resolveMermaidImageColor, resolveMermaidSvgForResvg, setMermaidImageRasterizer, subscribeMermaidImage };