react-super-mermaid 0.1.0 → 0.3.1

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/index.cjs CHANGED
@@ -108,6 +108,7 @@ var RSM_CSS = `
108
108
  --rsm-hover: #f3f4f6;
109
109
  --rsm-surface: #ffffff;
110
110
  --rsm-canvas-bg: transparent;
111
+ --rsm-grid-dot: rgba(0, 0, 0, 0.08);
111
112
  --rsm-radius: 8px;
112
113
  display: flex;
113
114
  flex-direction: column;
@@ -245,7 +246,91 @@ var RSM_CSS = `
245
246
  --rsm-accent: #60a5fa;
246
247
  --rsm-hover: #1f2937;
247
248
  --rsm-surface: #111827;
249
+ --rsm-grid-dot: rgba(255, 255, 255, 0.10);
248
250
  }
251
+
252
+ /* \u2500\u2500 \u80CC\u666F\u6A21\u5F0F \u2500\u2500 \u900F\u660E(\u9810\u8A2D,\u8DDF\u96A8\u9801\u9762) / \u7D14\u8272(surface) / \u9EDE\u9663\u683C\u7DDA\u3002 */
253
+ .rsm-root.rsm-bg-solid .rsm-canvas { background: var(--rsm-surface); }
254
+ .rsm-root.rsm-bg-grid .rsm-canvas {
255
+ background-color: var(--rsm-surface);
256
+ background-image: radial-gradient(var(--rsm-grid-dot) 1px, transparent 1px);
257
+ background-size: 18px 18px;
258
+ background-position: -9px -9px;
259
+ }
260
+
261
+ /* \u2500\u2500 \u5168\u87A2\u5E55\u8DF3\u7A97 \u2500\u2500 position:fixed \u8986\u84CB\u6574\u500B\u8996\u7A97,RWD \u53CB\u5584\u3002 */
262
+ .rsm-root.rsm-fullscreen {
263
+ position: fixed;
264
+ inset: 0;
265
+ width: 100vw;
266
+ width: 100dvw;
267
+ height: 100vh;
268
+ height: 100dvh;
269
+ max-width: 100vw;
270
+ max-height: 100dvh;
271
+ margin: 0;
272
+ z-index: 2147483000;
273
+ border: 0;
274
+ border-radius: 0;
275
+ animation: rsm-fs-in 0.16s ease-out;
276
+ }
277
+ @keyframes rsm-fs-in {
278
+ from { opacity: 0; }
279
+ to { opacity: 1; }
280
+ }
281
+
282
+ /* \u5168\u87A2\u5E55\u53F3\u4E0A\u89D2\u7684\u96E2\u958B\u9215(toolbar \u96B1\u85CF\u6642\u4E5F\u80FD\u95DC\u9589)\u3002 */
283
+ .rsm-fs-close {
284
+ position: absolute;
285
+ top: 10px;
286
+ right: 10px;
287
+ z-index: 5;
288
+ display: inline-flex;
289
+ align-items: center;
290
+ justify-content: center;
291
+ width: 34px;
292
+ height: 34px;
293
+ padding: 0;
294
+ font-size: 16px;
295
+ line-height: 1;
296
+ border: 1px solid var(--rsm-border);
297
+ border-radius: 8px;
298
+ background: var(--rsm-surface);
299
+ color: var(--rsm-fg);
300
+ cursor: pointer;
301
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
302
+ transition: background 0.12s ease, color 0.12s ease;
303
+ }
304
+ .rsm-fs-close:hover { background: var(--rsm-hover); }
305
+
306
+ /* \u2500\u2500 RWD \u2500\u2500 \u5C0F\u87A2\u5E55\u6536\u7DCA toolbar\u3001\u7E2E\u77ED\u641C\u5C0B\u6846,\u907F\u514D\u63DB\u884C\u64E0\u58D3\u756B\u5E03\u3002 */
307
+ @media (max-width: 640px) {
308
+ .rsm-toolbar { gap: 6px; padding: 6px 8px; }
309
+ .rsm-btn { padding: 4px 8px; font-size: 12px; }
310
+ .rsm-label { font-size: 11px; }
311
+ .rsm-select { padding: 3px 6px; font-size: 12px; }
312
+ .rsm-zoom > button { padding: 4px 8px; font-size: 12px; }
313
+ .rsm-input { flex-basis: 150px; }
314
+ }
315
+
316
+ /* \u2500\u2500 \u6A19\u7C64\u5B57\u91CD(\u91CF\u6E2C\u968E\u6BB5\u5C31\u751F\u6548)\u2500\u2500
317
+ * boostLegibility \u5728\u300C\u6E32\u67D3\u5F8C\u300D\u624D\u628A\u6A19\u7C64\u5B57\u91CD\u52A0\u5230 600/700,\u4F46 mermaid \u662F\u5728\u300C\u6E32\u67D3\u4E2D\u300D
318
+ * \u91CF\u6E2C\u6587\u5B57\u5BEC\u5EA6\u4F86\u6C7A\u5B9A foreignObject / \u7BC0\u9EDE\u5916\u6846\u7684\u5927\u5C0F\u3002\u82E5\u53EA\u5728\u4E8B\u5F8C\u52A0\u7C97,\u7C97\u9AD4\u5B57\u6703\u6BD4\u5DF2\u91CF\u597D\u7684
319
+ * \u6846\u66F4\u5BEC \u2192 foreignObject \u628A\u5C3E\u5B57\u88C1\u6389(\u5FC3\u667A\u5716\u7BC0\u9EDE\u300Creact-super-mermaid\u300D\u5C3E\u5DF4\u7684 d \u4E0D\u898B\u5C31\u662F\u9019\u500B)\u3002
320
+ * \u89E3\u6CD5:\u628A\u540C\u6A23\u7684\u5B57\u91CD\u7528 CSS \u63D0\u524D\u5BA3\u544A,\u4E14\u300C\u4E0D\u300Dscope \u5728 .rsm-root \u4E4B\u4E0B,\u800C\u662F scope \u5728 mermaid
321
+ * \u7684\u6E32\u67D3 id(svg[id^="rsm-"])\u2014\u2014\u56E0\u70BA\u91CF\u6E2C\u6642\u90A3\u9846\u66AB\u6642\u7684 svg \u9084\u5728 <body> \u4E0B\u3001\u5C1A\u672A\u639B\u9032 .rsm-root\u3002
322
+ * \u9019\u6A23 mermaid \u91CF\u5230\u7684\u5C31\u662F\u7C97\u9AD4\u5BEC\u5EA6,\u6846\u6703\u525B\u597D\u5BB9\u7D0D,\u4E8B\u5F8C boostLegibility \u8A2D\u540C\u503C\u4E0D\u518D\u6490\u7834\u3002
323
+ * \u53EA scope \u6211\u5011\u81EA\u5DF1\u6E32\u67D3\u51FA\u7684 svg,\u6545\u4E0D\u6703\u6C61\u67D3 host \u9801\u9762\u5176\u5B83 mermaid\u3002 */
324
+ svg[id^="rsm-"] g.node text,
325
+ svg[id^="rsm-"] g.node tspan,
326
+ svg[id^="rsm-"] g.node .nodeLabel,
327
+ svg[id^="rsm-"] g.mindmap-node text,
328
+ svg[id^="rsm-"] g.mindmap-node .nodeLabel,
329
+ svg[id^="rsm-"] g[class*="timeline-node"] text,
330
+ svg[id^="rsm-"] text.actor { font-weight: 600 !important; }
331
+ svg[id^="rsm-"] .cluster-label text,
332
+ svg[id^="rsm-"] .cluster-label .nodeLabel,
333
+ svg[id^="rsm-"] text.pieTitleText { font-weight: 700 !important; }
249
334
  `;
250
335
 
251
336
  // src/core/ensure-styles.ts
@@ -282,16 +367,88 @@ var NODE_PALETTE = [
282
367
  // violet
283
368
  ];
284
369
  var CLUSTER_PALETTE = [
285
- { fill: "rgba(59, 130, 246, 0.07)", stroke: "#93C5FD" },
286
- { fill: "rgba(34, 197, 94, 0.07)", stroke: "#86EFAC" },
287
- { fill: "rgba(249, 115, 22, 0.07)", stroke: "#FDBA74" },
288
- { fill: "rgba(168, 85, 247, 0.07)", stroke: "#D8B4FE" },
289
- { fill: "rgba(6, 182, 212, 0.07)", stroke: "#67E8F9" },
290
- { fill: "rgba(239, 68, 68, 0.07)", stroke: "#FCA5A5" }
370
+ { fill: "rgba(59, 130, 246, 0.16)", stroke: "#3B82F6" },
371
+ // blue
372
+ { fill: "rgba(34, 197, 94, 0.16)", stroke: "#22C55E" },
373
+ // green
374
+ { fill: "rgba(249, 115, 22, 0.16)", stroke: "#F97316" },
375
+ // orange
376
+ { fill: "rgba(168, 85, 247, 0.16)", stroke: "#A855F7" },
377
+ // purple
378
+ { fill: "rgba(239, 68, 68, 0.16)", stroke: "#EF4444" },
379
+ // red
380
+ { fill: "rgba(6, 182, 212, 0.16)", stroke: "#06B6D4" },
381
+ // cyan
382
+ { fill: "rgba(234, 179, 8, 0.16)", stroke: "#EAB308" },
383
+ // yellow
384
+ { fill: "rgba(139, 92, 246, 0.16)", stroke: "#8B5CF6" }
385
+ // violet
386
+ ];
387
+ var PIE_PALETTE = [
388
+ "#3B82F6",
389
+ // blue
390
+ "#22C55E",
391
+ // green
392
+ "#F59E0B",
393
+ // amber
394
+ "#A855F7",
395
+ // purple
396
+ "#EF4444",
397
+ // red
398
+ "#06B6D4",
399
+ // cyan
400
+ "#EC4899",
401
+ // pink
402
+ "#84CC16",
403
+ // lime
404
+ "#F97316",
405
+ // orange
406
+ "#14B8A6",
407
+ // teal
408
+ "#6366F1",
409
+ // indigo
410
+ "#EAB308"
411
+ // yellow
291
412
  ];
292
413
  var NODE_TEXT = "#1F2937";
293
414
  var SHADOW_FILTER_ID = "rsm-soft-shadow";
294
415
  var SVG_NS = "http://www.w3.org/2000/svg";
416
+ function resolveSvg(root) {
417
+ if (root instanceof Element && root.tagName.toLowerCase() === "svg") {
418
+ return root;
419
+ }
420
+ return root.querySelector("svg");
421
+ }
422
+ function canonColor(input) {
423
+ const s = (input || "").trim();
424
+ const hex = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(s);
425
+ if (hex) {
426
+ let h = hex[1];
427
+ if (h.length === 3) {
428
+ h = h.split("").map((c) => c + c).join("");
429
+ }
430
+ const n = parseInt(h, 16);
431
+ return `${n >> 16 & 255},${n >> 8 & 255},${n & 255}`;
432
+ }
433
+ const rgb = /rgba?\(([^)]+)\)/i.exec(s);
434
+ if (rgb) {
435
+ const p = rgb[1].split(",").map((x) => Math.round(parseFloat(x)));
436
+ return `${p[0]},${p[1]},${p[2]}`;
437
+ }
438
+ return s.toLowerCase();
439
+ }
440
+ function readableTextOn(color) {
441
+ const m = /^#?([0-9a-f]{6})$/i.exec(color.trim());
442
+ if (!m) {
443
+ return "#FFFFFF";
444
+ }
445
+ const n = parseInt(m[1], 16);
446
+ const r = n >> 16 & 255;
447
+ const g = n >> 8 & 255;
448
+ const b = n & 255;
449
+ const lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
450
+ return lum > 0.62 ? "#1F2937" : "#FFFFFF";
451
+ }
295
452
  function ensureShadowFilter(svg) {
296
453
  if (svg.querySelector(`#${SHADOW_FILTER_ID}`)) {
