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.
This commit is contained in:
2026-05-22 20:34:30 +03:00
parent 49b6c5191e
commit 9f4929bffa

View File

@@ -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(/<svg\b[^>]*>/, (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(/<svg\b/, `<svg width="${w}" height="${h}"`);
});
}
@@ -33,7 +60,7 @@ export async function renderMermaid(code: string, theme: string): Promise<Render
const id = `mmx-render-${++renderSeq}`;
try {
const { svg } = await mermaid.render(id, code);
return { svg };
return { svg: pinSize(svg) };
} catch (e) {
return { error: e instanceof Error ? e.message : String(e) };
} finally {