@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.
package/dist/mermaid.cjs CHANGED
@@ -41,6 +41,7 @@ __export(mermaid_exports, {
41
41
  markMermaidRenderErrorFatal: () => markMermaidRenderErrorFatal,
42
42
  normalizeMermaidImageOptions: () => normalizeMermaidImageOptions,
43
43
  resolveMermaidImageColor: () => resolveMermaidImageColor,
44
+ resolveMermaidSvgForResvg: () => resolveMermaidSvgForResvg,
44
45
  setMermaidImageRasterizer: () => setMermaidImageRasterizer,
45
46
  subscribeMermaidImage: () => subscribeMermaidImage
46
47
  });
@@ -1597,127 +1598,10 @@ var tMermaidTextProps = {
1597
1598
  });
1598
1599
 
1599
1600
  // src/vue/mermaid/beautiful-mermaid.ts
1600
- var import_vue9 = require("vue");
1601
- var 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.", MISSING_BEAUTIFUL_MERMAID_ERROR_CODE = "VUE_TUI_MISSING_BEAUTIFUL_MERMAID", INVALID_BEAUTIFUL_MERMAID_EXPORT_ERROR_CODE = "VUE_TUI_INVALID_BEAUTIFUL_MERMAID_EXPORT", cachedBeautifulMermaid = null;
1602
- function functionValue(value) {
1603
- return typeof value == "function" ? value : null;
1604
- }
1605
- function functionProp(target, key) {
1606
- if (!target || typeof target != "object" || !(key in target))
1607
- return null;
1608
- let value = target[key];
1609
- return typeof value == "function" ? value : null;
1610
- }
1611
- function errorMessage(error) {
1612
- return error instanceof Error ? error.message : String(error);
1613
- }
1614
- function codedError(message, code) {
1615
- let error = new Error(message);
1616
- return error.code = code, error;
1617
- }
1618
- function missingBeautifulMermaidError(error) {
1619
- if (error && typeof error == "object" && errorCode(error) === MISSING_BEAUTIFUL_MERMAID_ERROR_CODE)
1620
- return markMermaidRenderErrorFatal(error);
1621
- let detail = errorMessage(error);
1622
- return markMermaidRenderErrorFatal(
1623
- codedError(
1624
- `${BEAUTIFUL_MERMAID_INSTALL_HINT} (${detail})`,
1625
- MISSING_BEAUTIFUL_MERMAID_ERROR_CODE
1626
- )
1627
- );
1628
- }
1629
- function errorCode(error) {
1630
- if (!error || typeof error != "object")
1631
- return "";
1632
- let value = error.code;
1633
- return typeof value == "string" ? value : "";
1634
- }
1635
- function errorCause(error) {
1636
- if (!(!error || typeof error != "object"))
1637
- return error.cause;
1638
- }
1639
- function isMissingBeautifulMermaid(error) {
1640
- return isMissingBeautifulMermaidWithSeen(error, /* @__PURE__ */ new WeakSet());
1641
- }
1642
- function isMissingBeautifulMermaidWithSeen(error, seen) {
1643
- if (error && typeof error == "object") {
1644
- if (seen.has(error))
1645
- return !1;
1646
- seen.add(error);
1647
- }
1648
- let message = errorMessage(error), code = errorCode(error);
1649
- if (code === MISSING_BEAUTIFUL_MERMAID_ERROR_CODE || (/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)) && (code === "" || code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND" || code === "UNRESOLVED_IMPORT"))
1650
- return !0;
1651
- let cause = errorCause(error);
1652
- return cause !== void 0 && isMissingBeautifulMermaidWithSeen(cause, seen);
1653
- }
1654
- function resolveBeautifulMermaidRenderer(mod) {
1655
- let renderer = functionProp(mod, "renderMermaidASCII") ?? functionProp(mod, "renderMermaidAscii") ?? functionValue(mod.default) ?? functionProp(mod.default, "renderMermaidASCII") ?? functionProp(mod.default, "renderMermaidAscii");
1656
- if (!renderer)
1657
- throw markMermaidRenderErrorFatal(
1658
- codedError(
1659
- "beautiful-mermaid is installed but does not export renderMermaidASCII.",
1660
- INVALID_BEAUTIFUL_MERMAID_EXPORT_ERROR_CODE
1661
- )
1662
- );
1663
- return renderer;
1664
- }
1665
- function hasErrorCode(error, code, seen = /* @__PURE__ */ new WeakSet()) {
1666
- if (!error || typeof error != "object" || seen.has(error))
1667
- return !1;
1668
- if (seen.add(error), errorCode(error) === code)
1669
- return !0;
1670
- let cause = errorCause(error);
1671
- return cause !== void 0 && hasErrorCode(cause, code, seen);
1672
- }
1673
- function isBeautifulMermaidExportError(error) {
1674
- return hasErrorCode(error, INVALID_BEAUTIFUL_MERMAID_EXPORT_ERROR_CODE);
1675
- }
1676
- var isTransientBeautifulMermaidRenderError = (error) => !(isMissingBeautifulMermaid(error) || isBeautifulMermaidExportError(error));
1677
- async function loadBeautifulMermaid() {
1678
- return cachedBeautifulMermaid || (cachedBeautifulMermaid = import("beautiful-mermaid").then((mod) => mod).catch((error) => {
1679
- throw cachedBeautifulMermaid = null, isMissingBeautifulMermaid(error) ? missingBeautifulMermaidError(error) : markMermaidRenderErrorFatal(error);
1680
- })), cachedBeautifulMermaid;
1681
- }
1682
- var beautifulMermaidRenderer = async (code, options) => {
1683
- try {
1684
- let mod = await loadBeautifulMermaid();
1685
- return await resolveBeautifulMermaidRenderer(mod)(code, options);
1686
- } catch (error) {
1687
- throw isMissingBeautifulMermaid(error) ? missingBeautifulMermaidError(error) : error;
1688
- }
1689
- };
1690
- function createBeautifulMermaidRenderer() {
1691
- return beautifulMermaidRenderer;
1692
- }
1693
- var TMermaidText2 = (0, import_vue9.defineComponent)({
1694
- name: "TMermaidText",
1695
- inheritAttrs: !1,
1696
- props: tMermaidTextProps,
1697
- emits: {
1698
- copy: (_payload) => !0
1699
- },
1700
- setup(props, { attrs, emit, slots }) {
1701
- return () => {
1702
- let renderer = props.renderer ?? beautifulMermaidRenderer, shouldRenderSource = props.shouldRenderSource ?? isSimpleMermaidFlowchartSource;
1703
- return (0, import_vue9.h)(
1704
- TMermaidText,
1705
- {
1706
- ...attrs,
1707
- ...props,
1708
- renderer,
1709
- shouldRenderSource,
1710
- isTransientError: props.isTransientError ?? isTransientBeautifulMermaidRenderError,
1711
- onCopy: (payload) => emit("copy", payload)
1712
- },
1713
- slots
1714
- );
1715
- };
1716
- }
1717
- }), TMermaid = TMermaidText2, TBeautifulMermaidText = TMermaidText2, TBeautifulMermaid = TMermaidText2;
1601
+ var import_vue10 = require("vue");
1718
1602
 
1719
1603
  // src/vue/components/TMermaidImage.ts
1720
- var import_vue10 = require("vue");
1604
+ var import_vue9 = require("vue");
1721
1605
 
1722
1606
  // src/core/terminal/create-terminal.ts
1723
1607
  var TERMINAL_PLANE_INTERNALS = Symbol.for("@simon_he/vue-tui:v1:terminal-plane-internals"), TERMINAL_ROOT = Symbol.for("@simon_he/vue-tui:v1:terminal-root");
@@ -1928,6 +1812,10 @@ function createTerminalGraphicPngSequence(options) {
1928
1812
  );
1929
1813
  crop && (placement = { ...placement, ...crop });
1930
1814
  }