297
454
  return;
@@ -397,6 +554,17 @@ function styleEdgeLabels(svg) {
397
554
  rect.setAttribute("ry", "4");
398
555
  }
399
556
  }
557
+ function styleLabelText(svg, dark) {
558
+ const color = dark ? "#E2E8F0" : NODE_TEXT;
559
+ for (const t of Array.from(
560
+ svg.querySelectorAll("text.messageText, .edgeLabel text, .edgeLabel tspan")
561
+ )) {
562
+ t.style.fill = color;
563
+ }
564
+ for (const t of Array.from(svg.querySelectorAll(".edgeLabel span, .edgeLabel p"))) {
565
+ t.style.color = color;
566
+ }
567
+ }
400
568
  function colorizeLegacyEr(svg) {
401
569
  const erGroups = [];
402
570
  for (const rect of Array.from(svg.querySelectorAll("rect.er.entityBox"))) {
@@ -508,12 +676,194 @@ function colorizeSequence(svg, dark) {
508
676
  text.style.fill = dark ? "#E2E8F0" : NODE_TEXT;
509
677
  }
510
678
  }
679
+ function stylePie(svg, dark) {
680
+ const slices = Array.from(svg.querySelectorAll("path.pieCircle"));
681
+ const swatches = Array.from(svg.querySelectorAll("g.legend rect"));
682
+ if (slices.length === 0 && swatches.length === 0) {
683
+ return;
684
+ }
685
+ const remap = /* @__PURE__ */ new Map();
686
+ let next = 0;
687
+ const newColorFor = (old) => {
688
+ const key = canonColor(old) || `#slot-${next}`;
689
+ let c = remap.get(key);
690
+ if (!c) {
691
+ c = PIE_PALETTE[next % PIE_PALETTE.length];
692
+ remap.set(key, c);
693
+ next += 1;
694
+ }
695
+ return c;
696
+ };
697
+ for (const slice of slices) {
698
+ const old = slice.style.fill || slice.getAttribute("fill") || "";
699
+ const c = newColorFor(old);
700
+ slice.style.fill = c;
701
+ slice.style.opacity = "1";
702
+ slice.style.stroke = dark ? "#0F172A" : "#FFFFFF";
703
+ slice.style.strokeWidth = "2px";
704
+ slice.style.strokeLinejoin = "round";
705
+ }
706
+ for (const sw of swatches) {
707
+ const old = sw.style.fill || sw.getAttribute("fill") || "";
708
+ const c = newColorFor(old);
709
+ sw.style.fill = c;
710
+ sw.style.stroke = c;
711
+ sw.setAttribute("rx", "3");
712
+ sw.setAttribute("ry", "3");
713
+ }
714
+ Array.from(svg.querySelectorAll("text.slice")).forEach((label, i) => {
715
+ const slice = slices[i];
716
+ const c = slice ? slice.style.fill || PIE_PALETTE[0] : PIE_PALETTE[0];
717
+ label.style.fill = readableTextOn(c);
718
+ label.style.fontWeight = "600";
719
+ });
720
+ for (const title of Array.from(svg.querySelectorAll("text.pieTitleText"))) {
721
+ title.style.fontWeight = "700";
722
+ title.style.fill = dark ? "#E2E8F0" : "#1F2937";
723
+ }
724
+ for (const t of Array.from(svg.querySelectorAll("g.legend text"))) {
725
+ t.style.fill = dark ? "#E2E8F0" : "#1F2937";
726
+ }
727
+ for (const oc of Array.from(svg.querySelectorAll("circle.pieOuterCircle"))) {
728
+ oc.style.stroke = dark ? "#334155" : "#CBD5E1";
729
+ }
730
+ }
731
+ function styleGantt(svg, dark) {
732
+ const tasks = Array.from(svg.querySelectorAll("rect.task"));
733
+ if (tasks.length === 0) {
734
+ return;
735
+ }
736
+ for (const task of tasks) {
737
+ const cls = task.getAttribute("class") ?? "";
738
+ if (/\b(done|active|crit|milestone)\d*\b/.test(cls)) {
739
+ continue;
740
+ }
741
+ const m = cls.match(/task(\d+)/);
742
+ if (!m) {
743
+ continue;
744
+ }
745
+ const entry = NODE_PALETTE[Number(m[1]) % NODE_PALETTE.length];
746
+ task.style.fill = entry.fill;
747
+ task.style.stroke = entry.stroke;
748
+ task.setAttribute("rx", "4");
749
+ task.setAttribute("ry", "4");
750
+ }
751
+ Array.from(svg.querySelectorAll("rect.section")).forEach((band) => {
752
+ const m = (band.getAttribute("class") ?? "").match(/section(\d+)/);
753
+ if (m) {
754
+ band.style.fill = CLUSTER_PALETTE[Number(m[1]) % CLUSTER_PALETTE.length].fill;
755
+ }
756
+ });
757
+ for (const inBar of Array.from(svg.querySelectorAll("text.taskText"))) {
758
+ if (!/Outside/.test(inBar.getAttribute("class") ?? "")) {
759
+ inBar.style.fill = NODE_TEXT;
760
+ }
761
+ }
762
+ for (const tick of Array.from(svg.querySelectorAll("g.grid g.tick line"))) {
763
+ tick.style.stroke = dark ? "#334155" : "#E2E8F0";
764
+ }
765
+ }
766
+ function styleTimeline(svg) {
767
+ const nodes = Array.from(svg.querySelectorAll('g[class*="timeline-node"]'));
768
+ nodes.forEach((node, i) => {
769
+ const m = (node.getAttribute("class") ?? "").match(/section-(-?\d+)/);
770
+ const section = m ? Number(m[1]) : i;
771
+ const entry = section < 0 ? NODE_PALETTE[7] : NODE_PALETTE[section % NODE_PALETTE.length];
772
+ const backgrounds = Array.from(node.querySelectorAll(".node-bkg"));
773
+ if (backgrounds.length > 0) {
774
+ for (const bkg of backgrounds) {
775
+ bkg.style.fill = entry.fill;
776
+ bkg.style.stroke = entry.stroke;
777
+ bkg.style.strokeWidth = "1.4px";
778
+ }
779
+ } else {
780
+ paintShapes(node, entry);
781
+ }
782
+ darkenNodeText(node);
783
+ });
784
+ }
785
+ function styleMindmap(svg) {
786
+ const nodes = Array.from(svg.querySelectorAll("g.mindmap-node"));
787
+ if (nodes.length === 0) {
788
+ return;
789
+ }
790
+ for (const node of nodes) {
791
+ const m = (node.getAttribute("class") ?? "").match(/section-(-?\d+)/);
792
+ const section = m ? Number(m[1]) : 0;
793
+ const entry = section < 0 ? NODE_PALETTE[7] : NODE_PALETTE[section % NODE_PALETTE.length];
794
+ for (const shape of Array.from(
795
+ node.querySelectorAll("path, rect, circle, ellipse")
796
+ )) {
797
+ if (shape.closest("g.children")) {
798
+ continue;
799
+ }
800
+ shape.style.fill = entry.fill;
801
+ shape.style.stroke = entry.stroke;
802
+ shape.style.strokeWidth = "1.4px";
803
+ }
804
+ darkenNodeText(node);
805
+ }
806
+ for (const edge of Array.from(svg.querySelectorAll('path[class*="edge"]'))) {
807
+ const m = (edge.getAttribute("class") ?? "").match(/section-edge-(-?\d+)/);
808
+ if (m) {
809
+ const section = Number(m[1]);
810
+ const entry = section < 0 ? NODE_PALETTE[7] : NODE_PALETTE[section % NODE_PALETTE.length];
811
+ edge.style.stroke = entry.stroke;
812
+ edge.style.strokeWidth = "2px";
813
+ edge.style.opacity = "0.6";
814
+ edge.style.fill = "none";
815
+ }
816
+ }
817
+ }
818
+ function styleJourney(svg) {
819
+ const tasks = Array.from(
820
+ svg.querySelectorAll('circle[class*="task-type"], rect[class*="task-type"]')
821
+ );
822
+ tasks.forEach((shape) => {
823
+ const m = (shape.getAttribute("class") ?? "").match(/task-type-(\d+)/);
824
+ if (m) {
825
+ const entry = NODE_PALETTE[Number(m[1]) % NODE_PALETTE.length];
826
+ shape.style.fill = entry.fill;
827
+ shape.style.stroke = entry.stroke;
828
+ }
829
+ });
830
+ Array.from(svg.querySelectorAll('rect[class*="section-type"]')).forEach((rect) => {
831
+ const m = (rect.getAttribute("class") ?? "").match(/section-type-(\d+)/);
832
+ if (m) {
833
+ const entry = CLUSTER_PALETTE[Number(m[1]) % CLUSTER_PALETTE.length];
834
+ rect.style.fill = entry.fill;
835
+ rect.style.stroke = entry.stroke;
836
+ }
837
+ });
838
+ }
839
+ function boostLegibility(root) {
840
+ const svg = resolveSvg(root);
841
+ if (!svg) {
842
+ return;
843
+ }
844
+ for (const el of Array.from(
845
+ svg.querySelectorAll(
846
+ 'g.node text, g.node tspan, g.mindmap-node text, g[class*="timeline-node"] text, text.actor'
847
+ )
848
+ )) {
849
+ el.style.fontWeight = "600";
850
+ }
851
+ for (const el of Array.from(svg.querySelectorAll(".nodeLabel, g.node span, g.node p"))) {
852
+ el.style.fontWeight = "600";
853
+ }
854
+ for (const el of Array.from(svg.querySelectorAll("text"))) {
855
+ if (!el.style.fontWeight) {
856
+ el.style.fontWeight = "500";
857
+ }
858
+ }
859
+ }
511
860
  function colorizeDiagram(root, opts = {}) {
512
- const svg = root instanceof Element && root.tagName.toLowerCase() === "svg" ? root : root.querySelector("svg");
861
+ const svg = resolveSvg(root);
513
862
  if (!svg) {
514
863
  return;
515
864
  }
516
865
  ensureShadowFilter(svg);
866
+ const dark = opts.dark === true;
517
867
  Array.from(svg.querySelectorAll("g.node")).forEach((node, i) => {
518
868
  paintShapes(node, NODE_PALETTE[i % NODE_PALETTE.length]);
519
869
  darkenNodeText(node);
@@ -523,14 +873,41 @@ function colorizeDiagram(root, opts = {}) {
523
873
  for (const rect of Array.from(cluster.querySelectorAll(":scope > rect"))) {
524
874
  rect.style.fill = entry.fill;
525
875
  rect.style.stroke = entry.stroke;
526
- rect.style.strokeWidth = "1.2px";
876
+ rect.style.strokeWidth = "1.5px";
527
877
  roundRect(rect, 10);
528
878
  }
879
+ const label = cluster.querySelector(":scope > .cluster-label");
880
+ if (label) {
881
+ for (const el of Array.from(label.querySelectorAll("text, tspan"))) {
882
+ el.style.fill = entry.stroke;
883
+ el.style.fontWeight = "700";
884
+ }
885
+ for (const el of Array.from(label.querySelectorAll(".nodeLabel, span, p"))) {
886
+ el.style.color = entry.stroke;
887
+ el.style.fontWeight = "700";
888
+ }
889
+ for (const lr of Array.from(label.querySelectorAll("rect"))) {
890
+ lr.style.fill = entry.fill;
891
+ }
892
+ }
529
893
  });
530
894
  colorizeLegacyEr(svg);
531
- colorizeSequence(svg, opts.dark === true);
532
- styleEdges(svg, opts.dark === true);
895
+ colorizeSequence(svg, dark);
896
+ styleEdges(svg, dark);
533
897
  styleEdgeLabels(svg);
898
+ styleLabelText(svg, dark);
899
+ const kind = svg.getAttribute("aria-roledescription") ?? "";
900
+ if (kind === "pie" || kind === "pieChart") {
901
+ stylePie(svg, dark);
902
+ } else if (kind === "gantt") {
903
+ styleGantt(svg, dark);
904
+ } else if (kind === "timeline") {
905
+ styleTimeline(svg);
906
+ } else if (kind === "mindmap") {
907
+ styleMindmap(svg);
908
+ } else if (kind === "journey") {
909
+ styleJourney(svg);
910
+ }
534
911
  }
535
912
 
536
913
  // src/core/themes/sketch.ts
@@ -814,6 +1191,7 @@ function applyPostProcess(svg, postProcess, opts) {
814
1191
  } else if (postProcess === "sketch") {
815
1192
  sketchifyDiagram(svg, { dark: opts.dark, seed: opts.seed });
816
1193
  }
1194
+ boostLegibility(svg);
817
1195
  }
818
1196
  async function renderDiagram(opts) {
819
1197
  assertBrowser("renderDiagram");
@@ -1312,6 +1690,11 @@ function useMermaidViewer(opts) {
1312
1690
  getSvg
1313
1691
  };
1314
1692
  }
1693
+ var BACKGROUND_LABELS = {
1694
+ transparent: { icon: "\u25A6", label: "\u900F\u660E" },
1695
+ solid: { icon: "\u25FB", label: "\u7D14\u8272" },
1696
+ grid: { icon: "\u229E", label: "\u683C\u7DDA" }
1697
+ };
1315
1698
  var DEFAULT_THEME_OPTIONS = [
1316
1699
  { value: "colorful", label: "Colorful" },
1317
1700
  { value: "sketch", label: "Excalidraw" },
@@ -1335,6 +1718,20 @@ function Toolbar(props) {
1335
1718
  }
1336
1719
  )
1337
1720
  ] }),
