zombie-mermaid 1.3.0 → 1.4.0

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/README.md CHANGED
@@ -122,6 +122,7 @@ zombie-mermaid --help # Show all options
122
122
  - [Supported Diagrams](docs/diagrams.md) — syntax for every diagram type, XY chart styling, and ASCII rendering options
123
123
  - [React Integration](docs/react-integration.md) — zero-flash rendering with `useMemo()`
124
124
  - [API Reference](docs/api-reference.md) — full function and options reference
125
+ - [Migrating from `beautiful-mermaid`](docs/migrating-from-beautiful-mermaid.md) — what's drop-in, which fixes change rendered output, and how to audit your own diagrams before upgrading
125
126
 
126
127
  More design write-ups and reference material live in [docs/](docs/).
127
128
 
package/dist/cli.js CHANGED
@@ -1539,11 +1539,32 @@ function isSafeCssVarReference(font) {
1539
1539
  }
1540
1540
  return depth === 0;
1541
1541
  }
1542
+ var GENERIC_FONT_FAMILIES = /* @__PURE__ */ new Set([
1543
+ "sans-serif",
1544
+ "serif",
1545
+ "monospace",
1546
+ "system-ui",
1547
+ "ui-sans-serif",
1548
+ "ui-serif",
1549
+ "ui-monospace",
1550
+ "ui-rounded",
1551
+ "cursive",
1552
+ "fantasy",
1553
+ "math",
1554
+ "emoji",
1555
+ "fangsong"
1556
+ ]);
1557
+ function isFontStackOrGenericFamily(font) {
1558
+ const trimmed = font.trim();
1559
+ if (trimmed.includes(",")) return true;
1560
+ return GENERIC_FONT_FAMILIES.has(trimmed.toLowerCase());
1561
+ }
1542
1562
  function buildStyleBlock(font, hasMonoFont) {
1543
1563
  const isVarReference = isSafeCssVarReference(font);
1544
1564
  const safeFont = isVarReference ? font.trim() : font.replace(/[<>{};'"]/g, "");
1565
+ const skipImport = isVarReference || isFontStackOrGenericFamily(font);
1545
1566
  const fontImports = [
1546
- ...isVarReference ? [] : [
1567
+ ...skipImport ? [] : [
1547
1568
  `@import url('https://fonts.googleapis.com/css2?family=${encodeURIComponent(safeFont)}:wght@400;500;600;700&amp;display=swap');`
1548
1569
  ],
1549
1570
  ...hasMonoFont ? [
@@ -9211,9 +9232,14 @@ function pointsToPath(points, curve = "linear") {
9211
9232
  }
9212
9233
 
9213
9234
  // src/renderer.ts
9214
- function renderSvg(graph, colors, font = "Inter", transparent = false, fontSizes = FONT_SIZES, curve = "linear") {
9235
+ function renderSvg(graph, colors, font = "Inter", transparent = false, fontSizes = FONT_SIZES, curve = "linear", embedSource) {
9215
9236
  const parts = [];
9216
- parts.push(svgOpenTag(graph.width, graph.height, colors, transparent));
9237
+ parts.push(
9238
+ withDataSrc(
9239
+ svgOpenTag(graph.width, graph.height, colors, transparent),
9240
+ embedSource
9241
+ )
9242
+ );
9217
9243
  parts.push(buildStyleBlock(font, false));
9218
9244
  if (graph.edges.some((e) => e.animate)) {
9219
9245
  parts.push(edgeAnimationStyle());
@@ -9865,6 +9891,16 @@ function renderNodeLabel(node, _font, fontSizes) {
9865
9891
  function escapeAttr(value) {
9866
9892
  return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
9867
9893
  }
9894
+ function escapeMultilineAttr(value) {
9895
+ return escapeAttr(value).replace(/\r/g, "&#13;").replace(/\n/g, "&#10;").replace(/\t/g, "&#9;");
9896
+ }
9897
+ function withDataSrc(svgTag, source) {
9898
+ if (source === void 0) return svgTag;
9899
+ return svgTag.replace(
9900
+ "<svg ",
9901
+ `<svg data-src="${escapeMultilineAttr(source)}" `
9902
+ );
9903
+ }
9868
9904
  function sanitizeClassName(className) {
9869
9905
  if (!className) return void 0;
9870
9906
  return /^-?[a-zA-Z_][a-zA-Z0-9_-]*$/.test(className) ? className : void 0;
@@ -10243,9 +10279,14 @@ function layoutSequenceDiagram(diagram, options = {}) {
10243
10279
  }
10244
10280
 
10245
10281
  // src/sequence/renderer.ts
10246
- function renderSequenceSvg(diagram, colors, font = "Inter", transparent = false, fontSizes = FONT_SIZES) {
10282
+ function renderSequenceSvg(diagram, colors, font = "Inter", transparent = false, fontSizes = FONT_SIZES, embedSource) {
10247
10283
  const parts = [];
10248
- parts.push(svgOpenTag(diagram.width, diagram.height, colors, transparent));
10284
+ parts.push(
10285
+ withDataSrc(
10286
+ svgOpenTag(diagram.width, diagram.height, colors, transparent),
10287
+ embedSource
10288
+ )
10289
+ );
10249
10290
  parts.push(buildStyleBlock(font, false));
10250
10291
  parts.push("<defs>");
10251
10292
  parts.push(arrowMarkerDefs2());
@@ -10639,9 +10680,14 @@ var CLS_FONT = {
10639
10680
  annotationSize: 10,
10640
10681
  annotationWeight: 500
10641
10682
  };
10642
- function renderClassSvg(diagram, colors, font = "Inter", transparent = false, fontSizes = FONT_SIZES) {
10683
+ function renderClassSvg(diagram, colors, font = "Inter", transparent = false, fontSizes = FONT_SIZES, embedSource) {
10643
10684
  const parts = [];
10644
- parts.push(svgOpenTag(diagram.width, diagram.height, colors, transparent));
10685
+ parts.push(
10686
+ withDataSrc(
10687
+ svgOpenTag(diagram.width, diagram.height, colors, transparent),
10688
+ embedSource
10689
+ )
10690
+ );
10645
10691
  parts.push(buildStyleBlock(font, true));
10646
10692
  parts.push("<defs>");
10647
10693
  parts.push(relationshipMarkerDefs());
@@ -11022,9 +11068,14 @@ var ER_FONT = {
11022
11068
  keySize: 9,
11023
11069
  keyWeight: 600
11024
11070
  };
11025
- function renderErSvg(diagram, colors, font = "Inter", transparent = false, fontSizes = FONT_SIZES) {
11071
+ function renderErSvg(diagram, colors, font = "Inter", transparent = false, fontSizes = FONT_SIZES, embedSource) {
11026
11072
  const parts = [];
11027
- parts.push(svgOpenTag(diagram.width, diagram.height, colors, transparent));
11073
+ parts.push(
11074
+ withDataSrc(
11075
+ svgOpenTag(diagram.width, diagram.height, colors, transparent),
11076
+ embedSource
11077
+ )
11078
+ );
11028
11079
  parts.push(buildStyleBlock(font, true));
11029
11080
  parts.push("<defs>");
11030
11081
  parts.push("</defs>");
@@ -11756,19 +11807,20 @@ var TIP = {
11756
11807
  minY: 4,
11757
11808
  pointerSize: 6
11758
11809
  };
11759
- function renderXYChartSvg(chart, colors, font = "Inter", transparent = false, interactive = false) {
11810
+ function renderXYChartSvg(chart, colors, font = "Inter", transparent = false, interactive = false, embedSource) {
11760
11811
  const parts = [];
11761
11812
  const maxColorIdx = Math.max(
11762
11813
  0,
11763
11814
  ...chart.bars.map((b) => b.colorIndex),
11764
11815
  ...chart.lines.map((l) => l.colorIndex)
11765
11816
  );
11766
- const svgTag = svgOpenTag(
11767
- chart.width,
11768
- chart.height,
11769
- colors,
11770
- transparent
11771
- ).replace("<svg ", `<svg data-xychart-colors="${maxColorIdx}" `);
11817
+ const svgTag = withDataSrc(
11818
+ svgOpenTag(chart.width, chart.height, colors, transparent).replace(
11819
+ "<svg ",
11820
+ `<svg data-xychart-colors="${maxColorIdx}" `
11821
+ ),
11822
+ embedSource
11823
+ );
11772
11824
  parts.push(svgTag);
11773
11825
  parts.push(buildStyleBlock(font, false));
11774
11826
  const maxLinePoints = Math.max(...chart.lines.map((l) => l.points.length), 0);
@@ -12183,28 +12235,51 @@ function buildColors(options) {
12183
12235
  };
12184
12236
  }
12185
12237
  function renderMermaidSVG(text, options = {}) {
12238
+ const originalText = text;
12186
12239
  text = decodeXML(text);
12187
12240
  const colors = buildColors(options);
12188
12241
  const font = options.font ?? "Inter";
12189
12242
  const transparent = options.transparent ?? false;
12190
12243
  const fontSizes = resolveFontSizes(options.fontSizes);
12191
12244
  const diagramType = detectDiagramType(text);
12245
+ const embedSource = options.embedSource ? originalText : void 0;
12192
12246
  const lines = splitStatements(text);
12193
12247
  switch (diagramType) {
12194
12248
  case "sequence": {
12195
12249
  const diagram = parseSequenceDiagram(lines);
12196
12250
  const positioned = layoutSequenceDiagram(diagram, options);
12197
- return renderSequenceSvg(positioned, colors, font, transparent, fontSizes);
12251
+ return renderSequenceSvg(
12252
+ positioned,
12253
+ colors,
12254
+ font,
12255
+ transparent,
12256
+ fontSizes,
12257
+ embedSource
12258
+ );
12198
12259
  }
12199
12260
  case "class": {
12200
12261
  const diagram = parseClassDiagram(lines);
12201
12262
  const positioned = layoutClassDiagramSync(diagram, options);
12202
- return renderClassSvg(positioned, colors, font, transparent, fontSizes);
12263
+ return renderClassSvg(
12264
+ positioned,
12265
+ colors,
12266
+ font,
12267
+ transparent,
12268
+ fontSizes,
12269
+ embedSource
12270
+ );
12203
12271
  }
12204
12272
  case "er": {
12205
12273
  const diagram = parseErDiagram(lines);
12206
12274
  const positioned = layoutErDiagramSync(diagram, options);
12207
- return renderErSvg(positioned, colors, font, transparent, fontSizes);
12275
+ return renderErSvg(
12276
+ positioned,
12277
+ colors,
12278
+ font,
12279
+ transparent,
12280
+ fontSizes,
12281
+ embedSource
12282
+ );
12208
12283
  }
12209
12284
  case "xychart": {
12210
12285
  const chart = parseXYChart(lines);
@@ -12214,7 +12289,8 @@ function renderMermaidSVG(text, options = {}) {
12214
12289
  colors,
12215
12290
  font,
12216
12291
  transparent,
12217
- options.interactive ?? false
12292
+ options.interactive ?? false,
12293
+ embedSource
12218
12294
  );
12219
12295
  }
12220
12296
  case "flowchart":
@@ -12228,7 +12304,8 @@ function renderMermaidSVG(text, options = {}) {
12228
12304
  font,
12229
12305
  transparent,
12230
12306
  fontSizes,
12231
- effective.curve ?? "linear"
12307
+ effective.curve ?? "linear",
12308
+ embedSource
12232
12309
  );
12233
12310
  }
12234
12311
  }
package/dist/index.cjs CHANGED
@@ -205,11 +205,32 @@ function isSafeCssVarReference(font) {
205
205
  }
206
206
  return depth === 0;
207
207
  }
208
+ var GENERIC_FONT_FAMILIES = /* @__PURE__ */ new Set([
209
+ "sans-serif",
210
+ "serif",
211
+ "monospace",
212
+ "system-ui",
213
+ "ui-sans-serif",
214
+ "ui-serif",
215
+ "ui-monospace",
216
+ "ui-rounded",
217
+ "cursive",
218
+ "fantasy",
219
+ "math",
220
+ "emoji",
221
+ "fangsong"
222
+ ]);
223
+ function isFontStackOrGenericFamily(font) {
224
+ const trimmed = font.trim();
225
+ if (trimmed.includes(",")) return true;
226
+ return GENERIC_FONT_FAMILIES.has(trimmed.toLowerCase());
227
+ }
208
228
  function buildStyleBlock(font, hasMonoFont) {
209
229
  const isVarReference = isSafeCssVarReference(font);
210
230
  const safeFont = isVarReference ? font.trim() : font.replace(/[<>{};'"]/g, "");
231
+ const skipImport = isVarReference || isFontStackOrGenericFamily(font);
211
232
  const fontImports = [
212
- ...isVarReference ? [] : [
233
+ ...skipImport ? [] : [
213
234
  `@import url('https://fonts.googleapis.com/css2?family=${encodeURIComponent(safeFont)}:wght@400;500;600;700&amp;display=swap');`
214
235
  ],
215
236
  ...hasMonoFont ? [
@@ -9176,9 +9197,14 @@ function pointsToPath(points, curve = "linear") {
9176
9197
  }
9177
9198
 
9178
9199
  // src/renderer.ts
9179
- function renderSvg(graph, colors, font = "Inter", transparent = false, fontSizes = FONT_SIZES, curve = "linear") {
9200
+ function renderSvg(graph, colors, font = "Inter", transparent = false, fontSizes = FONT_SIZES, curve = "linear", embedSource) {
9180
9201
  const parts = [];
9181
- parts.push(svgOpenTag(graph.width, graph.height, colors, transparent));
9202
+ parts.push(
9203
+ withDataSrc(
9204
+ svgOpenTag(graph.width, graph.height, colors, transparent),
9205
+ embedSource
9206
+ )
9207
+ );
9182
9208
  parts.push(buildStyleBlock(font, false));
9183
9209
  if (graph.edges.some((e) => e.animate)) {
9184
9210
  parts.push(edgeAnimationStyle());
@@ -9830,6 +9856,16 @@ function renderNodeLabel(node, _font, fontSizes) {
9830
9856
  function escapeAttr(value) {
9831
9857
  return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
9832
9858
  }
9859
+ function escapeMultilineAttr(value) {
9860
+ return escapeAttr(value).replace(/\r/g, "&#13;").replace(/\n/g, "&#10;").replace(/\t/g, "&#9;");
9861
+ }
9862
+ function withDataSrc(svgTag, source) {
9863
+ if (source === void 0) return svgTag;
9864
+ return svgTag.replace(
9865
+ "<svg ",
9866
+ `<svg data-src="${escapeMultilineAttr(source)}" `
9867
+ );
9868
+ }
9833
9869
  function sanitizeClassName(className) {
9834
9870
  if (!className) return void 0;
9835
9871
  return /^-?[a-zA-Z_][a-zA-Z0-9_-]*$/.test(className) ? className : void 0;
@@ -10208,9 +10244,14 @@ function layoutSequenceDiagram(diagram, options = {}) {
10208
10244
  }
10209
10245
 
10210
10246
  // src/sequence/renderer.ts
10211
- function renderSequenceSvg(diagram, colors, font = "Inter", transparent = false, fontSizes = FONT_SIZES) {
10247
+ function renderSequenceSvg(diagram, colors, font = "Inter", transparent = false, fontSizes = FONT_SIZES, embedSource) {
10212
10248
  const parts = [];
10213
- parts.push(svgOpenTag(diagram.width, diagram.height, colors, transparent));
10249
+ parts.push(
10250
+ withDataSrc(
10251
+ svgOpenTag(diagram.width, diagram.height, colors, transparent),
10252
+ embedSource
10253
+ )
10254
+ );
10214
10255
  parts.push(buildStyleBlock(font, false));
10215
10256
  parts.push("<defs>");
10216
10257
  parts.push(arrowMarkerDefs2());
@@ -10604,9 +10645,14 @@ var CLS_FONT = {
10604
10645
  annotationSize: 10,
10605
10646
  annotationWeight: 500
10606
10647
  };
10607
- function renderClassSvg(diagram, colors, font = "Inter", transparent = false, fontSizes = FONT_SIZES) {
10648
+ function renderClassSvg(diagram, colors, font = "Inter", transparent = false, fontSizes = FONT_SIZES, embedSource) {
10608
10649
  const parts = [];
10609
- parts.push(svgOpenTag(diagram.width, diagram.height, colors, transparent));
10650
+ parts.push(
10651
+ withDataSrc(
10652
+ svgOpenTag(diagram.width, diagram.height, colors, transparent),
10653
+ embedSource
10654
+ )
10655
+ );
10610
10656
  parts.push(buildStyleBlock(font, true));
10611
10657
  parts.push("<defs>");
10612
10658
  parts.push(relationshipMarkerDefs());
@@ -10987,9 +11033,14 @@ var ER_FONT = {
10987
11033
  keySize: 9,
10988
11034
  keyWeight: 600
10989
11035
  };
10990
- function renderErSvg(diagram, colors, font = "Inter", transparent = false, fontSizes = FONT_SIZES) {
11036
+ function renderErSvg(diagram, colors, font = "Inter", transparent = false, fontSizes = FONT_SIZES, embedSource) {
10991
11037
  const parts = [];
10992
- parts.push(svgOpenTag(diagram.width, diagram.height, colors, transparent));
11038
+ parts.push(
11039
+ withDataSrc(
11040
+ svgOpenTag(diagram.width, diagram.height, colors, transparent),
11041
+ embedSource
11042
+ )
11043
+ );
10993
11044
  parts.push(buildStyleBlock(font, true));
10994
11045
  parts.push("<defs>");
10995
11046
  parts.push("</defs>");
@@ -11721,19 +11772,20 @@ var TIP = {
11721
11772
  minY: 4,
11722
11773
  pointerSize: 6
11723
11774
  };
11724
- function renderXYChartSvg(chart, colors, font = "Inter", transparent = false, interactive = false) {
11775
+ function renderXYChartSvg(chart, colors, font = "Inter", transparent = false, interactive = false, embedSource) {
11725
11776
  const parts = [];
11726
11777
  const maxColorIdx = Math.max(
11727
11778
  0,
11728
11779
  ...chart.bars.map((b) => b.colorIndex),
11729
11780
  ...chart.lines.map((l) => l.colorIndex)
11730
11781
  );
11731
- const svgTag = svgOpenTag(
11732
- chart.width,
11733
- chart.height,
11734
- colors,
11735
- transparent
11736
- ).replace("<svg ", `<svg data-xychart-colors="${maxColorIdx}" `);
11782
+ const svgTag = withDataSrc(
11783
+ svgOpenTag(chart.width, chart.height, colors, transparent).replace(
11784
+ "<svg ",
11785
+ `<svg data-xychart-colors="${maxColorIdx}" `
11786
+ ),
11787
+ embedSource
11788
+ );
11737
11789
  parts.push(svgTag);
11738
11790
  parts.push(buildStyleBlock(font, false));
11739
11791
  const maxLinePoints = Math.max(...chart.lines.map((l) => l.points.length), 0);
@@ -12148,28 +12200,51 @@ function buildColors(options) {
12148
12200
  };
12149
12201
  }
12150
12202
  function renderMermaidSVG(text, options = {}) {
12203
+ const originalText = text;
12151
12204
  text = (0, import_entities.decodeXML)(text);
12152
12205
  const colors = buildColors(options);
12153
12206
  const font = options.font ?? "Inter";
12154
12207
  const transparent = options.transparent ?? false;
12155
12208
  const fontSizes = resolveFontSizes(options.fontSizes);
12156
12209
  const diagramType = detectDiagramType(text);
12210
+ const embedSource = options.embedSource ? originalText : void 0;
12157
12211
  const lines = splitStatements(text);
12158
12212
  switch (diagramType) {
12159
12213
  case "sequence": {
12160
12214
  const diagram = parseSequenceDiagram(lines);
12161
12215
  const positioned = layoutSequenceDiagram(diagram, options);
12162
- return renderSequenceSvg(positioned, colors, font, transparent, fontSizes);
12216
+ return renderSequenceSvg(
12217
+ positioned,
12218
+ colors,
12219
+ font,
12220
+ transparent,
12221
+ fontSizes,
12222
+ embedSource
12223
+ );
12163
12224
  }
12164
12225
  case "class": {
12165
12226
  const diagram = parseClassDiagram(lines);
12166
12227
  const positioned = layoutClassDiagramSync(diagram, options);
12167
- return renderClassSvg(positioned, colors, font, transparent, fontSizes);
12228
+ return renderClassSvg(
12229
+ positioned,
12230
+ colors,
12231
+ font,
12232
+ transparent,
12233
+ fontSizes,
12234
+ embedSource
12235
+ );
12168
12236
  }
12169
12237
  case "er": {
12170
12238
  const diagram = parseErDiagram(lines);
12171
12239
  const positioned = layoutErDiagramSync(diagram, options);
12172
- return renderErSvg(positioned, colors, font, transparent, fontSizes);
12240
+ return renderErSvg(
12241
+ positioned,
12242
+ colors,
12243
+ font,
12244
+ transparent,
12245
+ fontSizes,
12246
+ embedSource
12247
+ );
12173
12248
  }
12174
12249
  case "xychart": {
12175
12250
  const chart = parseXYChart(lines);
@@ -12179,7 +12254,8 @@ function renderMermaidSVG(text, options = {}) {
12179
12254
  colors,
12180
12255
  font,
12181
12256
  transparent,
12182
- options.interactive ?? false
12257
+ options.interactive ?? false,
12258
+ embedSource
12183
12259
  );
12184
12260
  }
12185
12261
  case "flowchart":
@@ -12193,7 +12269,8 @@ function renderMermaidSVG(text, options = {}) {
12193
12269
  font,
12194
12270
  transparent,
12195
12271
  fontSizes,
12196
- effective.curve ?? "linear"
12272
+ effective.curve ?? "linear",
12273
+ embedSource
12197
12274
  );
12198
12275
  }
12199
12276
  }