1815
+ let transmissionPlacement = options.cropTransmission !== !1 ? placement : {
1816
+ columns: options.transmissionColumns ?? options.placementColumns,
1817
+ rows: options.transmissionRows ?? options.placementRows
1818
+ };
1931
1819
  return {
1932
1820
  type: "sequence",
1933
1821
  protocol: "kitty",
@@ -1935,7 +1823,7 @@ function createTerminalGraphicPngSequence(options) {
1935
1823
  imageId: options.imageId,
1936
1824
  placementId: options.placementId,
1937
1825
  zIndex: options.zIndex,
1938
- ...placement
1826
+ ...transmissionPlacement
1939
1827
  }),
1940
1828
  resizeSequence: createKittyPlacementSequence({
1941
1829
  imageId: options.imageId,
@@ -2154,6 +2042,106 @@ function svgRenderFunction(mod) {
2154
2042
  return candidate;
2155
2043
  return null;
2156
2044
  }
2045
+ function resolveMermaidSvgForResvg(svg, fallbackBg, fallbackFg) {
2046
+ let { bg, fg } = extractSvgRootStyleColors(svg, fallbackBg, fallbackFg), variables = extractSvgCssVariables(svg), root = { bg, fg }, resolved = svg, guard = 0;
2047
+ for (; (resolved.includes("var(") || resolved.includes("color-mix(")) && guard++ < 500; ) {
2048
+ let varIndex = resolved.lastIndexOf("var("), mixIndex = resolved.lastIndexOf("color-mix("), at = Math.max(varIndex, mixIndex);
2049
+ if (at < 0)
2050
+ break;
2051
+ let open = resolved.indexOf("(", at), close = findMatchingSvgParen(resolved, open);
2052
+ if (open < 0 || close < 0)
2053
+ break;
2054
+ let expr = resolved.slice(at, close + 1), color = resolveSvgColorExpr(expr, root, variables);
2055
+ resolved = resolved.slice(0, at) + color + resolved.slice(close + 1);
2056
+ }
2057
+ return stripSvgStyleBlocks(resolved);
2058
+ }
2059
+ function extractSvgRootStyleColors(svg, fallbackBg, fallbackFg) {
2060
+ let bg = fallbackBg, fg = fallbackFg, styleMatch = /<svg\b[^>]*\bstyle="([^"]*)"/i.exec(svg);
2061
+ if (styleMatch?.[1]) {
2062
+ let bgMatch = /(?:^|;)\s*--bg\s*:\s*([^;]+)/i.exec(styleMatch[1]), fgMatch = /(?:^|;)\s*--fg\s*:\s*([^;]+)/i.exec(styleMatch[1]);
2063
+ bgMatch?.[1] && (bg = bgMatch[1].trim()), fgMatch?.[1] && (fg = fgMatch[1].trim());
2064
+ }
2065
+ return { bg, fg };
2066
+ }
2067
+ function extractSvgCssVariables(svg) {
2068
+ let map = /* @__PURE__ */ new Map(), styleMatch = /<style[^>]*>([\s\S]*?)<\/style>/i.exec(svg);
2069
+ if (!styleMatch?.[1])
2070
+ return map;
2071
+ let declaration = /(--[A-Za-z0-9_-]+)\s*:\s*([^;{}]+);/g, match;
2072
+ for (; (match = declaration.exec(styleMatch[1])) !== null; )
2073
+ map.set(match[1], match[2].trim());
2074
+ return map;
2075
+ }
2076
+ function stripSvgStyleBlocks(svg) {
2077
+ return svg.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
2078
+ }
2079
+ function findMatchingSvgParen(input, openIndex) {
2080
+ let depth = 0;
2081
+ for (let i = openIndex; i < input.length; i++) {
2082
+ let ch = input[i];
2083
+ if (ch === "(")
2084
+ depth++;
2085
+ else if (ch === ")" && (depth--, depth === 0))
2086
+ return i;
2087
+ }
2088
+ return -1;
2089
+ }
2090
+ function findTopLevelSvgComma(input) {
2091
+ let depth = 0;
2092
+ for (let i = 0; i < input.length; i++) {
2093
+ let ch = input[i];
2094
+ if (ch === "(")
2095
+ depth++;
2096
+ else if (ch === ")")
2097
+ depth--;
2098
+ else if (ch === "," && depth === 0)
2099
+ return i;
2100
+ }
2101
+ return -1;
2102
+ }
2103
+ function normalizeHexColor(value) {
2104
+ let match = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(value.trim());
2105
+ if (!match)
2106
+ return null;
2107
+ let hex = match[1];
2108
+ return hex.length === 3 && (hex = hex.split("").map((ch) => ch + ch).join("")), `#${hex.toLowerCase()}`;
2109
+ }
2110
+ function mixSrgbColors(aHex, bHex, percent) {
2111
+ let a = parseHexRgb(aHex), b = parseHexRgb(bHex), p = Math.max(0, Math.min(100, percent)) / 100, mix = (x, y) => Math.round(x * p + y * (1 - p)), toHex = (value) => value.toString(16).padStart(2, "0");
2112
+ return `#${toHex(mix(a[0], b[0]))}${toHex(mix(a[1], b[1]))}${toHex(mix(a[2], b[2]))}`;
2113
+ }
2114
+ function parseHexRgb(hex) {
2115
+ return [
2116
+ parseInt(hex.slice(1, 3), 16),
2117
+ parseInt(hex.slice(3, 5), 16),
2118
+ parseInt(hex.slice(5, 7), 16)
2119
+ ];
2120
+ }
2121
+ function resolveSvgColorExpr(expr, root, variables) {
2122
+ let value = expr.trim();
2123
+ if (value.startsWith("var(")) {
2124
+ let close = findMatchingSvgParen(value, 3);
2125
+ if (close < 0)
2126
+ return value;
2127
+ let inner = value.slice(4, close).trim(), comma = findTopLevelSvgComma(inner), name = (comma < 0 ? inner : inner.slice(0, comma)).trim(), fallback = comma < 0 ? "" : inner.slice(comma + 1).trim(), direct = name === "--fg" ? root.fg : name === "--bg" ? root.bg : variables.get(name);
2128
+ return direct != null ? resolveSvgColorExpr(direct, root, variables) : fallback ? resolveSvgColorExpr(fallback, root, variables) : value;
2129
+ }
2130
+ if (value.startsWith("color-mix(")) {
2131
+ let close = findMatchingSvgParen(value, 9);
2132
+ if (close < 0)
2133
+ return value;
2134
+ let afterMethod = value.slice(10, close).trim().replace(/^in\s+srgb\s*,\s*/i, ""), comma = findTopLevelSvgComma(afterMethod);
2135
+ if (comma < 0)
2136
+ return value;
2137
+ let left = afterMethod.slice(0, comma).trim(), right = afterMethod.slice(comma + 1).trim(), leftMatch = /^([^%\s]+)\s+([\d.]+)%$/.exec(left) ?? /^([\d.]+)%\s+([^%\s]+)$/.exec(left);
2138
+ if (!leftMatch)
2139
+ return value;
2140
+ let a = normalizeHexColor(resolveSvgColorExpr(leftMatch[1], root, variables)), b = normalizeHexColor(resolveSvgColorExpr(right, root, variables));
2141
+ return !a || !b ? value : mixSrgbColors(a, b, Number(leftMatch[2]));
2142
+ }
2143
+ return normalizeHexColor(value) ?? value;
2144
+ }
2157
2145
  function loadBuiltinMermaidRasterizer() {
2158
2146
  return builtinRasterizerLoad || (builtinRasterizerLoad = (async () => {
2159
2147
  try {
@@ -2176,7 +2164,7 @@ function loadBuiltinMermaidRasterizer() {
2176
2164
  }), svgText = String(svg ?? "");
2177
2165
  if (!svgText.includes("<svg"))
2178
2166
  return null;
2179
- let rendered = new Resvg(svgText, {
2167
+ let resolvedSvg = resolveMermaidSvgForResvg(svgText, options.bg, options.fg), rendered = new Resvg(resolvedSvg, {
2180
2168
  background: "rgba(0,0,0,0)"
2181
2169
  }).render(), naturalWidth = positiveInt(rendered?.width), naturalHeight = positiveInt(rendered?.height), png = typeof rendered?.asPng == "function" ? rendered.asPng() : void 0;
2182
2170
  if (!png || png.length < 8 || naturalWidth == null || naturalHeight == null)
@@ -2290,6 +2278,25 @@ var tMermaidImageProps = {
2290
2278
  maxWidthCells: { type: Number, default: void 0 },
2291
2279
  maxHeightCells: { type: Number, default: void 0 },
2292
2280
  padding: { type: Number, default: void 0 },
2281
+ // Adaptive ANSI fallback. When the terminal has no graphics protocol (or the
2282
+ // image rasterizer fails for this source), the component renders the diagram
2283
+ // as ASCII text through a text renderer before falling back to the raw
2284
+ // source. Without a textRenderer the component stays image-or-raw.
2285
+ textRenderer: {
2286
+ type: Function,
2287
+ default: void 0
2288
+ },
2289
+ // Eligibility guard for the ANSI fallback; returning false keeps the raw
2290
+ // source visible (e.g. only render simple flowcharts as ASCII).
2291
+ shouldRenderSource: {
2292
+ type: Function,
2293
+ default: void 0
2294
+ },
2295
+ // Spacing/theme options forwarded to the ANSI text renderer.
2296
+ textOptions: {
2297
+ type: Object,
2298
+ default: void 0
2299
+ },
2293
2300
  // Box chrome (mirrors TMermaidText).
2294
2301
  box: { type: Boolean, default: !0 },
2295
2302
  title: { type: String, default: "mermaid" },
@@ -2302,15 +2309,28 @@ var tMermaidImageProps = {
2302
2309
  maxRenderSourceLines: { type: Number, default: DEFAULT_MERMAID_IMAGE_MAX_RENDER_SOURCE_LINES },
2303
2310
  // Clicking the diagram (or the copy button) copies the full raw mermaid source.
2304
2311
  copyOnClick: { type: Boolean, default: !0 },
2305
- ascii: { type: Boolean, default: !1 }
2306
- }, TMermaidImage = (0, import_vue10.defineComponent)({
2312
+ ascii: { type: Boolean, default: !1 },
2313
+ // Wheel zoom (Kitty graphics only): with the zoom modifier held, wheel over
2314
+ // the image zooms centered on the mouse, clamped to the container area.
2315
+ // Without the modifier the wheel passes through to the surrounding scroll
2316
+ // container (historical-message scrolling is never blocked). iTerm2 has no
2317
+ // in-place resize sequence, so zoom stays disabled there.
2318
+ zoomOnWheel: { type: Boolean, default: !0 },
2319
+ zoomModifier: {
2320
+ type: String,
2321
+ default: "metaCtrl"
2322
+ },
2323
+ minZoom: { type: Number, default: 1 },
2324
+ maxZoom: { type: Number, default: 6 },
2325
+ zoomSensitivity: { type: Number, default: 2e-3 }
2326
+ }, TMermaidImage = (0, import_vue9.defineComponent)({
2307
2327
  name: "TMermaidImage",
2308
2328
  props: tMermaidImageProps,
2309
2329
  emits: {
2310
2330
  copy: (_payload) => !0
2311
2331
  },
2312
2332
  setup(props, { emit }) {
2313
- let instance = (0, import_vue10.getCurrentInstance)(), terminalContext = useTerminal(), { terminal, defaultStyle, scheduler, widthProvider } = terminalContext, layout = useLayout(), { visible, rootProps } = useVisibility(), parentEventZ = (0, import_vue10.inject)(EventZIndexContextKey, (0, import_vue10.computed)(() => 0)), imageCells = (0, import_vue10.shallowRef)(null), documentVersion = (0, import_vue10.shallowRef)(0), copied = (0, import_vue10.shallowRef)(!1), builtOnce = !1, renderVersion = 0, alive = !0, frameTaskId = `TMermaidImage:${instance?.uid ?? "unknown"}:mermaid-image`, uid = instance?.uid ?? 0, copiedTimer = null, copyRequestVersion = 0, activeGraphicId = null, source = (0, import_vue10.computed)(() => props.code ?? props.content ?? ""), waitingForStreamingSourceToFinish = (0, import_vue10.computed)(() => props.streaming && !props.final), graphicsOutputVersion = (0, import_vue10.shallowRef)(getTerminalGraphicsOutputVersion(terminal)), graphicsOutput = (0, import_vue10.computed)(() => (graphicsOutputVersion.value, getTerminalGraphicsOutput(terminal))), graphicsProtocol = (0, import_vue10.computed)(() => {
2333
+ let instance = (0, import_vue9.getCurrentInstance)(), terminalContext = useTerminal(), { terminal, defaultStyle, scheduler, widthProvider } = terminalContext, layout = useLayout(), { visible, rootProps } = useVisibility(), parentEventZ = (0, import_vue9.inject)(EventZIndexContextKey, (0, import_vue9.computed)(() => 0)), imageCells = (0, import_vue9.shallowRef)(null), textSnapshot = (0, import_vue9.shallowRef)(null), zoomScale = (0, import_vue9.shallowRef)(1), panX = (0, import_vue9.shallowRef)(0), panY = (0, import_vue9.shallowRef)(0), documentVersion = (0, import_vue9.shallowRef)(0), copied = (0, import_vue9.shallowRef)(!1), dragState = null, dragMoved = !1, lastDragAt = 0, DRAG_CLICK_SUPPRESS_MS = 250, builtOnce = !1, renderVersion = 0, alive = !0, frameTaskId = `TMermaidImage:${instance?.uid ?? "unknown"}:mermaid-image`, uid = instance?.uid ?? 0, copiedTimer = null, copyRequestVersion = 0, activeGraphicId = null, activeImageSignature = "", source = (0, import_vue9.computed)(() => props.code ?? props.content ?? ""), waitingForStreamingSourceToFinish = (0, import_vue9.computed)(() => props.streaming && !props.final), graphicsOutputVersion = (0, import_vue9.shallowRef)(getTerminalGraphicsOutputVersion(terminal)), graphicsOutput = (0, import_vue9.computed)(() => (graphicsOutputVersion.value, getTerminalGraphicsOutput(terminal))), graphicsProtocol = (0, import_vue9.computed)(() => {
2314
2334
  let output = graphicsOutput.value;
2315
2335
  if (!output?.capabilities.supported)
2316
2336
  return null;
@@ -2353,15 +2373,51 @@ var tMermaidImageProps = {
2353
2373
  } catch {
2354
2374
  }
2355
2375
  }
2356
- let hasBox = (0, import_vue10.computed)(() => props.box !== !1), normalizedWidth = (0, import_vue10.computed)(() => {
2376
+ function clampZoom(zoom) {
2377
+ let min = Number.isFinite(Number(props.minZoom)) ? Math.max(0.05, Number(props.minZoom)) : 1, max = Number.isFinite(Number(props.maxZoom)) ? Math.max(min, Number(props.maxZoom)) : Math.max(min, 6);
2378
+ return Math.min(max, Math.max(min, zoom));
2379
+ }
2380
+ function zoomSensitivity() {
2381
+ let value = Number(props.zoomSensitivity);
2382
+ return Number.isFinite(value) && value > 0 ? value : 2e-3;
2383
+ }
2384
+ function wheelZoomFactor(deltaY, deltaMode) {
2385
+ let sign = deltaY > 0 ? -1 : 1, sensitivity = zoomSensitivity(), abs = Math.abs(deltaY), magnitude = (deltaMode ?? 0) === 1 || abs > 0 && abs <= 3 && Number.isInteger(deltaY) ? 100 : abs;
2386
+ return Math.exp(sign * magnitude * sensitivity);
2387
+ }
2388
+ function resetZoomState() {
2389
+ zoomScale.value === 1 && panX.value === 0 && panY.value === 0 || (zoomScale.value = 1, panX.value = 0, panY.value = 0, dragState = null);
2390
+ }
2391
+ function hasZoomModifier(event) {
2392
+ let modifier = props.zoomModifier ?? "metaCtrl";
2393
+ return modifier === "none" ? !0 : modifier === "meta" ? event.metaKey === !0 : modifier === "ctrl" ? event.ctrlKey === !0 : event.ctrlKey === !0 || event.metaKey === !0;
2394
+ }
2395
+ function clampPan() {
2396
+ let inner = innerRectFrom(fullRect.value), fit = displayFit.value;
2397
+ if (!fit || inner.w <= 0 || inner.h <= 0)
2398
+ return;
2399
+ let zoom = clampZoom(zoomScale.value), fullW = fit.displayW * zoom, fullH = fit.displayH * zoom, fitOriginX = inner.x + (inner.w > fit.displayW ? Math.floor((inner.w - fit.displayW) / 2) : 0), nextX = panX.value, nextY = panY.value;
2400
+ fullW <= inner.w ? nextX = 0 : nextX = Math.min(
2401
+ inner.x - fitOriginX,
2402
+ Math.max(inner.x + inner.w - fullW - fitOriginX, nextX)
2403
+ ), fullH <= inner.h ? nextY = 0 : nextY = Math.min(0, Math.max(inner.h - fullH, nextY)), nextX !== panX.value && (panX.value = nextX), nextY !== panY.value && (panY.value = nextY);
2404
+ }
2405
+ function setImageCells(cells) {
2406
+ let signature = cells ? cells.base64 : "";
2407
+ signature !== activeImageSignature && (activeImageSignature = signature, resetZoomState()), imageCells.value = cells;
2408
+ }
2409
+ let hasBox = (0, import_vue9.computed)(() => props.box !== !1), normalizedWidth = (0, import_vue9.computed)(() => {
2357
2410
  let width = Math.floor(Number(props.w));
2358
2411
  return Number.isFinite(width) ? Math.max(0, width) : 0;
2359
- }), reservesBoxRows = (0, import_vue10.computed)(() => hasBox.value && normalizedWidth.value >= 2), boxChars = (0, import_vue10.computed)(
2412
+ }), reservesBoxRows = (0, import_vue9.computed)(() => hasBox.value && normalizedWidth.value >= 2), boxChars = (0, import_vue9.computed)(
2360
2413
  () => props.ascii ? ASCII_MERMAID_IMAGE_BOX_CHARS : UNICODE_MERMAID_IMAGE_BOX_CHARS
2361
- ), sourceLines = (0, import_vue10.computed)(() => splitRenderedOutput2(source.value)), currentStyle = (0, import_vue10.computed)(() => props.style ?? defaultStyle.value), innerWidth = (0, import_vue10.computed)(() => {
2414
+ ), sourceLines = (0, import_vue9.computed)(() => splitRenderedOutput2(source.value)), displayLines = (0, import_vue9.computed)(() => {
2415
+ let snapshot = textSnapshot.value;
2416
+ return snapshot && snapshot.source === source.value && hasVisibleTextOutput(snapshot.lines) ? snapshot.lines : sourceLines.value;
2417
+ }), currentStyle = (0, import_vue9.computed)(() => props.style ?? defaultStyle.value), innerWidth = (0, import_vue9.computed)(() => {
2362
2418
  let w = normalizedWidth.value;
2363
2419
  return reservesBoxRows.value ? Math.max(0, w - 2) : w;
2364
- }), fixedInnerHeight = (0, import_vue10.computed)(() => {
2420
+ }), fixedInnerHeight = (0, import_vue9.computed)(() => {
2365
2421
  if (props.h == null)
2366
2422
  return 0;
2367
2423
  let h4 = Math.floor(Number(props.h));
@@ -2369,23 +2425,23 @@ var tMermaidImageProps = {
2369
2425
  return 0;
2370
2426
  let value = Math.max(0, h4);
2371
2427
  return reservesBoxRows.value ? Math.max(0, value - 2) : value;
2372
- }), autoHeight = (0, import_vue10.computed)(() => props.h == null), displayFit = (0, import_vue10.computed)(() => {
2428
+ }), autoHeight = (0, import_vue9.computed)(() => props.h == null), displayFit = (0, import_vue9.computed)(() => {
2373
2429
  let cells = imageCells.value;
2374
2430
  return cells ? autoHeight.value ? fitImageCells(cells, innerWidth.value, Number.MAX_SAFE_INTEGER) : fitImageCells(cells, innerWidth.value, fixedInnerHeight.value) : null;
2375
- }), contentHeight = (0, import_vue10.computed)(() => autoHeight.value ? displayFit.value?.displayH ?? Math.max(1, sourceLines.value.length) : fixedInnerHeight.value), fullHeight = (0, import_vue10.computed)(() => {
2431
+ }), contentHeight = (0, import_vue9.computed)(() => autoHeight.value ? displayFit.value?.displayH ?? Math.max(1, displayLines.value.length) : fixedInnerHeight.value), fullHeight = (0, import_vue9.computed)(() => {
2376
2432
  if (props.h != null) {
2377
2433
  let h4 = Math.floor(Number(props.h));
2378
2434
  return Number.isFinite(h4) ? Math.max(0, h4) : 0;
2379
2435
  }
2380
2436
  return reservesBoxRows.value ? contentHeight.value + 2 : contentHeight.value;
2381
- }), fullRect = (0, import_vue10.computed)(() => translateRect(
2437
+ }), fullRect = (0, import_vue9.computed)(() => translateRect(
2382
2438
  { x: props.x, y: props.y, w: props.w, h: fullHeight.value },
2383
2439
  layout.originX,
2384
2440
  layout.originY
2385
- )), absRect = (0, import_vue10.computed)(() => {
2441
+ )), absRect = (0, import_vue9.computed)(() => {
2386
2442
  let translated = fullRect.value;
2387
2443
  return layout.clipRect ? intersectRect(translated, layout.clipRect) ?? { x: 0, y: 0, w: 0, h: 0 } : translated;
2388
- }), copyLabel = (0, import_vue10.computed)(() => copied.value ? props.copiedText : props.copyText);
2444
+ }), copyLabel = (0, import_vue9.computed)(() => copied.value ? props.copiedText : props.copyText);
2389
2445
  function cellWidth(value) {
2390
2446
  return withTextWidthProvider(widthProvider, () => textCellWidth(value));
2391
2447
  }
@@ -2414,6 +2470,60 @@ var tMermaidImageProps = {
2414
2470
  padding: props.padding
2415
2471
  });
2416
2472
  }
2473
+ function hasVisibleTextOutput(lines) {
2474
+ return lines.some((line) => line.trim().length > 0);
2475
+ }
2476
+ function resolveTextOptions() {
2477
+ return {
2478
+ ...props.textOptions ?? {},
2479
+ useAscii: props.ascii,
2480
+ colorMode: "none"
2481
+ };
2482
+ }
2483
+ function textRenderSignature(code) {
2484
+ return [
2485
+ code,
2486
+ props.ascii ? "a" : "",
2487
+ JSON.stringify(props.textOptions ?? null),
2488
+ props.textRenderer != null ? "r" : "",
2489
+ props.shouldRenderSource != null ? "g" : ""
2490
+ ].join("");
2491
+ }
2492
+ async function renderTextWithTimeout(renderer, code, options) {
2493
+ let timeoutMs = normalizeNonNegativeInt2(props.renderTimeoutMs, 0);
2494
+ if (timeoutMs <= 0)
2495
+ return await renderer(code, options);
2496
+ let timer = null;
2497
+ try {
2498
+ return await Promise.race([
2499
+ Promise.resolve().then(() => renderer(code, options)),
2500
+ new Promise((_resolve, reject) => {
2501
+ timer = setTimeout(() => reject(new Error("Mermaid text render timeout")), timeoutMs);
2502
+ })
2503
+ ]);
2504
+ } finally {
2505
+ timer != null && clearTimeout(timer);
2506
+ }
2507
+ }
2508
+ async function runCustomRasterizer(code, options) {
2509
+ let renderer = props.renderer;
2510
+ if (!renderer)
2511
+ return null;
2512
+ let timeoutMs = normalizeNonNegativeInt2(props.renderTimeoutMs, 0), run = () => renderer(code, options);
2513
+ if (timeoutMs <= 0)
2514
+ return await run() ?? null;
2515
+ let timer = null;
2516
+ try {
2517
+ return await Promise.race([
2518
+ run(),
2519
+ new Promise((resolve) => {
2520
+ timer = setTimeout(() => resolve(null), timeoutMs);
2521
+ })
2522
+ ]) ?? null;
2523
+ } finally {
2524
+ timer != null && clearTimeout(timer);
2525
+ }
2526
+ }
2417
2527
  function shouldSkipRenderForSize(code) {
2418
2528
  let maxChars = normalizeNonNegativeInt2(
2419
2529
  props.maxRenderSourceChars,
@@ -2443,46 +2553,87 @@ var tMermaidImageProps = {
2443
2553
  timer != null && clearTimeout(timer);
2444
2554
  }
2445
2555
  }
2446
- async function renderImage(version) {
2556
+ async function renderTextFallback(version) {
2557
+ let code = source.value, renderer = props.textRenderer;
2558
+ if (!renderer)
2559
+ return;
2560
+ let guard = props.shouldRenderSource;
2561
+ if (guard) {
2562
+ let eligible = !1;
2563
+ try {
2564
+ eligible = guard(code, { final: props.final, streaming: props.streaming });
2565
+ } catch {
2566
+ eligible = !1;
2567
+ }
2568
+ if (!eligible)
2569
+ return;
2570
+ }
2571
+ try {
2572
+ let rendered = await renderTextWithTimeout(renderer, code, resolveTextOptions());
2573
+ if (!alive || version !== renderVersion)
2574
+ return;
2575
+ let lines = splitRenderedOutput2(rendered);
2576
+ if (!hasVisibleTextOutput(lines))
2577
+ return;
2578
+ textSnapshot.value = (0, import_vue9.markRaw)({
2579
+ source: code,
2580
+ signature: textRenderSignature(code),
2581
+ lines: (0, import_vue9.markRaw)(lines)
2582
+ }), bump();
2583
+ } catch {
2584
+ }
2585
+ }
2586
+ async function renderPresentation(version) {
2447
2587
  let code = source.value;
2448
2588
  if (!code.trim()) {
2449
2589
  if (!alive || version !== renderVersion)
2450
2590
  return;
2451
- imageCells.value = null, bump();
2591
+ setImageCells(null), textSnapshot.value = null, bump();
2452
2592
  return;
2453
2593
  }
2454
2594
  if (waitingForStreamingSourceToFinish.value || shouldSkipRenderForSize(code)) {
2455
2595
  if (!alive || version !== renderVersion)
2456
2596
  return;
2457
- imageCells.value = null, bump();
2597
+ setImageCells(null), textSnapshot.value = null, bump();
2458
2598
  return;
2459
2599
  }
2460
- if (imageCells.value = null, bump(), !graphicsProtocol.value)
2461
- return;
2462
- let options = resolveImageOptions(), cached = getCachedMermaidImage(code, options);
2600
+ let protocol = graphicsProtocol.value, options = resolveImageOptions(), cached = protocol && !props.renderer ? getCachedMermaidImage(code, options) : null;
2463
2601
  if (cached) {
2464
2602
  if (!alive || version !== renderVersion)
2465
2603
  return;
2466
- imageCells.value = cached, bump();
2604
+ setImageCells(cached), textSnapshot.value = null, bump();
2467
2605
  return;
2468
2606
  }
2469
- if (!isMermaidImageRendererReady()) {
2470
- loadMermaidImageRenderer().then((ready) => {
2471
- !alive || version !== renderVersion || !ready || renderImage(version);
2472
- });
2473
- return;
2607
+ let currentSignature = textRenderSignature(code), keepText = textSnapshot.value != null && textSnapshot.value.source === code && textSnapshot.value.signature === currentSignature;
2608
+ if (keepText || (setImageCells(null), textSnapshot.value = null, bump()), protocol) {
2609
+ let result = null;
2610
+ if (props.renderer)
2611
+ result = await runCustomRasterizer(code, options);
2612
+ else if (isMermaidImageRendererReady())
2613
+ result = await rasterizeWithTimeout(code, options);
2614
+ else {
2615
+ loadMermaidImageRenderer().then((ready) => {
2616
+ !alive || version !== renderVersion || !ready || renderPresentation(version);
2617
+ });
2618
+ return;
2619
+ }
2620
+ if (!alive || version !== renderVersion)
2621
+ return;
2622
+ if (result) {
2623
+ setImageCells(result), textSnapshot.value = null, bump();
2624
+ return;
2625
+ }
2474
2626
  }
2475
- let result = await rasterizeWithTimeout(code, options);
2476
- !alive || version !== renderVersion || (imageCells.value = result, bump());
2627
+ keepText || await renderTextFallback(version);
2477
2628
  }
2478
2629
  function scheduleRender() {
2479
2630
  let version = ++renderVersion;
2480
- if (resetCopyFeedback(!1), imageCells.value = null, waitingForStreamingSourceToFinish.value) {
2631
+ if (resetCopyFeedback(!1), setImageCells(null), waitingForStreamingSourceToFinish.value) {
2481
2632
  builtOnce = !0, scheduler.cancelFrameTask?.(frameTaskId), bump();
2482
2633
  return;
2483
2634
  }
2484
2635
  if (!builtOnce || !props.streaming) {
2485
- builtOnce = !0, renderImage(version);
2636
+ builtOnce = !0, renderPresentation(version);
2486
2637
  return;
2487
2638
  }
2488
2639
  scheduler.queueFrameTask({
@@ -2491,11 +2642,11 @@ var tMermaidImageProps = {
2491
2642
  priority: "low",
2492
2643
  sync: !1,
2493
2644
  run: () => {
2494
- alive && version === renderVersion && renderImage(version);
2645
+ alive && version === renderVersion && renderPresentation(version);
2495
2646
  }
2496
- }) === !1 && renderImage(version);
2647
+ }) === !1 && renderPresentation(version);
2497
2648
  }
2498
- (0, import_vue10.watch)(
2649
+ (0, import_vue9.watch)(
2499
2650
  [
2500
2651
  source,
2501
2652
  () => props.renderer,
@@ -2511,7 +2662,11 @@ var tMermaidImageProps = {
2511
2662
  () => props.final,
2512
2663
  () => props.maxRenderSourceChars,
2513
2664
  () => props.maxRenderSourceLines,
2514
- graphicsOutputVersion
2665
+ () => props.textRenderer,
2666
+ () => props.shouldRenderSource,
2667
+ () => props.textOptions,
2668
+ () => props.ascii,
2669
+ () => props.zoomModifier
2515
2670
  ],
2516
2671
  () => {
2517
2672
  scheduleRender();
@@ -2519,11 +2674,14 @@ var tMermaidImageProps = {
2519
2674
  { immediate: !0, deep: !0 }
2520
2675
  );
2521
2676
  let unsubscribeMermaidImage = subscribeMermaidImage(() => {
2522
- alive && scheduleRender();
2677
+ if (!alive || !graphicsProtocol.value || props.renderer)
2678
+ return;
2679
+ let cached = getCachedMermaidImage(source.value, resolveImageOptions());
2680
+ cached && alive && (setImageCells(cached), textSnapshot.value = null, bump());
2523
2681
  }), unsubscribeGraphicsOutput = subscribeTerminalGraphicsOutput(terminal, () => {
2524
2682
  alive && (graphicsOutputVersion.value = getTerminalGraphicsOutputVersion(terminal), scheduleRender());
2525
2683
  });
2526
- (0, import_vue10.onBeforeUnmount)(() => {
2684
+ (0, import_vue9.onBeforeUnmount)(() => {
2527
2685
  alive = !1, renderVersion++, clearCopiedTimer(), queueClearGraphic(), unsubscribeMermaidImage(), unsubscribeGraphicsOutput(), scheduler.cancelFrameTask?.(frameTaskId);
2528
2686
  });
2529
2687
  function canDrawBox(width, height) {
@@ -2583,7 +2741,7 @@ var tMermaidImageProps = {
2583
2741
  return rowIndex === rowHeight - 1 ? `${chars.bl}${repeatChar(chars.h, innerW)}${chars.br}` : `${chars.v}${contentLine(rowIndex - 1, innerW, !0)}${chars.v}`;
2584
2742
  }
2585
2743
  function contentLine(rowIndex, width, pad) {
2586
- let src = imageCells.value != null && displayFit.value != null && graphicsProtocol.value != null ? spaces(width) : sourceLines.value[rowIndex] ?? "", clipped = sliceCells(src, width);
2744
+ let src = imageCells.value != null && displayFit.value != null && graphicsProtocol.value != null ? spaces(width) : displayLines.value[rowIndex] ?? "", clipped = sliceCells(src, width);
2587
2745
  return pad ? padCells(clipped, width) : clipped;
2588
2746
  }
2589
2747
  function innerRectFrom(full) {
@@ -2601,11 +2759,21 @@ var tMermaidImageProps = {
2601
2759
  queueClearGraphic();
2602
2760
  return;
2603
2761
  }
2604
- let offsetX = inner.w > fit.displayW ? Math.floor((inner.w - fit.displayW) / 2) : 0, rect = {
2605
- x: inner.x + offsetX,
2606
- y: inner.y,
2607
- w: fit.displayW,
2608
- h: fit.displayH
2762
+ let zoom = clampZoom(zoomScale.value), fitW = fit.displayW, fitH = fit.displayH, fullRect2 = {
2763
+ x: inner.x + (inner.w > fitW ? Math.floor((inner.w - fitW) / 2) : 0) + panX.value,
2764
+ y: inner.y + panY.value,
2765
+ w: fitW * zoom,
2766
+ h: fitH * zoom
2767
+ }, visible2 = intersectRect(fullRect2, inner);
2768
+ if (!visible2 || visible2.w < 1 || visible2.h < 1) {
2769
+ queueClearGraphic();
2770
+ return;
2771
+ }
2772
+ let rectX = Math.max(inner.x, Math.round(visible2.x)), rectY = Math.max(inner.y, Math.round(visible2.y)), rect = {
2773
+ x: rectX,
2774
+ y: rectY,
2775
+ w: Math.max(1, Math.min(inner.x + inner.w, rectX + Math.round(visible2.w)) - rectX),
2776
+ h: Math.max(1, Math.min(inner.y + inner.h, rectY + Math.round(visible2.h)) - rectY)
2609
2777
  }, imageId = stableTerminalGraphicNumericId(`mermaid-image:${uid}:${renderVersion}:img`), placementId = stableTerminalGraphicNumericId(
2610
2778
  `mermaid-image:${uid}:${renderVersion}:plc`
2611
2779
  ), built = createTerminalGraphicPngSequence({
@@ -2617,10 +2785,16 @@ var tMermaidImageProps = {
2617
2785
  rows: cells.heightCells,
2618
2786
  sourceWidth: cells.naturalWidth,
2619
2787
  sourceHeight: cells.naturalHeight,
2620
- placementColumns: fit.displayW,
2621
- placementRows: fit.displayH,
2788
+ placementColumns: rect.w,
2789
+ placementRows: rect.h,
2790
+ // Keep the initial transmission stable at the fit size without a
2791
+ // source crop so wheel zoom can replace the placement (a=p) without
2792
+ // re-sending the PNG, and every placement references the full image.
2793
+ transmissionColumns: fitW,
2794
+ transmissionRows: fitH,
2795
+ cropTransmission: !1,
2622
2796
  rect,
2623
- fullRect: rect,
2797
+ fullRect: fullRect2,
2624
2798
  zIndex: -1,
2625
2799
  fallback: source.value
2626
2800
  });
@@ -2644,16 +2818,19 @@ var tMermaidImageProps = {
2644
2818
  id,
2645
2819
  x: rect.x,
2646
2820
  y: rect.y,
2647
- w: fit.displayW,
2648
- h: fit.displayH,
2821
+ w: rect.w,
2822
+ h: rect.h,
2649
2823
  protocol,
2650
2824
  sequence: built.sequence,
2651
2825
  resizeSequence: built.resizeSequence,
2652
2826
  clearSequence: built.clearSequence,
2653
- fallbackText: source.value
2827
+ fallbackText: source.value,
2828
+ // Same image data + placement id: let the stdout renderer replace the
2829
+ // placement with the resize sequence instead of re-sending the PNG.
2830
+ placementMoveWithoutClear: protocol === "kitty"
2654
2831
  }) ? activeGraphicId = id : queueClearGraphic();
2655
2832
  }
2656
- let copyHitRect = (0, import_vue10.computed)(() => {
2833
+ let copyHitRect = (0, import_vue9.computed)(() => {
2657
2834
  let full = fullRect.value;
2658
2835
  if (!visible.value || !props.copyButton || !canDrawBox(full.w, full.h))
2659
2836
  return { x: 0, y: 0, w: 0, h: 0 };
@@ -2667,8 +2844,8 @@ var tMermaidImageProps = {
2667
2844
  h: 1
2668
2845
  };
2669
2846
  return layout.clipRect ? intersectRect(raw, layout.clipRect) ?? { x: 0, y: 0, w: 0, h: 0 } : raw;
2670
- }), contentHitRect = (0, import_vue10.computed)(() => {
2671
- if (!visible.value || !props.copyOnClick)
2847
+ }), contentHitRect = (0, import_vue9.computed)(() => {
2848
+ if (!visible.value)
2672
2849
  return { x: 0, y: 0, w: 0, h: 0 };
2673
2850
  let full = fullRect.value;
2674
2851
  if (full.w <= 0 || full.h <= 0)
@@ -2706,12 +2883,52 @@ var tMermaidImageProps = {
2706
2883
  requestVersion === copyRequestVersion && source.value === text && (ok ? showCopiedFeedback() : setCopied(!1)), emit("copy", ok ? { text, ok } : { text, ok, error: copyError });
2707
2884
  }
2708
2885
  function onCopyClick(event) {
2709
- event.preventDefault(), event.stopPropagation(), copySource();
2886
+ event.preventDefault(), event.stopPropagation(), !(Date.now() - lastDragAt < DRAG_CLICK_SUPPRESS_MS) && copySource();
2710
2887
  }
2711
2888
  function onCopyKeydown(event) {
2712
2889
  event.key !== "Enter" && event.key !== " " || (event.preventDefault(), event.stopPropagation(), copySource());
2713
2890
  }
2714
- let copyNodeActive = (0, import_vue10.computed)(
2891
+ let zoomEnabled = (0, import_vue9.computed)(
2892
+ () => props.zoomOnWheel !== !1 && imageCells.value != null && displayFit.value != null && graphicsProtocol.value === "kitty" && contentHitRect.value.w > 0 && contentHitRect.value.h > 0
2893
+ ), dragEnabled = (0, import_vue9.computed)(
2894
+ () => props.zoomOnWheel !== !1 && imageCells.value != null && displayFit.value != null && graphicsProtocol.value === "kitty" && zoomScale.value > 1 && contentHitRect.value.w > 0 && contentHitRect.value.h > 0
2895
+ );
2896
+ function onWheel(event) {
2897
+ if (!zoomEnabled.value || !hasZoomModifier(event))
2898
+ return;
2899
+ event.preventDefault(), event.stopPropagation();
2900
+ let deltaY = event.deltaY ?? 0;
2901
+ if (!Number.isFinite(deltaY) || deltaY === 0)
2902
+ return;
2903
+ let inner = innerRectFrom(fullRect.value), fit = displayFit.value;
2904
+ if (!fit || inner.w <= 0 || inner.h <= 0)
2905
+ return;
2906
+ let currentZoom = clampZoom(zoomScale.value), fitW = fit.displayW, fitH = fit.displayH, fitOriginX = inner.x + (inner.w > fitW ? Math.floor((inner.w - fitW) / 2) : 0), fullX = fitOriginX + panX.value, fullY = inner.y + panY.value, mouseX = event.cellX, mouseY = event.cellY, fx = currentZoom > 0 ? (mouseX - fullX) / (fitW * currentZoom) : 0.5, fy = currentZoom > 0 ? (mouseY - fullY) / (fitH * currentZoom) : 0.5, nextZoom = clampZoom(currentZoom * wheelZoomFactor(deltaY, event.deltaMode));
2907
+ if (nextZoom === currentZoom)
2908
+ return;
2909
+ let nextW = fitW * nextZoom, nextH = fitH * nextZoom, nextX = mouseX - fx * nextW, nextY = mouseY - fy * nextH;
2910
+ nextW <= inner.w ? nextX = inner.x + (inner.w - nextW) / 2 : nextX = Math.min(inner.x, Math.max(inner.x + inner.w - nextW, nextX)), nextH <= inner.h ? nextY = inner.y + (inner.h - nextH) / 2 : nextY = Math.min(inner.y, Math.max(inner.y + inner.h - nextH, nextY)), zoomScale.value = nextZoom, panX.value = nextX - fitOriginX, panY.value = nextY - inner.y, clampPan(), bump();
2911
+ }
2912
+ function onPointerDown(event) {
2913
+ dragEnabled.value && (event.preventDefault(), event.stopPropagation(), dragState = {
2914
+ startX: event.cellX,
2915
+ startY: event.cellY,
2916
+ panStartX: panX.value,
2917
+ panStartY: panY.value
2918
+ }, dragMoved = !1);
2919
+ }
2920
+ function onPointerMove(event) {
2921
+ let drag = dragState;
2922
+ if (!drag)
2923
+ return;
2924
+ event.preventDefault(), event.stopPropagation();
2925
+ let dx = event.cellX - drag.startX, dy = event.cellY - drag.startY;
2926
+ !dragMoved && Math.abs(dx) + Math.abs(dy) >= 1 && (dragMoved = !0), dragMoved && (panX.value = drag.panStartX + dx, panY.value = drag.panStartY + dy, clampPan(), bump());
2927
+ }
2928
+ function onPointerUp(event) {
2929
+ dragState && (event.preventDefault(), event.stopPropagation(), dragState = null, dragMoved && (lastDragAt = Date.now()));
2930
+ }
2931
+ let copyNodeActive = (0, import_vue9.computed)(
2715
2932
  () => visible.value && props.copyButton && copyHitRect.value.w > 0 && copyHitRect.value.h > 0
2716
2933
  );
2717
2934
  useTerminalNode(() => ({
@@ -2725,8 +2942,8 @@ var tMermaidImageProps = {
2725
2942
  keydown: onCopyKeydown
2726
2943
  } : {}
2727
2944
  }));
2728
- let contentNodeActive = (0, import_vue10.computed)(
2729
- () => visible.value && props.copyOnClick && contentHitRect.value.w > 0 && contentHitRect.value.h > 0
2945
+ let contentNodeActive = (0, import_vue9.computed)(
2946
+ () => visible.value && (props.copyOnClick || zoomEnabled.value || dragEnabled.value) && contentHitRect.value.w > 0 && contentHitRect.value.h > 0
2730
2947
  );
2731
2948
  return useTerminalNode(() => ({
2732
2949
  rect: contentHitRect.value,
@@ -2735,7 +2952,11 @@ var tMermaidImageProps = {
2735
2952
  focusable: !1,
2736
2953
  selectable: !1,
2737
2954
  handlers: contentNodeActive.value ? {
2738
- click: onCopyClick
2955
+ click: props.copyOnClick ? onCopyClick : void 0,
2956
+ wheel: zoomEnabled.value ? onWheel : void 0,
2957
+ pointerdown: dragEnabled.value ? onPointerDown : void 0,
2958
+ pointermove: dragEnabled.value ? onPointerMove : void 0,
2959
+ pointerup: dragEnabled.value ? onPointerUp : void 0
2739
2960
  } : {}
2740
2961
  })), useRenderNode(() => ({
2741
2962
  zIndex: props.zIndex,
@@ -2746,6 +2967,11 @@ var tMermaidImageProps = {
2746
2967
  fullRect.value,
2747
2968
  imageCells.value,
2748
2969
  displayFit.value,
2970
+ zoomScale.value,
2971
+ panX.value,
2972
+ panY.value,
2973
+ textSnapshot.value,
2974
+ displayLines.value,
2749
2975
  sourceLines.value,
2750
2976
  currentStyle.value,
2751
2977
  props.clear,
@@ -2788,6 +3014,148 @@ var tMermaidImageProps = {
2788
3014
  paintRow(y);
2789
3015
  });
2790
3016
  }
2791
- })), () => (0, import_vue10.h)("span", rootProps);
3017
+ })), () => (0, import_vue9.h)("span", rootProps);
2792
3018
  }
2793
3019
  });
3020
+
3021
+ // src/vue/mermaid/beautiful-mermaid.ts
3022
+ var 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.", MISSING_BEAUTIFUL_MERMAID_ERROR_CODE = "VUE_TUI_MISSING_BEAUTIFUL_MERMAID", INVALID_BEAUTIFUL_MERMAID_EXPORT_ERROR_CODE = "VUE_TUI_INVALID_BEAUTIFUL_MERMAID_EXPORT", cachedBeautifulMermaid = null;
3023
+ function functionValue(value) {
3024
+ return typeof value == "function" ? value : null;
3025
+ }
3026
+ function functionProp(target, key) {
3027
+ if (!target || typeof target != "object" || !(key in target))
3028
+ return null;
3029
+ let value = target[key];
3030
+ return typeof value == "function" ? value : null;
3031
+ }
3032
+ function errorMessage(error) {
3033
+ return error instanceof Error ? error.message : String(error);
3034
+ }
3035
+ function codedError(message, code) {
3036
+ let error = new Error(message);
3037
+ return error.code = code, error;
3038
+ }
3039
+ function missingBeautifulMermaidError(error) {
3040
+ if (error && typeof error == "object" && errorCode(error) === MISSING_BEAUTIFUL_MERMAID_ERROR_CODE)
3041
+ return markMermaidRenderErrorFatal(error);
3042
+ let detail = errorMessage(error);
3043
+ return markMermaidRenderErrorFatal(
3044
+ codedError(
3045
+ `${BEAUTIFUL_MERMAID_INSTALL_HINT} (${detail})`,
3046
+ MISSING_BEAUTIFUL_MERMAID_ERROR_CODE
3047
+ )
3048
+ );
3049
+ }
3050
+ function errorCode(error) {
3051
+ if (!error || typeof error != "object")
3052
+ return "";
3053
+ let value = error.code;
3054
+ return typeof value == "string" ? value : "";
3055
+ }
3056
+ function errorCause(error) {
3057
+ if (!(!error || typeof error != "object"))
3058
+ return error.cause;
3059
+ }
3060
+ function isMissingBeautifulMermaid(error) {
3061
+ return isMissingBeautifulMermaidWithSeen(error, /* @__PURE__ */ new WeakSet());
3062
+ }
3063
+ function isMissingBeautifulMermaidWithSeen(error, seen) {
3064
+ if (error && typeof error == "object") {
3065
+ if (seen.has(error))
3066
+ return !1;
3067
+ seen.add(error);
3068
+ }
3069
+ let message = errorMessage(error), code = errorCode(error);
3070
+ if (code === MISSING_BEAUTIFUL_MERMAID_ERROR_CODE || (/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)) && (code === "" || code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND" || code === "UNRESOLVED_IMPORT"))
3071
+ return !0;
3072
+ let cause = errorCause(error);
3073
+ return cause !== void 0 && isMissingBeautifulMermaidWithSeen(cause, seen);
3074
+ }
3075
+ function resolveBeautifulMermaidRenderer(mod) {
3076
+ let renderer = functionProp(mod, "renderMermaidASCII") ?? functionProp(mod, "renderMermaidAscii") ?? functionValue(mod.default) ?? functionProp(mod.default, "renderMermaidASCII") ?? functionProp(mod.default, "renderMermaidAscii");
3077
+ if (!renderer)
3078
+ throw markMermaidRenderErrorFatal(
3079
+ codedError(
3080
+ "beautiful-mermaid is installed but does not export renderMermaidASCII.",
3081
+ INVALID_BEAUTIFUL_MERMAID_EXPORT_ERROR_CODE
3082
+ )
3083
+ );
3084
+ return renderer;
3085
+ }
3086
+ function hasErrorCode(error, code, seen = /* @__PURE__ */ new WeakSet()) {
3087
+ if (!error || typeof error != "object" || seen.has(error))
3088
+ return !1;
3089
+ if (seen.add(error), errorCode(error) === code)
3090
+ return !0;
3091
+ let cause = errorCause(error);
3092
+ return cause !== void 0 && hasErrorCode(cause, code, seen);
3093
+ }
3094
+ function isBeautifulMermaidExportError(error) {
3095
+ return hasErrorCode(error, INVALID_BEAUTIFUL_MERMAID_EXPORT_ERROR_CODE);
3096
+ }
3097
+ var isTransientBeautifulMermaidRenderError = (error) => !(isMissingBeautifulMermaid(error) || isBeautifulMermaidExportError(error));
3098
+ async function loadBeautifulMermaid() {
3099
+ return cachedBeautifulMermaid || (cachedBeautifulMermaid = import("beautiful-mermaid").then((mod) => mod).catch((error) => {
3100
+ throw cachedBeautifulMermaid = null, isMissingBeautifulMermaid(error) ? missingBeautifulMermaidError(error) : markMermaidRenderErrorFatal(error);
3101
+ })), cachedBeautifulMermaid;
3102
+ }
3103
+ var beautifulMermaidRenderer = async (code, options) => {
3104
+ try {
3105
+ let mod = await loadBeautifulMermaid();
3106
+ return await resolveBeautifulMermaidRenderer(mod)(code, options);
3107
+ } catch (error) {
3108
+ throw isMissingBeautifulMermaid(error) ? missingBeautifulMermaidError(error) : error;
3109
+ }
3110
+ };
3111
+ function createBeautifulMermaidRenderer() {
3112
+ return beautifulMermaidRenderer;
3113
+ }
3114
+ var TMermaidText2 = (0, import_vue10.defineComponent)({
3115
+ name: "TMermaidText",
3116
+ inheritAttrs: !1,
3117
+ props: tMermaidTextProps,
3118
+ emits: {
3119
+ copy: (_payload) => !0
3120
+ },
3121
+ setup(props, { attrs, emit, slots }) {
3122
+ return () => {
3123
+ let renderer = props.renderer ?? beautifulMermaidRenderer, shouldRenderSource = props.shouldRenderSource ?? isSimpleMermaidFlowchartSource;
3124
+ return (0, import_vue10.h)(
3125
+ TMermaidText,
3126
+ {
3127
+ ...attrs,
3128
+ ...props,
3129
+ renderer,
3130
+ shouldRenderSource,
3131
+ isTransientError: props.isTransientError ?? isTransientBeautifulMermaidRenderError,
3132
+ onCopy: (payload) => emit("copy", payload)
3133
+ },
3134
+ slots
3135
+ );
3136
+ };
3137
+ }
3138
+ }), TMermaid = (0, import_vue10.defineComponent)({
3139
+ name: "TMermaid",
3140
+ inheritAttrs: !1,
3141
+ props: tMermaidImageProps,
3142
+ emits: {
3143
+ copy: (_payload) => !0
3144
+ },
3145
+ setup(props, { attrs, emit, slots }) {
3146
+ return () => {
3147
+ let { renderer: _legacyTextRenderer, ...rest } = props, textRenderer = props.textRenderer ?? props.renderer ?? beautifulMermaidRenderer;
3148
+ return (0, import_vue10.h)(
3149
+ TMermaidImage,
3150
+ {
3151
+ ...attrs,
3152
+ ...rest,
3153
+ textRenderer,
3154
+ shouldRenderSource: props.shouldRenderSource ?? isSimpleMermaidFlowchartSource,
3155
+ onCopy: (payload) => emit("copy", payload)
3156
+ },
3157
+ slots
3158
+ );
3159
+ };
3160
+ }
3161
+ }), TBeautifulMermaidText = TMermaidText2, TBeautifulMermaid = TMermaid;