1721
+ props.backgroundEnabled ? /* @__PURE__ */ jsxRuntime.jsxs(
1722
+ "button",
1723
+ {
1724
+ type: "button",
1725
+ className: "rsm-btn",
1726
+ onClick: props.onCycleBackground,
1727
+ title: "\u5207\u63DB\u756B\u5E03\u80CC\u666F\uFF08\u900F\u660E / \u7D14\u8272 / \u683C\u7DDA\uFF0CB\uFF09",
1728
+ children: [
1729
+ BACKGROUND_LABELS[props.background].icon,
1730
+ " \u80CC\u666F\uFF1A",
1731
+ BACKGROUND_LABELS[props.background].label
1732
+ ]
1733
+ }
1734
+ ) : null,
1338
1735
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rsm-toolbar-spacer" }),
1339
1736
  props.searchEnabled ? /* @__PURE__ */ jsxRuntime.jsx(
1340
1737
  "button",
@@ -1388,9 +1785,21 @@ function Toolbar(props) {
1388
1785
  ),
1389
1786
  /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: props.onZoomIn, title: "\u653E\u5927\uFF08+\uFF09", children: "\uFF0B" }),
1390
1787
  /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: props.onReset, title: "\u7B26\u5408\u8996\u7A97\uFF080\uFF09", children: "\u2922" })
