From 9f4929bffa8bfed9d6e948be01444d818d20c995 Mon Sep 17 00:00:00 2001 From: Aleksey Shakhmatov Date: Fri, 22 May 2026 20:34:30 +0300 Subject: [PATCH] fix(preview): pin SVG to intrinsic size for all diagram types The previous fix only disabled useMaxWidth for flowcharts, so sequence (and other) diagrams still rendered responsively and fit shrank them to a tiny speck. Normalise every rendered SVG to its viewBox pixel size (stripping width:100% / max-width) in the render path, so fit/zoom is exact regardless of diagram type. Drops the now-redundant per-type flowchart flag. --- src/lib/mermaid.ts | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/src/lib/mermaid.ts b/src/lib/mermaid.ts index 025ebf3..ae3c24f 100644 --- a/src/lib/mermaid.ts +++ b/src/lib/mermaid.ts @@ -15,10 +15,37 @@ function configure(theme: string) { theme: theme as any, securityLevel: 'loose', fontFamily: 'inherit', - // Render at the diagram's intrinsic pixel size (not responsive 100% width) - // so the preview's own zoom/fit maths stay exact — otherwise a large - // flowchart is scaled down by the SVG *and* again by fit, ending up tiny. - flowchart: { useMaxWidth: false, htmlLabels: true }, + flowchart: { htmlLabels: true }, + }); +} + +/** + * Force an SVG to its intrinsic pixel size (from the viewBox), stripping the + * responsive `width:100%` / `max-width` that Mermaid emits by default. + * + * Mermaid's `useMaxWidth` makes diagrams shrink to their container, but the + * preview applies its own zoom/fit transform — so a responsive SVG ends up + * scaled down twice and renders tiny. Pinning the laid-out size to the viewBox + * keeps fit/zoom exact for *every* diagram type (sequence, class, …), which + * per-type `useMaxWidth` flags can't guarantee uniformly. + */ +function pinSize(svg: string): string { + const vb = svg.match(/viewBox="([\d.\s-]+)"/); + if (!vb) return svg; + const p = vb[1].trim().split(/\s+/).map(Number); + if (p.length !== 4 || p.some(Number.isNaN)) return svg; + const [, , w, h] = p; + return svg.replace(/]*>/, (tag) => { + let t = tag.replace(/\s(?:width|height)="[^"]*"/g, ''); + t = t.replace(/\sstyle="([^"]*)"/, (_m, s: string) => { + const cleaned = s + .split(';') + .map((d) => d.trim()) + .filter((d) => d && !/^(?:max-width|width)\s*:/i.test(d)) + .join('; '); + return cleaned ? ` style="${cleaned}"` : ''; + }); + return t.replace(/