@simon_he/vue-tui 1.1.4 → 1.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,177 @@ 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 isSvgVarNameChar(ch) {
2068
+ let code = ch.charCodeAt(0);
2069
+ return code >= 48 && code <= 57 || // 0-9
2070
+ code >= 65 && code <= 90 || // A-Z
2071
+ code >= 97 && code <= 122 || // a-z
2072
+ ch === "_" || ch === "-";
2073
+ }
2074
+ function isSvgWhitespace(ch) {
2075
+ return ch === " " || ch === " " || ch === `
2076
+ ` || ch === "\r" || ch === "\f";
2077
+ }
2078
+ function findSvgStyleBlock(svg, from = 0) {
2079
+ let lower = svg.toLowerCase(), open = lower.indexOf("<style", from);
2080
+ if (open < 0)
2081
+ return null;
2082
+ let tagEnd = svg.indexOf(">", open);
2083
+ if (tagEnd < 0)
2084
+ return null;
2085
+ let close = lower.indexOf("</style", tagEnd + 1);
2086
+ if (close < 0)
2087
+ return null;
2088
+ let closeEnd = svg.indexOf(">", close);
2089
+ return closeEnd < 0 ? null : { start: open, contentStart: tagEnd + 1, contentEnd: close, end: closeEnd + 1 };
2090
+ }
2091
+ function extractSvgCssVariables(svg) {
2092
+ let map = /* @__PURE__ */ new Map(), block = findSvgStyleBlock(svg);
2093
+ if (!block)
2094
+ return map;
2095
+ let content = svg.slice(block.contentStart, block.contentEnd), cursor = 0;
2096
+ for (; cursor < content.length; ) {
2097
+ let semi = content.indexOf(";", cursor);
2098
+ if (semi < 0)
2099
+ break;
2100
+ let statement = content.slice(cursor, semi), pos = 0;
2101
+ for (; pos < statement.length; ) {
2102
+ let open = statement.indexOf("--", pos);
2103
+ if (open < 0)
2104
+ break;
2105
+ let nameEnd = open + 2;
2106
+ for (; nameEnd < statement.length && isSvgVarNameChar(statement[nameEnd]); )
2107
+ nameEnd++;
2108
+ if (nameEnd === open + 2) {
2109
+ pos = open + 2;
2110
+ continue;
2111
+ }
2112
+ let colonAt = nameEnd;
2113
+ for (; colonAt < statement.length && isSvgWhitespace(statement[colonAt]); )
2114
+ colonAt++;
2115
+ if (statement[colonAt] !== ":") {
2116
+ pos = open + 2;
2117
+ continue;
2118
+ }
2119
+ let value = statement.slice(colonAt + 1).trim();
2120
+ value !== "" && !value.includes("{") && !value.includes("}") && map.set(statement.slice(open, nameEnd), value);
2121
+ break;
2122
+ }
2123
+ cursor = semi + 1;
2124
+ }
2125
+ return map;
2126
+ }
2127
+ function isSvgStyleOpener(svg, open) {
2128
+ let after = svg[open + 6];
2129
+ return after === ">" || after === "/" || isSvgWhitespace(after);
2130
+ }
2131
+ function stripSvgStyleBlocks(svg) {
2132
+ let result = svg, block = findSvgStyleBlock(result);
2133
+ for (; block; )
2134
+ result = result.slice(0, block.start) + result.slice(block.end), block = findSvgStyleBlock(result, block.start);
2135
+ let open = result.toLowerCase().indexOf("<style");
2136
+ for (; open >= 0; ) {
2137
+ if (!isSvgStyleOpener(result, open)) {
2138
+ open = result.toLowerCase().indexOf("<style", open + 6);
2139
+ continue;
2140
+ }
2141
+ let nextLt = result.indexOf("<", open + 6), tagEnd = result.indexOf(">", open);
2142
+ if (tagEnd < 0 || nextLt >= 0 && nextLt < tagEnd) {
2143
+ open = result.toLowerCase().indexOf("<style", open + 6);
2144
+ continue;
2145
+ }
2146
+ result = result.slice(0, open) + result.slice(tagEnd + 1), open = result.toLowerCase().indexOf("<style", open);
2147
+ }
2148
+ return result;
2149
+ }
2150
+ function findMatchingSvgParen(input, openIndex) {
2151
+ let depth = 0;
2152
+ for (let i = openIndex; i < input.length; i++) {
2153
+ let ch = input[i];
2154
+ if (ch === "(")
2155
+ depth++;
2156
+ else if (ch === ")" && (depth--, depth === 0))
2157
+ return i;
2158
+ }
2159
+ return -1;
2160
+ }
2161
+ function findTopLevelSvgComma(input) {
2162
+ let depth = 0;
2163
+ for (let i = 0; i < input.length; i++) {
2164
+ let ch = input[i];
2165
+ if (ch === "(")
2166
+ depth++;
2167
+ else if (ch === ")")
2168
+ depth--;
2169
+ else if (ch === "," && depth === 0)
2170
+ return i;
2171
+ }
2172
+ return -1;
2173
+ }
2174
+ function normalizeHexColor(value) {
2175
+ let match = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(value.trim());
2176
+ if (!match)
2177
+ return null;
2178
+ let hex = match[1];
2179
+ return hex.length === 3 && (hex = hex.split("").map((ch) => ch + ch).join("")), `#${hex.toLowerCase()}`;
2180
+ }
2181
+ function mixSrgbColors(aHex, bHex, percent) {
2182
+ 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");
2183
+ return `#${toHex(mix(a[0], b[0]))}${toHex(mix(a[1], b[1]))}${toHex(mix(a[2], b[2]))}`;
2184
+ }
2185
+ function parseHexRgb(hex) {
2186
+ return [
2187
+ parseInt(hex.slice(1, 3), 16),
2188
+ parseInt(hex.slice(3, 5), 16),
2189
+ parseInt(hex.slice(5, 7), 16)
2190
+ ];
2191
+ }
2192
+ function resolveSvgColorExpr(expr, root, variables) {
2193
+ let value = expr.trim();
2194
+ if (value.startsWith("var(")) {
2195
+ let close = findMatchingSvgParen(value, 3);
2196
+ if (close < 0)
2197
+ return value;
2198
+ 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);
2199
+ return direct != null ? resolveSvgColorExpr(direct, root, variables) : fallback ? resolveSvgColorExpr(fallback, root, variables) : value;
2200
+ }
2201
+ if (value.startsWith("color-mix(")) {
2202
+ let close = findMatchingSvgParen(value, 9);
2203
+ if (close < 0)
2204
+ return value;
2205
+ let afterMethod = value.slice(10, close).trim().replace(/^in\s+srgb\s*,\s*/i, ""), comma = findTopLevelSvgComma(afterMethod);
2206
+ if (comma < 0)
2207
+ return value;
2208
+ let left = afterMethod.slice(0, comma).trim(), right = afterMethod.slice(comma + 1).trim(), leftMatch = /^([^%\s]+)\s+([\d.]+)%$/.exec(left) ?? /^([\d.]+)%\s+([^%\s]+)$/.exec(left);
2209
+ if (!leftMatch)
2210
+ return value;
2211
+ let a = normalizeHexColor(resolveSvgColorExpr(leftMatch[1], root, variables)), b = normalizeHexColor(resolveSvgColorExpr(right, root, variables));
2212
+ return !a || !b ? value : mixSrgbColors(a, b, Number(leftMatch[2]));
2213
+ }
2214
+ return normalizeHexColor(value) ?? value;
2215
+ }
2157
2216
  function loadBuiltinMermaidRasterizer() {
2158
2217
  return builtinRasterizerLoad || (builtinRasterizerLoad = (async () => {
2159
2218
  try {
@@ -2176,7 +2235,7 @@ function loadBuiltinMermaidRasterizer() {
2176
2235
  }), svgText = String(svg ?? "");
2177
2236
  if (!svgText.includes("<svg"))
2178
2237
  return null;
2179
- let rendered = new Resvg(svgText, {
2238
+ let resolvedSvg = resolveMermaidSvgForResvg(svgText, options.bg, options.fg), rendered = new Resvg(resolvedSvg, {
2180
2239
  background: "rgba(0,0,0,0)"
2181
2240
  }).render(), naturalWidth = positiveInt(rendered?.width), naturalHeight = positiveInt(rendered?.height), png = typeof rendered?.asPng == "function" ? rendered.asPng() : void 0;
2182
2241
  if (!png || png.length < 8 || naturalWidth == null || naturalHeight == null)
@@ -2290,6 +2349,25 @@ var tMermaidImageProps = {
2290
2349
  maxWidthCells: { type: Number, default: void 0 },
2291
2350
  maxHeightCells: { type: Number, default: void 0 },
2292
2351
  padding: { type: Number, default: void 0 },
2352
+ // Adaptive ANSI fallback. When the terminal has no graphics protocol (or the
2353
+ // image rasterizer fails for this source), the component renders the diagram
2354
+ // as ASCII text through a text renderer before falling back to the raw
2355
+ // source. Without a textRenderer the component stays image-or-raw.
2356
+ textRenderer: {
2357
+ type: Function,
2358
+ default: void 0
2359
+ },
2360
+ // Eligibility guard for the ANSI fallback; returning false keeps the raw
2361
+ // source visible (e.g. only render simple flowcharts as ASCII).
2362
+ shouldRenderSource: {
2363
+ type: Function,
2364
+ default: void 0
2365
+ },
2366
+ // Spacing/theme options forwarded to the ANSI text renderer.
2367
+ textOptions: {
2368
+ type: Object,
2369
+ default: void 0
2370
+ },
2293
2371
  // Box chrome (mirrors TMermaidText).
2294
2372
  box: { type: Boolean, default: !0 },
2295
2373
  title: { type: String, default: "mermaid" },
@@ -2302,15 +2380,28 @@ var tMermaidImageProps = {
2302
2380
  maxRenderSourceLines: { type: Number, default: DEFAULT_MERMAID_IMAGE_MAX_RENDER_SOURCE_LINES },
2303
2381
  // Clicking the diagram (or the copy button) copies the full raw mermaid source.
2304
2382
  copyOnClick: { type: Boolean, default: !0 },
2305
- ascii: { type: Boolean, default: !1 }
2306
- }, TMermaidImage = (0, import_vue10.defineComponent)({
2383
+ ascii: { type: Boolean, default: !1 },
2384
+ // Wheel zoom (Kitty graphics only): with the zoom modifier held, wheel over
2385
+ // the image zooms centered on the mouse, clamped to the container area.
2386
+ // Without the modifier the wheel passes through to the surrounding scroll
2387
+ // container (historical-message scrolling is never blocked). iTerm2 has no
2388
+ // in-place resize sequence, so zoom stays disabled there.
2389
+ zoomOnWheel: { type: Boolean, default: !0 },
2390
+ zoomModifier: {
2391
+ type: String,
2392
+ default: "metaCtrl"
2393
+ },
2394
+ minZoom: { type: Number, default: 1 },
2395
+ maxZoom: { type: Number, default: 6 },
2396
+ zoomSensitivity: { type: Number, default: 2e-3 }
2397
+ }, TMermaidImage = (0, import_vue9.defineComponent)({
2307
2398
  name: "TMermaidImage",
2308
2399
  props: tMermaidImageProps,
2309
2400
  emits: {
2310
2401
  copy: (_payload) => !0
2311
2402
  },
2312
2403
  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)(() => {
2404
+ 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
2405
  let output = graphicsOutput.value;
2315
2406
  if (!output?.capabilities.supported)
2316
2407
  return null;
@@ -2353,15 +2444,51 @@ var tMermaidImageProps = {
2353
2444
  } catch {
2354
2445
  }
2355
2446
  }
2356
- let hasBox = (0, import_vue10.computed)(() => props.box !== !1), normalizedWidth = (0, import_vue10.computed)(() => {
2447
+ function clampZoom(zoom) {
2448
+ 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);
2449
+ return Math.min(max, Math.max(min, zoom));
2450
+ }
2451
+ function zoomSensitivity() {
2452
+ let value = Number(props.zoomSensitivity);
2453
+ return Number.isFinite(value) && value > 0 ? value : 2e-3;
2454
+ }
2455
+ function wheelZoomFactor(deltaY, deltaMode) {
2456
+ 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;
2457
+ return Math.exp(sign * magnitude * sensitivity);
2458
+ }
2459
+ function resetZoomState() {
2460
+ zoomScale.value === 1 && panX.value === 0 && panY.value === 0 || (zoomScale.value = 1, panX.value = 0, panY.value = 0, dragState = null);
2461
+ }
2462
+ function hasZoomModifier(event) {
2463
+ let modifier = props.zoomModifier ?? "metaCtrl";
2464
+ return modifier === "none" ? !0 : modifier === "meta" ? event.metaKey === !0 : modifier === "ctrl" ? event.ctrlKey === !0 : event.ctrlKey === !0 || event.metaKey === !0;
2465
+ }
2466
+ function clampPan() {
2467
+ let inner = innerRectFrom(fullRect.value), fit = displayFit.value;
2468
+ if (!fit || inner.w <= 0 || inner.h <= 0)
2469
+ return;
2470
+ 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;
2471
+ fullW <= inner.w ? nextX = 0 : nextX = Math.min(
2472
+ inner.x - fitOriginX,
2473
+ Math.max(inner.x + inner.w - fullW - fitOriginX, nextX)
2474
+ ), 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);
2475
+ }
2476
+ function setImageCells(cells) {
2477
+ let signature = cells ? cells.base64 : "";
2478
+ signature !== activeImageSignature && (activeImageSignature = signature, resetZoomState()), imageCells.value = cells;
2479
+ }
2480
+ let hasBox = (0, import_vue9.computed)(() => props.box !== !1), normalizedWidth = (0, import_vue9.computed)(() => {
2357
2481
  let width = Math.floor(Number(props.w));
2358
2482
  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)(
2483
+ }), reservesBoxRows = (0, import_vue9.computed)(() => hasBox.value && normalizedWidth.value >= 2), boxChars = (0, import_vue9.computed)(
2360
2484
  () => 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)(() => {
2485
+ ), sourceLines = (0, import_vue9.computed)(() => splitRenderedOutput2(source.value)), displayLines = (0, import_vue9.computed)(() => {
2486
+ let snapshot = textSnapshot.value;
2487
+ return snapshot && snapshot.source === source.value && hasVisibleTextOutput(snapshot.lines) ? snapshot.lines : sourceLines.value;
2488
+ }), currentStyle = (0, import_vue9.computed)(() => props.style ?? defaultStyle.value), innerWidth = (0, import_vue9.computed)(() => {
2362
2489
  let w = normalizedWidth.value;
2363
2490
  return reservesBoxRows.value ? Math.max(0, w - 2) : w;
2364
- }), fixedInnerHeight = (0, import_vue10.computed)(() => {
2491
+ }), fixedInnerHeight = (0, import_vue9.computed)(() => {
2365
2492
  if (props.h == null)
2366
2493
  return 0;
2367
2494
  let h4 = Math.floor(Number(props.h));
@@ -2369,23 +2496,23 @@ var tMermaidImageProps = {
2369
2496
  return 0;
2370
2497
  let value = Math.max(0, h4);
2371
2498
  return reservesBoxRows.value ? Math.max(0, value - 2) : value;
2372
- }), autoHeight = (0, import_vue10.computed)(() => props.h == null), displayFit = (0, import_vue10.computed)(() => {
2499
+ }), autoHeight = (0, import_vue9.computed)(() => props.h == null), displayFit = (0, import_vue9.computed)(() => {
2373
2500
  let cells = imageCells.value;
2374
2501
  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)(() => {
2502
+ }), contentHeight = (0, import_vue9.computed)(() => autoHeight.value ? displayFit.value?.displayH ?? Math.max(1, displayLines.value.length) : fixedInnerHeight.value), fullHeight = (0, import_vue9.computed)(() => {
2376
2503
  if (props.h != null) {
2377
2504
  let h4 = Math.floor(Number(props.h));
2378
2505
  return Number.isFinite(h4) ? Math.max(0, h4) : 0;
2379
2506
  }
2380
2507
  return reservesBoxRows.value ? contentHeight.value + 2 : contentHeight.value;
2381
- }), fullRect = (0, import_vue10.computed)(() => translateRect(
2508
+ }), fullRect = (0, import_vue9.computed)(() => translateRect(
2382
2509
  { x: props.x, y: props.y, w: props.w, h: fullHeight.value },
2383
2510
  layout.originX,
2384
2511
  layout.originY
2385
- )), absRect = (0, import_vue10.computed)(() => {
2512
+ )), absRect = (0, import_vue9.computed)(() => {
2386
2513
  let translated = fullRect.value;
2387
2514
  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);
2515
+ }), copyLabel = (0, import_vue9.computed)(() => copied.value ? props.copiedText : props.copyText);
2389
2516
  function cellWidth(value) {
2390
2517
  return withTextWidthProvider(widthProvider, () => textCellWidth(value));
2391
2518
  }
@@ -2414,6 +2541,60 @@ var tMermaidImageProps = {
2414
2541
  padding: props.padding
2415
2542
  });
2416
2543
  }
2544
+ function hasVisibleTextOutput(lines) {
2545
+ return lines.some((line) => line.trim().length > 0);
2546
+ }
2547
+ function resolveTextOptions() {
2548
+ return {
2549
+ ...props.textOptions ?? {},
2550
+ useAscii: props.ascii,
2551
+ colorMode: "none"
2552
+ };
2553
+ }
2554
+ function textRenderSignature(code) {
2555
+ return [
2556
+ code,
2557
+ props.ascii ? "a" : "",
2558
+ JSON.stringify(props.textOptions ?? null),
2559
+ props.textRenderer != null ? "r" : "",
2560
+ props.shouldRenderSource != null ? "g" : ""
2561
+ ].join("");
2562
+ }
2563
+ async function renderTextWithTimeout(renderer, code, options) {
2564
+ let timeoutMs = normalizeNonNegativeInt2(props.renderTimeoutMs, 0);
2565
+ if (timeoutMs <= 0)
2566
+ return await renderer(code, options);
2567
+ let timer = null;
2568
+ try {
2569
+ return await Promise.race([
2570
+ Promise.resolve().then(() => renderer(code, options)),
2571
+ new Promise((_resolve, reject) => {
2572
+ timer = setTimeout(() => reject(new Error("Mermaid text render timeout")), timeoutMs);
2573
+ })
2574
+ ]);
2575
+ } finally {
2576
+ timer != null && clearTimeout(timer);
2577
+ }
2578
+ }
2579
+ async function runCustomRasterizer(code, options) {
2580
+ let renderer = props.renderer;
2581
+ if (!renderer)
2582
+ return null;
2583
+ let timeoutMs = normalizeNonNegativeInt2(props.renderTimeoutMs, 0), run = () => renderer(code, options);
2584
+ if (timeoutMs <= 0)
2585
+ return await run() ?? null;
2586
+ let timer = null;
2587
+ try {
2588
+ return await Promise.race([
2589
+ run(),
2590
+ new Promise((resolve) => {
2591
+ timer = setTimeout(() => resolve(null), timeoutMs);
2592
+ })
2593
+ ]) ?? null;
2594
+ } finally {
2595
+ timer != null && clearTimeout(timer);
2596
+ }
2597
+ }
2417
2598
  function shouldSkipRenderForSize(code) {
2418
2599
  let maxChars = normalizeNonNegativeInt2(
2419
2600
  props.maxRenderSourceChars,
@@ -2443,46 +2624,87 @@ var tMermaidImageProps = {
2443
2624
  timer != null && clearTimeout(timer);
2444
2625
  }
2445
2626
  }
2446
- async function renderImage(version) {
2627
+ async function renderTextFallback(version) {
2628
+ let code = source.value, renderer = props.textRenderer;
2629
+ if (!renderer)
2630
+ return;
2631
+ let guard = props.shouldRenderSource;
2632
+ if (guard) {
2633
+ let eligible = !1;
2634
+ try {
2635
+ eligible = guard(code, { final: props.final, streaming: props.streaming });
2636
+ } catch {
2637
+ eligible = !1;
2638
+ }
2639
+ if (!eligible)
2640
+ return;
2641
+ }
2642
+ try {
2643
+ let rendered = await renderTextWithTimeout(renderer, code, resolveTextOptions());
2644
+ if (!alive || version !== renderVersion)
2645
+ return;
2646
+ let lines = splitRenderedOutput2(rendered);
2647
+ if (!hasVisibleTextOutput(lines))
2648
+ return;
2649
+ textSnapshot.value = (0, import_vue9.markRaw)({
2650
+ source: code,
2651
+ signature: textRenderSignature(code),
2652
+ lines: (0, import_vue9.markRaw)(lines)
2653
+ }), bump();
2654
+ } catch {
2655
+ }
2656
+ }
2657
+ async function renderPresentation(version) {
2447
2658
  let code = source.value;
2448
2659
  if (!code.trim()) {
2449
2660
  if (!alive || version !== renderVersion)
2450
2661
  return;
2451
- imageCells.value = null, bump();
2662
+ setImageCells(null), textSnapshot.value = null, bump();
2452
2663
  return;
2453
2664
  }
2454
2665
  if (waitingForStreamingSourceToFinish.value || shouldSkipRenderForSize(code)) {
2455
2666
  if (!alive || version !== renderVersion)
2456
2667
  return;
2457
- imageCells.value = null, bump();
2668
+ setImageCells(null), textSnapshot.value = null, bump();
2458
2669
  return;
2459
2670
  }
2460
- if (imageCells.value = null, bump(), !graphicsProtocol.value)
2461
- return;
2462
- let options = resolveImageOptions(), cached = getCachedMermaidImage(code, options);
2671
+ let protocol = graphicsProtocol.value, options = resolveImageOptions(), cached = protocol && !props.renderer ? getCachedMermaidImage(code, options) : null;
2463
2672
  if (cached) {
2464
2673
  if (!alive || version !== renderVersion)
2465
2674
  return;
2466
- imageCells.value = cached, bump();
2675
+ setImageCells(cached), textSnapshot.value = null, bump();
2467
2676
  return;
2468
2677
  }
2469
- if (!isMermaidImageRendererReady()) {
2470
- loadMermaidImageRenderer().then((ready) => {
2471
- !alive || version !== renderVersion || !ready || renderImage(version);
2472
- });
2473
- return;
2678
+ let currentSignature = textRenderSignature(code), keepText = textSnapshot.value != null && textSnapshot.value.source === code && textSnapshot.value.signature === currentSignature;
2679
+ if (keepText || (setImageCells(null), textSnapshot.value = null, bump()), protocol) {
2680
+ let result = null;
2681
+ if (props.renderer)
2682
+ result = await runCustomRasterizer(code, options);
2683
+ else if (isMermaidImageRendererReady())
2684
+ result = await rasterizeWithTimeout(code, options);
2685
+ else {
2686
+ loadMermaidImageRenderer().then((ready) => {
2687
+ !alive || version !== renderVersion || !ready || renderPresentation(version);
2688
+ });
2689
+ return;
2690
+ }
2691
+ if (!alive || version !== renderVersion)
2692
+ return;
2693
+ if (result) {
2694
+ setImageCells(result), textSnapshot.value = null, bump();
2695
+ return;
2696
+ }
2474
2697
  }
2475
- let result = await rasterizeWithTimeout(code, options);
2476
- !alive || version !== renderVersion || (imageCells.value = result, bump());
2698
+ keepText || await renderTextFallback(version);
2477
2699
  }
2478
2700
  function scheduleRender() {
2479
2701
  let version = ++renderVersion;
2480
- if (resetCopyFeedback(!1), imageCells.value = null, waitingForStreamingSourceToFinish.value) {
2702
+ if (resetCopyFeedback(!1), setImageCells(null), waitingForStreamingSourceToFinish.value) {
2481
2703
  builtOnce = !0, scheduler.cancelFrameTask?.(frameTaskId), bump();
2482
2704
  return;
2483
2705
  }
2484
2706
  if (!builtOnce || !props.streaming) {
2485
- builtOnce = !0, renderImage(version);
2707
+ builtOnce = !0, renderPresentation(version);
2486
2708
  return;
2487
2709
  }
2488
2710
  scheduler.queueFrameTask({
@@ -2491,11 +2713,11 @@ var tMermaidImageProps = {
2491
2713
  priority: "low",
2492
2714
  sync: !1,
2493
2715
  run: () => {
2494
- alive && version === renderVersion && renderImage(version);
2716
+ alive && version === renderVersion && renderPresentation(version);
2495
2717
  }
2496
- }) === !1 && renderImage(version);
2718
+ }) === !1 && renderPresentation(version);
2497
2719
  }
2498
- (0, import_vue10.watch)(
2720
+ (0, import_vue9.watch)(
2499
2721
  [
2500
2722
  source,
2501
2723
  () => props.renderer,
@@ -2511,7 +2733,11 @@ var tMermaidImageProps = {
2511
2733
  () => props.final,
2512
2734
  () => props.maxRenderSourceChars,
2513
2735
  () => props.maxRenderSourceLines,
2514
- graphicsOutputVersion
2736
+ () => props.textRenderer,
2737
+ () => props.shouldRenderSource,
2738
+ () => props.textOptions,
2739
+ () => props.ascii,
2740
+ () => props.zoomModifier
2515
2741
  ],
2516
2742
  () => {
2517
2743
  scheduleRender();
@@ -2519,11 +2745,14 @@ var tMermaidImageProps = {
2519
2745
  { immediate: !0, deep: !0 }
2520
2746
  );
2521
2747
  let unsubscribeMermaidImage = subscribeMermaidImage(() => {
2522
- alive && scheduleRender();
2748
+ if (!alive || !graphicsProtocol.value || props.renderer)
2749
+ return;
2750
+ let cached = getCachedMermaidImage(source.value, resolveImageOptions());
2751
+ cached && alive && (setImageCells(cached), textSnapshot.value = null, bump());
2523
2752
  }), unsubscribeGraphicsOutput = subscribeTerminalGraphicsOutput(terminal, () => {
2524
2753
  alive && (graphicsOutputVersion.value = getTerminalGraphicsOutputVersion(terminal), scheduleRender());
2525
2754
  });
2526
- (0, import_vue10.onBeforeUnmount)(() => {
2755
+ (0, import_vue9.onBeforeUnmount)(() => {
2527
2756
  alive = !1, renderVersion++, clearCopiedTimer(), queueClearGraphic(), unsubscribeMermaidImage(), unsubscribeGraphicsOutput(), scheduler.cancelFrameTask?.(frameTaskId);
2528
2757
  });
2529
2758
  function canDrawBox(width, height) {
@@ -2583,7 +2812,7 @@ var tMermaidImageProps = {
2583
2812
  return rowIndex === rowHeight - 1 ? `${chars.bl}${repeatChar(chars.h, innerW)}${chars.br}` : `${chars.v}${contentLine(rowIndex - 1, innerW, !0)}${chars.v}`;
2584
2813
  }
2585
2814
  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);
2815
+ let src = imageCells.value != null && displayFit.value != null && graphicsProtocol.value != null ? spaces(width) : displayLines.value[rowIndex] ?? "", clipped = sliceCells(src, width);
2587
2816
  return pad ? padCells(clipped, width) : clipped;
2588
2817
  }
2589
2818
  function innerRectFrom(full) {
@@ -2601,11 +2830,21 @@ var tMermaidImageProps = {
2601
2830
  queueClearGraphic();
2602
2831
  return;
2603
2832
  }
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
2833
+ let zoom = clampZoom(zoomScale.value), fitW = fit.displayW, fitH = fit.displayH, fullRect2 = {
2834
+ x: inner.x + (inner.w > fitW ? Math.floor((inner.w - fitW) / 2) : 0) + panX.value,
2835
+ y: inner.y + panY.value,
2836
+ w: fitW * zoom,
2837
+ h: fitH * zoom
2838
+ }, visible2 = intersectRect(fullRect2, inner);
2839
+ if (!visible2 || visible2.w < 1 || visible2.h < 1) {
2840
+ queueClearGraphic();
2841
+ return;
2842
+ }
2843
+ let rectX = Math.max(inner.x, Math.round(visible2.x)), rectY = Math.max(inner.y, Math.round(visible2.y)), rect = {
2844
+ x: rectX,
2845
+ y: rectY,
2846
+ w: Math.max(1, Math.min(inner.x + inner.w, rectX + Math.round(visible2.w)) - rectX),
2847
+ h: Math.max(1, Math.min(inner.y + inner.h, rectY + Math.round(visible2.h)) - rectY)
2609
2848
  }, imageId = stableTerminalGraphicNumericId(`mermaid-image:${uid}:${renderVersion}:img`), placementId = stableTerminalGraphicNumericId(
2610
2849
  `mermaid-image:${uid}:${renderVersion}:plc`
2611
2850
  ), built = createTerminalGraphicPngSequence({
@@ -2617,10 +2856,16 @@ var tMermaidImageProps = {
2617
2856
  rows: cells.heightCells,
2618
2857
  sourceWidth: cells.naturalWidth,
2619
2858
  sourceHeight: cells.naturalHeight,
2620
- placementColumns: fit.displayW,
2621
- placementRows: fit.displayH,
2859
+ placementColumns: rect.w,
2860
+ placementRows: rect.h,
2861
+ // Keep the initial transmission stable at the fit size without a
2862
+ // source crop so wheel zoom can replace the placement (a=p) without
2863
+ // re-sending the PNG, and every placement references the full image.
2864
+ transmissionColumns: fitW,
2865
+ transmissionRows: fitH,
2866
+ cropTransmission: !1,
2622
2867
  rect,
2623
- fullRect: rect,
2868
+ fullRect: fullRect2,
2624
2869
  zIndex: -1,
2625
2870
  fallback: source.value
2626
2871
  });
@@ -2644,16 +2889,19 @@ var tMermaidImageProps = {
2644
2889
  id,
2645
2890
  x: rect.x,
2646
2891
  y: rect.y,
2647
- w: fit.displayW,
2648
- h: fit.displayH,
2892
+ w: rect.w,
2893
+ h: rect.h,
2649
2894
  protocol,
2650
2895
  sequence: built.sequence,
2651
2896
  resizeSequence: built.resizeSequence,
2652
2897
  clearSequence: built.clearSequence,
2653
- fallbackText: source.value
2898
+ fallbackText: source.value,
2899
+ // Same image data + placement id: let the stdout renderer replace the
2900
+ // placement with the resize sequence instead of re-sending the PNG.
2901
+ placementMoveWithoutClear: protocol === "kitty"
2654
2902
  }) ? activeGraphicId = id : queueClearGraphic();
2655
2903
  }
2656
- let copyHitRect = (0, import_vue10.computed)(() => {
2904
+ let copyHitRect = (0, import_vue9.computed)(() => {
2657
2905
  let full = fullRect.value;
2658
2906
  if (!visible.value || !props.copyButton || !canDrawBox(full.w, full.h))
2659
2907
  return { x: 0, y: 0, w: 0, h: 0 };
@@ -2667,8 +2915,8 @@ var tMermaidImageProps = {
2667
2915
  h: 1
2668
2916
  };
2669
2917
  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)
2918
+ }), contentHitRect = (0, import_vue9.computed)(() => {
2919
+ if (!visible.value)
2672
2920
  return { x: 0, y: 0, w: 0, h: 0 };
2673
2921
  let full = fullRect.value;
2674
2922
  if (full.w <= 0 || full.h <= 0)
@@ -2706,12 +2954,52 @@ var tMermaidImageProps = {
2706
2954
  requestVersion === copyRequestVersion && source.value === text && (ok ? showCopiedFeedback() : setCopied(!1)), emit("copy", ok ? { text, ok } : { text, ok, error: copyError });
2707
2955
  }
2708
2956
  function onCopyClick(event) {
2709
- event.preventDefault(), event.stopPropagation(), copySource();
2957
+ event.preventDefault(), event.stopPropagation(), !(Date.now() - lastDragAt < DRAG_CLICK_SUPPRESS_MS) && copySource();
2710
2958
  }
2711
2959
  function onCopyKeydown(event) {
2712
2960
  event.key !== "Enter" && event.key !== " " || (event.preventDefault(), event.stopPropagation(), copySource());
2713
2961
  }
2714
- let copyNodeActive = (0, import_vue10.computed)(
2962
+ let zoomEnabled = (0, import_vue9.computed)(
2963
+ () => props.zoomOnWheel !== !1 && imageCells.value != null && displayFit.value != null && graphicsProtocol.value === "kitty" && contentHitRect.value.w > 0 && contentHitRect.value.h > 0
2964
+ ), dragEnabled = (0, import_vue9.computed)(
2965
+ () => props.zoomOnWheel !== !1 && imageCells.value != null && displayFit.value != null && graphicsProtocol.value === "kitty" && zoomScale.value > 1 && contentHitRect.value.w > 0 && contentHitRect.value.h > 0
2966
+ );
2967
+ function onWheel(event) {
2968
+ if (!zoomEnabled.value || !hasZoomModifier(event))
2969
+ return;
2970
+ event.preventDefault(), event.stopPropagation();
2971
+ let deltaY = event.deltaY ?? 0;
2972
+ if (!Number.isFinite(deltaY) || deltaY === 0)
2973
+ return;
2974
+ let inner = innerRectFrom(fullRect.value), fit = displayFit.value;
2975
+ if (!fit || inner.w <= 0 || inner.h <= 0)
2976
+ return;
2977
+ 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));
2978
+ if (nextZoom === currentZoom)
2979
+ return;
2980
+ let nextW = fitW * nextZoom, nextH = fitH * nextZoom, nextX = mouseX - fx * nextW, nextY = mouseY - fy * nextH;
2981
+ 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();
2982
+ }
2983
+ function onPointerDown(event) {
2984
+ dragEnabled.value && (event.preventDefault(), event.stopPropagation(), dragState = {
2985
+ startX: event.cellX,
2986
+ startY: event.cellY,
2987
+ panStartX: panX.value,
2988
+ panStartY: panY.value
2989
+ }, dragMoved = !1);
2990
+ }
2991
+ function onPointerMove(event) {
2992
+ let drag = dragState;
2993
+ if (!drag)
2994
+ return;
2995
+ event.preventDefault(), event.stopPropagation();
2996
+ let dx = event.cellX - drag.startX, dy = event.cellY - drag.startY;
2997
+ !dragMoved && Math.abs(dx) + Math.abs(dy) >= 1 && (dragMoved = !0), dragMoved && (panX.value = drag.panStartX + dx, panY.value = drag.panStartY + dy, clampPan(), bump());
2998
+ }
2999
+ function onPointerUp(event) {
3000
+ dragState && (event.preventDefault(), event.stopPropagation(), dragState = null, dragMoved && (lastDragAt = Date.now()));
3001
+ }
3002
+ let copyNodeActive = (0, import_vue9.computed)(
2715
3003
  () => visible.value && props.copyButton && copyHitRect.value.w > 0 && copyHitRect.value.h > 0
2716
3004
  );
2717
3005
  useTerminalNode(() => ({
@@ -2725,8 +3013,8 @@ var tMermaidImageProps = {
2725
3013
  keydown: onCopyKeydown
2726
3014
  } : {}
2727
3015
  }));
2728
- let contentNodeActive = (0, import_vue10.computed)(
2729
- () => visible.value && props.copyOnClick && contentHitRect.value.w > 0 && contentHitRect.value.h > 0
3016
+ let contentNodeActive = (0, import_vue9.computed)(
3017
+ () => visible.value && (props.copyOnClick || zoomEnabled.value || dragEnabled.value) && contentHitRect.value.w > 0 && contentHitRect.value.h > 0
2730
3018
  );
2731
3019
  return useTerminalNode(() => ({
2732
3020
  rect: contentHitRect.value,
@@ -2735,7 +3023,11 @@ var tMermaidImageProps = {
2735
3023
  focusable: !1,
2736
3024
  selectable: !1,
2737
3025
  handlers: contentNodeActive.value ? {
2738
- click: onCopyClick
3026
+ click: props.copyOnClick ? onCopyClick : void 0,
3027
+ wheel: zoomEnabled.value ? onWheel : void 0,
3028
+ pointerdown: dragEnabled.value ? onPointerDown : void 0,
3029
+ pointermove: dragEnabled.value ? onPointerMove : void 0,
3030
+ pointerup: dragEnabled.value ? onPointerUp : void 0
2739
3031
  } : {}
2740
3032
  })), useRenderNode(() => ({
2741
3033
  zIndex: props.zIndex,
@@ -2746,6 +3038,11 @@ var tMermaidImageProps = {
2746
3038
  fullRect.value,
2747
3039
  imageCells.value,
2748
3040
  displayFit.value,
3041
+ zoomScale.value,
3042
+ panX.value,
3043
+ panY.value,
3044
+ textSnapshot.value,
3045
+ displayLines.value,
2749
3046
  sourceLines.value,
2750
3047
  currentStyle.value,
2751
3048
  props.clear,
@@ -2788,6 +3085,148 @@ var tMermaidImageProps = {
2788
3085
  paintRow(y);
2789
3086
  });
2790
3087
  }
2791
- })), () => (0, import_vue10.h)("span", rootProps);
3088
+ })), () => (0, import_vue9.h)("span", rootProps);
2792
3089
  }
2793
3090
  });
3091
+
3092
+ // src/vue/mermaid/beautiful-mermaid.ts
3093
+ 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;
3094
+ function functionValue(value) {
3095
+ return typeof value == "function" ? value : null;
3096
+ }
3097
+ function functionProp(target, key) {
3098
+ if (!target || typeof target != "object" || !(key in target))
3099
+ return null;
3100
+ let value = target[key];
3101
+ return typeof value == "function" ? value : null;
3102
+ }
3103
+ function errorMessage(error) {
3104
+ return error instanceof Error ? error.message : String(error);
3105
+ }
3106
+ function codedError(message, code) {
3107
+ let error = new Error(message);
3108
+ return error.code = code, error;
3109
+ }
3110
+ function missingBeautifulMermaidError(error) {
3111
+ if (error && typeof error == "object" && errorCode(error) === MISSING_BEAUTIFUL_MERMAID_ERROR_CODE)
3112
+ return markMermaidRenderErrorFatal(error);
3113
+ let detail = errorMessage(error);
3114
+ return markMermaidRenderErrorFatal(
3115
+ codedError(
3116
+ `${BEAUTIFUL_MERMAID_INSTALL_HINT} (${detail})`,
3117
+ MISSING_BEAUTIFUL_MERMAID_ERROR_CODE
3118
+ )
3119
+ );
3120
+ }
3121
+ function errorCode(error) {
3122
+ if (!error || typeof error != "object")
3123
+ return "";
3124
+ let value = error.code;
3125
+ return typeof value == "string" ? value : "";
3126
+ }
3127
+ function errorCause(error) {
3128
+ if (!(!error || typeof error != "object"))
3129
+ return error.cause;
3130
+ }
3131
+ function isMissingBeautifulMermaid(error) {
3132
+ return isMissingBeautifulMermaidWithSeen(error, /* @__PURE__ */ new WeakSet());
3133
+ }
3134
+ function isMissingBeautifulMermaidWithSeen(error, seen) {
3135
+ if (error && typeof error == "object") {
3136
+ if (seen.has(error))
3137
+ return !1;
3138
+ seen.add(error);
3139
+ }
3140
+ let message = errorMessage(error), code = errorCode(error);
3141
+ 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"))
3142
+ return !0;
3143
+ let cause = errorCause(error);
3144
+ return cause !== void 0 && isMissingBeautifulMermaidWithSeen(cause, seen);
3145
+ }
3146
+ function resolveBeautifulMermaidRenderer(mod) {
3147
+ let renderer = functionProp(mod, "renderMermaidASCII") ?? functionProp(mod, "renderMermaidAscii") ?? functionValue(mod.default) ?? functionProp(mod.default, "renderMermaidASCII") ?? functionProp(mod.default, "renderMermaidAscii");
3148
+ if (!renderer)
3149
+ throw markMermaidRenderErrorFatal(
3150
+ codedError(
3151
+ "beautiful-mermaid is installed but does not export renderMermaidASCII.",
3152
+ INVALID_BEAUTIFUL_MERMAID_EXPORT_ERROR_CODE
3153
+ )
3154
+ );
3155
+ return renderer;
3156
+ }
3157
+ function hasErrorCode(error, code, seen = /* @__PURE__ */ new WeakSet()) {
3158
+ if (!error || typeof error != "object" || seen.has(error))
3159
+ return !1;
3160
+ if (seen.add(error), errorCode(error) === code)
3161
+ return !0;
3162
+ let cause = errorCause(error);
3163
+ return cause !== void 0 && hasErrorCode(cause, code, seen);
3164
+ }
3165
+ function isBeautifulMermaidExportError(error) {
3166
+ return hasErrorCode(error, INVALID_BEAUTIFUL_MERMAID_EXPORT_ERROR_CODE);
3167
+ }
3168
+ var isTransientBeautifulMermaidRenderError = (error) => !(isMissingBeautifulMermaid(error) || isBeautifulMermaidExportError(error));
3169
+ async function loadBeautifulMermaid() {
3170
+ return cachedBeautifulMermaid || (cachedBeautifulMermaid = import("beautiful-mermaid").then((mod) => mod).catch((error) => {
3171
+ throw cachedBeautifulMermaid = null, isMissingBeautifulMermaid(error) ? missingBeautifulMermaidError(error) : markMermaidRenderErrorFatal(error);
3172
+ })), cachedBeautifulMermaid;
3173
+ }
3174
+ var beautifulMermaidRenderer = async (code, options) => {
3175
+ try {
3176
+ let mod = await loadBeautifulMermaid();
3177
+ return await resolveBeautifulMermaidRenderer(mod)(code, options);
3178
+ } catch (error) {
3179
+ throw isMissingBeautifulMermaid(error) ? missingBeautifulMermaidError(error) : error;
3180
+ }
3181
+ };
3182
+ function createBeautifulMermaidRenderer() {
3183
+ return beautifulMermaidRenderer;
3184
+ }
3185
+ var TMermaidText2 = (0, import_vue10.defineComponent)({
3186
+ name: "TMermaidText",
3187
+ inheritAttrs: !1,
3188
+ props: tMermaidTextProps,
3189
+ emits: {
3190
+ copy: (_payload) => !0
3191
+ },
3192
+ setup(props, { attrs, emit, slots }) {
3193
+ return () => {
3194
+ let renderer = props.renderer ?? beautifulMermaidRenderer, shouldRenderSource = props.shouldRenderSource ?? isSimpleMermaidFlowchartSource;
3195
+ return (0, import_vue10.h)(
3196
+ TMermaidText,
3197
+ {
3198
+ ...attrs,
3199
+ ...props,
3200
+ renderer,
3201
+ shouldRenderSource,
3202
+ isTransientError: props.isTransientError ?? isTransientBeautifulMermaidRenderError,
3203
+ onCopy: (payload) => emit("copy", payload)
3204
+ },
3205
+ slots
3206
+ );
3207
+ };
3208
+ }
3209
+ }), TMermaid = (0, import_vue10.defineComponent)({
3210
+ name: "TMermaid",
3211
+ inheritAttrs: !1,
3212
+ props: tMermaidImageProps,
3213
+ emits: {
3214
+ copy: (_payload) => !0
3215
+ },
3216
+ setup(props, { attrs, emit, slots }) {
3217
+ return () => {
3218
+ let { renderer: _legacyTextRenderer, ...rest } = props, textRenderer = props.textRenderer ?? props.renderer ?? beautifulMermaidRenderer;
3219
+ return (0, import_vue10.h)(
3220
+ TMermaidImage,
3221
+ {
3222
+ ...attrs,
3223
+ ...rest,
3224
+ textRenderer,
3225
+ shouldRenderSource: props.shouldRenderSource ?? isSimpleMermaidFlowchartSource,
3226
+ onCopy: (payload) => emit("copy", payload)
3227
+ },
3228
+ slots
3229
+ );
3230
+ };
3231
+ }
3232
+ }), TBeautifulMermaidText = TMermaidText2, TBeautifulMermaid = TMermaid;