1391
- ] })
1788
+ ] }),
1789
+ props.fullscreenEnabled ? /* @__PURE__ */ jsxRuntime.jsx(
1790
+ "button",
1791
+ {
1792
+ type: "button",
1793
+ className: "rsm-btn rsm-btn-fullscreen",
1794
+ "aria-pressed": props.fullscreen ? "true" : "false",
1795
+ onClick: props.onToggleFullscreen,
1796
+ title: props.fullscreen ? "\u96E2\u958B\u5168\u87A2\u5E55\uFF08Esc\uFF09" : "\u5168\u87A2\u5E55\u6AA2\u8996\uFF08F\uFF09",
1797
+ children: props.fullscreen ? "\u2715 \u96E2\u958B\u5168\u87A2\u5E55" : "\u26F6 \u5168\u87A2\u5E55"
1798
+ }
1799
+ ) : null
1392
1800
  ] });
1393
1801
  }
1802
+ var BACKGROUND_CYCLE = ["transparent", "solid", "grid"];
1394
1803
  function usePrefersDark(explicit) {
1395
1804
  const [autoDark, setAutoDark] = react.useState(false);
1396
1805
  react.useEffect(() => {
@@ -1414,6 +1823,9 @@ var MermaidViewer = react.forwardRef(
1414
1823
  panZoom = true,
1415
1824
  search: searchEnabled = true,
1416
1825
  exportable = true,
1826
+ background: backgroundEnabled = true,
1827
+ fullscreen: fullscreenEnabled = true,
1828
+ onFullscreenChange,
1417
1829
  keyboard = true,
1418
1830
  seed = 42,
1419
1831
  fontUrl,
@@ -1437,6 +1849,15 @@ var MermaidViewer = react.forwardRef(
1437
1849
  const [query, setQuery] = react.useState("");
1438
1850
  const [matchInfo, setMatchInfo] = react.useState({ current: 0, total: 0 });
1439
1851
  const [exporting, setExporting] = react.useState(false);
1852
+ const [background, setBackgroundState] = react.useState(
1853
+ props.backgroundMode ?? "transparent"
1854
+ );
1855
+ react.useEffect(() => {
1856
+ if (props.backgroundMode) {
1857
+ setBackgroundState(props.backgroundMode);
1858
+ }
1859
+ }, [props.backgroundMode]);
1860
+ const [isFullscreen, setIsFullscreen] = react.useState(false);
1440
1861
  const rootRef = react.useRef(null);
1441
1862
  const searchInputRef = react.useRef(null);
1442
1863
  const vm = useMermaidViewer({
@@ -1499,6 +1920,85 @@ var MermaidViewer = react.forwardRef(
1499
1920
  setExporting(false);
1500
1921
  }
1501
1922
  }, [vm, onError]);
1923
+ const cycleBackground = react.useCallback(() => {
1924
+ setBackgroundState((prev) => {
1925
+ const i = BACKGROUND_CYCLE.indexOf(prev);
1926
+ return BACKGROUND_CYCLE[(i + 1) % BACKGROUND_CYCLE.length];
1927
+ });
1928
+ }, []);
1929
+ const setBackground = react.useCallback((mode) => {
1930
+ setBackgroundState(mode);
1931
+ }, []);
1932
+ const enterFullscreen = react.useCallback(() => {
1933
+ setIsFullscreen((prev) => {
1934
+ if (!prev) {
1935
+ onFullscreenChange?.(true);
1936
+ }
1937
+ return true;
1938
+ });
1939
+ }, [onFullscreenChange]);
1940
+ const exitFullscreen = react.useCallback(() => {
1941
+ setIsFullscreen((prev) => {
1942
+ if (prev) {
1943
+ onFullscreenChange?.(false);
1944
+ }
1945
+ return false;
1946
+ });
1947
+ }, [onFullscreenChange]);
1948
+ const toggleFullscreen = react.useCallback(() => {
1949
+ setIsFullscreen((prev) => {
1950
+ onFullscreenChange?.(!prev);
1951
+ return !prev;
1952
+ });
1953
+ }, [onFullscreenChange]);
1954
+ react.useEffect(() => {
1955
+ if (!isFullscreen) {
1956
+ return void 0;
1957
+ }
1958
+ const body = typeof document !== "undefined" ? document.body : null;
1959
+ const prevOverflow = body?.style.overflow ?? "";
1960
+ if (body) {
1961
+ body.style.overflow = "hidden";
1962
+ }
1963
+ const onWinKey = (e) => {
1964
+ if (e.key === "Escape") {
1965
+ e.preventDefault();
1966
+ exitFullscreen();
1967
+ }
1968
+ };
1969
+ window.addEventListener("keydown", onWinKey);
1970
+ const fitId = window.setTimeout(() => vm.reset(), 60);
1971
+ let resizeId = 0;
1972
+ const onResize = () => {
1973
+ window.clearTimeout(resizeId);
1974
+ resizeId = window.setTimeout(() => vm.reset(), 150);
1975
+ };
1976
+ window.addEventListener("resize", onResize);
1977
+ window.addEventListener("orientationchange", onResize);
1978
+ rootRef.current?.focus();
1979
+ return () => {
1980
+ window.removeEventListener("keydown", onWinKey);
1981
+ window.removeEventListener("resize", onResize);
1982
+ window.removeEventListener("orientationchange", onResize);
1983
+ window.clearTimeout(fitId);
1984
+ window.clearTimeout(resizeId);
1985
+ if (body) {
1986
+ body.style.overflow = prevOverflow;
1987
+ }
1988
+ };
1989
+ }, [isFullscreen, exitFullscreen]);
1990
+ const fsMountedRef = react.useRef(false);
1991
+ react.useEffect(() => {
1992
+ if (!fsMountedRef.current) {
1993
+ fsMountedRef.current = true;
1994
+ return void 0;
1995
+ }
1996
+ if (isFullscreen) {
1997
+ return void 0;
1998
+ }
1999
+ const id = window.setTimeout(() => vm.reset(), 60);
2000
+ return () => window.clearTimeout(id);
2001
+ }, [isFullscreen]);
1502
2002
  react.useEffect(() => {
1503
2003
  if (!keyboard) {
1504
2004
  return void 0;
@@ -1524,6 +2024,11 @@ var MermaidViewer = react.forwardRef(
1524
2024
  closeSearch();
1525
2025
  return;
1526
2026
  }
2027
+ if (e.key === "Escape" && isFullscreen) {
2028
+ e.preventDefault();
2029
+ exitFullscreen();
2030
+ return;
2031
+ }
1527
2032
  if (typing) {
1528
2033
  return;
1529
2034
  }
@@ -1537,11 +2042,29 @@ var MermaidViewer = react.forwardRef(
1537
2042
  vm.actualSize();
1538
2043
  } else if (e.key === "w" || e.key === "W") {
1539
2044
  vm.fit();
2045
+ } else if (fullscreenEnabled && (e.key === "f" || e.key === "F")) {
2046
+ e.preventDefault();
2047
+ toggleFullscreen();
2048
+ } else if (backgroundEnabled && (e.key === "b" || e.key === "B")) {
2049
+ e.preventDefault();
2050
+ cycleBackground();
1540
2051
  }
1541
2052
  };
1542
2053
  root.addEventListener("keydown", onKey);
1543
2054
  return () => root.removeEventListener("keydown", onKey);
1544
- }, [keyboard, searchOpen, openSearch, closeSearch, vm]);
2055
+ }, [
2056
+ keyboard,
2057
+ searchOpen,
2058
+ openSearch,
2059
+ closeSearch,
2060
+ vm,
2061
+ isFullscreen,
2062
+ exitFullscreen,
2063
+ toggleFullscreen,
2064
+ cycleBackground,
2065
+ fullscreenEnabled,
2066
+ backgroundEnabled
2067
+ ]);
1545
2068
  react.useImperativeHandle(
1546
2069
  ref,
1547
2070
  () => ({
@@ -1559,9 +2082,25 @@ var MermaidViewer = react.forwardRef(
1559
2082
  exportPng: vm.exportPng,
1560
2083
  downloadSvg: vm.downloadSvg,
1561
2084
  downloadPng: vm.downloadPng,
1562
- getSvg: vm.getSvg
2085
+ getSvg: vm.getSvg,
2086
+ enterFullscreen,
2087
+ exitFullscreen,
2088
+ toggleFullscreen,
2089
+ isFullscreen: () => isFullscreen,
2090
+ setBackground,
2091
+ cycleBackground,
2092
+ getBackground: () => background
1563
2093
  }),
1564
- [vm]
2094
+ [
2095
+ vm,
2096
+ enterFullscreen,
2097
+ exitFullscreen,
2098
+ toggleFullscreen,
2099
+ isFullscreen,
2100
+ setBackground,
2101
+ cycleBackground,
2102
+ background
2103
+ ]
1565
2104
  );
1566
2105
  let countText = "";
1567
2106
  if (matchInfo.total > 0) {
@@ -1569,7 +2108,13 @@ var MermaidViewer = react.forwardRef(
1569
2108
  } else if (query.trim()) {
1570
2109
  countText = "0";
1571
2110
  }
1572
- const rootClassName = ["rsm-root", dark ? "rsm-dark" : "", className ?? ""].filter(Boolean).join(" ");
2111
+ const rootClassName = [
2112
+ "rsm-root",
2113
+ dark ? "rsm-dark" : "",
2114
+ `rsm-bg-${background}`,
2115
+ isFullscreen ? "rsm-fullscreen" : "",
2116
+ className ?? ""
2117
+ ].filter(Boolean).join(" ");
1573
2118
  return /* @__PURE__ */ jsxRuntime.jsxs(
1574
2119
  "div",
1575
2120
  {
@@ -1595,7 +2140,13 @@ var MermaidViewer = react.forwardRef(
1595
2140
  exportEnabled: exportable,
1596
2141
  exporting,
1597
2142
  onExportSvg: exportSvg,
1598
- onExportPng: exportPng
2143
+ onExportPng: exportPng,
2144
+ backgroundEnabled,
2145
+ background,
2146
+ onCycleBackground: cycleBackground,
2147
+ fullscreenEnabled,
2148
+ fullscreen: isFullscreen,
2149
+ onToggleFullscreen: toggleFullscreen
1599
2150
  }
1600
2151
  ) : null,
1601
2152
  toolbar && searchEnabled && searchOpen ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rsm-searchbar", children: [
@@ -1635,6 +2186,17 @@ var MermaidViewer = react.forwardRef(
1635
2186
  "\u5716\u8868\u8F09\u5165\u5931\u6557\uFF1A",
1636
2187
  vm.error
1637
2188
  ] }) : null,
2189
+ isFullscreen ? /* @__PURE__ */ jsxRuntime.jsx(
2190
+ "button",
2191
+ {
2192
+ type: "button",
2193
+ className: "rsm-fs-close",
2194
+ onClick: exitFullscreen,
2195
+ title: "\u96E2\u958B\u5168\u87A2\u5E55\uFF08Esc\uFF09",
2196
+ "aria-label": "\u96E2\u958B\u5168\u87A2\u5E55",
2197
+ children: "\u2715"
2198
+ }
2199
+ ) : null,
1638
2200
  /* @__PURE__ */ jsxRuntime.jsx("div", { ref: vm.stageRef, className: "rsm-stage" })
1639
2201
  ] })
1640
2202
  ]
@@ -1654,6 +2216,7 @@ exports.MermaidDiagram = MermaidDiagram;
1654
2216
  exports.MermaidViewer = MermaidViewer;
1655
2217
  exports.SKETCH_FONT = SKETCH_FONT;
1656
2218
  exports.Toolbar = Toolbar;
2219
+ exports.boostLegibility = boostLegibility;
1657
2220
  exports.colorizeDiagram = colorizeDiagram;
1658
2221
  exports.downloadBlob = downloadBlob;
1659
2222
  exports.ensureSketchFont = ensureSketchFont;