react-super-mermaid 0.3.0 → 0.4.2

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
@@ -166,7 +166,7 @@ gitGraph
166
166
  - 🔍 **In-diagram search** — highlight + pan to matches (`/` or `Ctrl/Cmd+F`).
167
167
  - đŸ–ī¸ **Pan & zoom** — fit, actual size, keyboard `+ - 0 1 w` (via `svg-pan-zoom`).
168
168
  - â›ļ **Fullscreen modal** — open the diagram in a viewport-filling, RWD-friendly popup (`f` / `Esc`); body scroll locked, auto re-fit.
169
- - â–Ļ **Background toggle** — cycle the canvas between transparent / solid / dot-grid (`b`), handy for reading and exports.
169
+ - â–Ļ **Background picker** — a swatch popover to set the canvas **surface** (preset colors + a custom color well) and an independent **pattern** (none / dots / grid lines, cycle with `b`). Surface + pattern combine freely and carry through to exports.
170
170
  - 📤 **Export** — SVG and high-res PNG/JPEG/WebP (1×/2×/4×, optional transparent background).
171
171
  - đŸĒļ **Lightweight & decoupled** — `mermaid`, `svg-pan-zoom`, `react` are **optional peer deps**, never bundled. No Tailwind, no app coupling.
172
172
  - 🌐 **Load external mermaid** — inject an instance, dynamic-import the peer, or pull from a CDN. Your bundle doesn't carry mermaid.
@@ -248,8 +248,9 @@ Resolution order is **injected → peer import → CDN**, memoized so mermaid lo
248
248
  | `panZoom` | `boolean` | `true` | |
249
249
  | `search` | `boolean` | `true` | toolbar search |
250
250
  | `exportable` | `boolean` | `true` | toolbar SVG/PNG export |
251
- | `background` | `boolean` | `true` | toolbar background toggle (transparent / solid / grid) |
252
- | `backgroundMode` | `'transparent' \| 'solid' \| 'grid'` | `'transparent'` | initial / controlled canvas background |
251
+ | `background` | `boolean` | `true` | show the background picker (surface swatches + custom color + pattern toggle) |
252
+ | `pattern` | `'none' \| 'dots' \| 'grid'` | `'dots'` | initial / controlled overlay pattern |
253
+ | `solidColor` | `string \| null` | `null` (transparent) | initial / controlled canvas surface color (hex); `null` follows the page |
253
254
  | `fullscreen` | `boolean` | `true` | toolbar fullscreen button — opens a viewport-filling modal (RWD) |
254
255
  | `onFullscreenChange` | `(fullscreen: boolean) => void` | — | fired on enter / exit fullscreen |
255
256
  | `keyboard` | `boolean` | `true` | shortcuts when the viewer is focused |
@@ -281,7 +282,7 @@ await ref.current?.downloadPng('diagram.png', { scale: 4 });
281
282
  const svgString = ref.current?.exportSvg();
282
283
  ```
283
284
 
284
- Handle: `zoomIn / zoomOut / fit / reset / actualSize / getZoomPercent / search / next / prev / clearSearch / exportSvg / exportPng / downloadSvg / downloadPng / getSvg / enterFullscreen / exitFullscreen / toggleFullscreen / isFullscreen / setBackground / cycleBackground / getBackground`.
285
+ Handle: `zoomIn / zoomOut / fit / reset / actualSize / getZoomPercent / search / next / prev / clearSearch / exportSvg / exportPng / downloadSvg / downloadPng / getSvg / enterFullscreen / exitFullscreen / toggleFullscreen / isFullscreen / setPattern / cyclePattern / getPattern / setSolidColor / getSolidColor`.
285
286
 
286
287
  ## Themes & the sketch font
287
288
 
@@ -307,7 +308,7 @@ Styles are injected automatically (`injectStyles` default `true`) — no CSS imp
307
308
 
308
309
  ## Keyboard shortcuts
309
310
 
310
- Focus the viewer, then: `/` or `Ctrl/Cmd+F` search ¡ `+`/`-` zoom ¡ `0` fit ¡ `1` actual size ¡ `w` fit width ¡ `f` toggle fullscreen ¡ `b` cycle background ¡ `Esc` close search / exit fullscreen.
311
+ Focus the viewer, then: `/` or `Ctrl/Cmd+F` search ¡ `+`/`-` zoom ¡ `0` fit ¡ `1` actual size ¡ `w` fit width ¡ `f` toggle fullscreen ¡ `b` cycle background pattern (none / dots / grid) ¡ `Esc` close search / exit fullscreen.
311
312
 
312
313
  ## Next.js / SSR
313
314
 
package/dist/index.cjs CHANGED
@@ -107,8 +107,13 @@ var RSM_CSS = `
107
107
  --rsm-accent: #2563eb;
108
108
  --rsm-hover: #f3f4f6;
109
109
  --rsm-surface: #ffffff;
110
+ /* --rsm-paper = \u756B\u5E03\u5E95\u8272,\u5C0D\u9F4A VS Code \u64F4\u5145\u5957\u4EF6\u7684 editor-background(\u4EAE)\u3002 */
111
+ --rsm-paper: #ffffff;
110
112
  --rsm-canvas-bg: transparent;
111
- --rsm-grid-dot: rgba(0, 0, 0, 0.08);
113
+ /* \u9EDE\u9663\u683C\u7DDA:\u5C0D\u9F4A VS Code \u7684 color-mix(foreground 9%) \u516C\u5F0F\u3002 */
114
+ --rsm-grid-dot: color-mix(in srgb, var(--rsm-fg) 9%, transparent);
115
+ /* \u7DB2\u683C\u7DDA:\u6BD4\u7DB2\u9EDE\u518D\u6DE1\u4E00\u9EDE,\u907F\u514D\u7DDA\u689D\u6436\u904E\u5716\u8868\u3002 */
116
+ --rsm-grid-line: color-mix(in srgb, var(--rsm-fg) 7%, transparent);
112
117
  --rsm-radius: 8px;
113
118
  display: flex;
114
119
  flex-direction: column;
@@ -170,6 +175,14 @@ var RSM_CSS = `
170
175
  }
171
176
  .rsm-btn:disabled { opacity: 0.6; cursor: default; }
172
177
 
178
+ /* \u5DE5\u5177\u5217\u63A7\u5236\u9805\u9AD8\u5EA6\u4E00\u81F4(\u6A23\u5F0F\u4E0B\u62C9 / \u4E00\u822C\u9215 / \u7E2E\u653E\u7FA4 / \u80CC\u666F\u9215),\u907F\u514D\u9AD8\u4F4E\u4E0D\u9F4A\u3002 */
179
+ .rsm-toolbar .rsm-btn,
180
+ .rsm-toolbar .rsm-select,
181
+ .rsm-toolbar .rsm-zoom { min-height: 30px; }
182
+ .rsm-toolbar .rsm-btn,
183
+ .rsm-toolbar .rsm-select { align-items: center; }
184
+ .rsm-toolbar .rsm-zoom > button { display: inline-flex; align-items: center; justify-content: center; }
185
+
173
186
  .rsm-zoom {
174
187
  display: inline-flex;
175
188
  align-items: stretch;
@@ -215,7 +228,8 @@ var RSM_CSS = `
215
228
  flex: 1 1 auto;
216
229
  min-height: 0;
217
230
  overflow: hidden;
218
- background: var(--rsm-canvas-bg);
231
+ /* \u5E95\u8272\u7531 --rsm-canvas-bg \u63A7\u5236(\u9810\u8A2D\u900F\u660E,\u8DDF\u96A8\u9801\u9762);\u5716\u6A23\u758A\u5728\u5176\u4E0A(background-image)\u3002 */
232
+ background-color: var(--rsm-canvas-bg, transparent);
219
233
  }
220
234
  .rsm-stage { width: 100%; height: 100%; }
221
235
  .rsm-root svg { cursor: grab; user-select: none; }
@@ -240,23 +254,172 @@ var RSM_CSS = `
240
254
  .rsm-root .rsm-hit { filter: drop-shadow(0 0 5px #f59e0b) drop-shadow(0 0 1.5px #f59e0b); }
241
255
 
242
256
  .rsm-root.rsm-dark {
243
- --rsm-border: #374151;
244
- --rsm-fg: #e5e7eb;
245
- --rsm-muted: #9ca3af;
246
- --rsm-accent: #60a5fa;
247
- --rsm-hover: #1f2937;
248
- --rsm-surface: #111827;
249
- --rsm-grid-dot: rgba(255, 255, 255, 0.10);
257
+ /* \u6697\u8272\u9762\u677F\u5C0D\u9F4A VS Code Dark+ / Dark Modern \u7684\u4E2D\u6027\u7070(\u975E\u85CD\u8ABF)\u3002 */
258
+ --rsm-border: #3c3c3c;
259
+ --rsm-fg: #cccccc;
260
+ --rsm-muted: #9d9d9d;
261
+ --rsm-accent: #3794ff;
262
+ --rsm-hover: #2a2d2e;
263
+ --rsm-surface: #252526;
264
+ /* \u756B\u5E03\u5E95\u8272 = VS Code editor-background(\u6697);grid-dot \u7531 --rsm-fg 9% \u81EA\u52D5\u63A8\u5C0E\u3002 */
265
+ --rsm-paper: #1e1e1e;
250
266
  }
251
267
 
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);
268
+ /* \u2500\u2500 \u80CC\u666F \u2500\u2500 \u5E95\u8272 + \u758A\u52A0\u5716\u6A23,\u5169\u8005\u7368\u7ACB\u3002
269
+ * \u5E95\u8272:--rsm-canvas-bg(\u7531\u8272\u7968 / \u81EA\u8A02\u8272 inline \u8986\u5BEB;\u672A\u8A2D = \u900F\u660E\u8DDF\u96A8\u9801\u9762)\u3002
270
+ * \u5716\u6A23:.rsm-pattern-dots(\u7DB2\u9EDE) / .rsm-pattern-grid(\u7DB2\u683C\u7DDA),\u758A\u5728\u5E95\u8272\u4E4B\u4E0A\u3002 */
271
+ .rsm-root.rsm-pattern-dots .rsm-canvas {
256
272
  background-image: radial-gradient(var(--rsm-grid-dot) 1px, transparent 1px);
257
273
  background-size: 18px 18px;
258
274
  background-position: -9px -9px;
259
275
  }
276
+ .rsm-root.rsm-pattern-grid .rsm-canvas {
277
+ background-image:
278
+ linear-gradient(to right, var(--rsm-grid-line) 1px, transparent 1px),
279
+ linear-gradient(to bottom, var(--rsm-grid-line) 1px, transparent 1px);
280
+ background-size: 22px 22px;
281
+ background-position: -1px -1px;
282
+ }
283
+
284
+ /* \u2500\u2500 \u80CC\u666F\u9078\u64C7\u5668(toolbar \u5167\u7684\u8272\u4E95\u6309\u9215 + \u5F48\u51FA\u9762\u677F)\u2500\u2500 */
285
+ .rsm-bg { position: relative; display: inline-flex; }
286
+
287
+ /* \u89F8\u767C\u9215\u5DE6\u5074\u7684\u300C\u8272\u4E95\u300D:\u53CD\u6620\u76EE\u524D\u5E95\u8272;\u900F\u660E / \u9810\u8A2D\u6642\u756B\u4E00\u9053\u659C\u7DDA\u8868\u793A\u300C\u4E0D\u8986\u5BEB\u300D\u3002 */
288
+ .rsm-bg-well {
289
+ width: 16px;
290
+ height: 16px;
291
+ border-radius: 4px;
292
+ border: 1px solid color-mix(in srgb, var(--rsm-fg) 28%, transparent);
293
+ background-color: var(--rsm-well-color, transparent);
294
+ }
295
+ .rsm-bg-well[data-empty="true"] {
296
+ background-color: var(--rsm-surface);
297
+ background-image: linear-gradient(
298
+ to top right,
299
+ transparent calc(50% - 1px),
300
+ #ef4444 calc(50% - 1px),
301
+ #ef4444 calc(50% + 1px),
302
+ transparent calc(50% + 1px)
303
+ );
304
+ }
305
+
306
+ /* \u5F48\u51FA\u9762\u677F:\u5361\u7247\u5F0F\u3001\u8F15\u9670\u5F71\u3001\u6DE1\u5165\u3002 */
307
+ .rsm-bg-pop {
308
+ position: absolute;
309
+ top: calc(100% + 8px);
310
+ left: 0;
311
+ z-index: 50;
312
+ display: flex;
313
+ flex-direction: column;
314
+ gap: 12px;
315
+ padding: 12px;
316
+ min-width: 260px;
317
+ border: 1px solid var(--rsm-border);
318
+ border-radius: 12px;
319
+ background: var(--rsm-surface);
320
+ color: var(--rsm-fg);
321
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.16), 0 2px 6px rgba(0, 0, 0, 0.08);
322
+ animation: rsm-pop-in 0.13s ease-out;
323
+ }
324
+ @keyframes rsm-pop-in {
325
+ from { opacity: 0; transform: translateY(-5px); }
326
+ to { opacity: 1; transform: none; }
327
+ }
328
+ .rsm-bg-section { display: flex; flex-direction: column; gap: 8px; }
329
+ .rsm-bg-section-label {
330
+ font-size: 11px;
331
+ font-weight: 600;
332
+ letter-spacing: 0.03em;
333
+ color: var(--rsm-muted);
334
+ }
335
+ .rsm-bg-swatches { display: flex; flex-wrap: wrap; gap: 8px; }
336
+
337
+ /* \u8272\u7968:\u5713\u89D2\u5C0F\u65B9\u584A;\u9078\u4E2D\u52A0\u540C\u8272\u5916\u74B0\u3002 */
338
+ .rsm-swatch {
339
+ position: relative;
340
+ width: 26px;
341
+ height: 26px;
342
+ padding: 0;
343
+ border: 1px solid color-mix(in srgb, var(--rsm-fg) 16%, transparent);
344
+ border-radius: 7px;
345
+ cursor: pointer;
346
+ transition: transform 0.1s ease, box-shadow 0.1s ease;
347
+ }
348
+ .rsm-swatch:hover { transform: scale(1.12); }
349
+ .rsm-swatch:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--rsm-accent); }
350
+ .rsm-swatch.rsm-selected { outline: 2px solid var(--rsm-accent); outline-offset: 2px; }
351
+ .rsm-swatch[data-empty="true"] {
352
+ background-color: var(--rsm-surface);
353
+ background-image: linear-gradient(
354
+ to top right,
355
+ transparent calc(50% - 1px),
356
+ #ef4444 calc(50% - 1px),
357
+ #ef4444 calc(50% + 1px),
358
+ transparent calc(50% + 1px)
359
+ );
360
+ }
361
+
362
+ /* \u81EA\u8A02\u8272\u7968:\u8986\u4E00\u500B\u96B1\u5F62\u7684\u539F\u751F color input,\u672A\u9078\u6642\u986F\u793A \u{1F3A8}\u3002 */
363
+ .rsm-swatch-custom {
364
+ display: inline-flex;
365
+ align-items: center;
366
+ justify-content: center;
367
+ overflow: hidden;
368
+ background:
369
+ conic-gradient(from 180deg, #f87171, #fbbf24, #34d399, #60a5fa, #a78bfa, #f87171);
370
+ }
371
+ .rsm-swatch-custom.rsm-has-color { background: none; }
372
+ .rsm-swatch-custom input[type="color"] {
373
+ position: absolute;
374
+ inset: 0;
375
+ width: 100%;
376
+ height: 100%;
377
+ margin: 0;
378
+ padding: 0;
379
+ border: 0;
380
+ opacity: 0;
381
+ cursor: pointer;
382
+ }
383
+ .rsm-swatch-custom-icon {
384
+ font-size: 12px;
385
+ line-height: 1;
386
+ pointer-events: none;
387
+ filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.35));
388
+ }
389
+
390
+ /* \u5716\u6A23\u5207\u63DB:\u5206\u6BB5\u5F0F\u6309\u9215(\u7121 / \u7DB2\u9EDE / \u7DB2\u683C)\u3002 */
391
+ .rsm-seg {
392
+ display: inline-flex;
393
+ align-self: flex-start;
394
+ border: 1px solid var(--rsm-border);
395
+ border-radius: 8px;
396
+ overflow: hidden;
397
+ }
398
+ .rsm-seg > button {
399
+ flex: 1 1 0;
400
+ min-width: 58px;
401
+ display: inline-flex;
402
+ align-items: center;
403
+ justify-content: center;
404
+ gap: 5px;
405
+ border: 0;
406
+ background: var(--rsm-surface);
407
+ color: var(--rsm-fg);
408
+ padding: 6px 11px;
409
+ font-size: 12px;
410
+ line-height: 1.3;
411
+ white-space: nowrap;
412
+ cursor: pointer;
413
+ transition: background 0.1s ease, color 0.1s ease;
414
+ }
415
+ .rsm-seg > button + button { border-left: 1px solid var(--rsm-border); }
416
+ .rsm-seg > button:hover { background: var(--rsm-hover); }
417
+ .rsm-seg > button[aria-pressed="true"] {
418
+ background: color-mix(in srgb, var(--rsm-accent) 14%, transparent);
419
+ color: var(--rsm-accent);
420
+ font-weight: 600;
421
+ }
422
+ .rsm-seg-glyph { font-size: 13px; line-height: 1; }
260
423
 
261
424
  /* \u2500\u2500 \u5168\u87A2\u5E55\u8DF3\u7A97 \u2500\u2500 position:fixed \u8986\u84CB\u6574\u500B\u8996\u7A97,RWD \u53CB\u5584\u3002 */
262
425
  .rsm-root.rsm-fullscreen {
@@ -312,6 +475,25 @@ var RSM_CSS = `
312
475
  .rsm-zoom > button { padding: 4px 8px; font-size: 12px; }
313
476
  .rsm-input { flex-basis: 150px; }
314
477
  }
478
+
479
+ /* \u2500\u2500 \u6A19\u7C64\u5B57\u91CD(\u91CF\u6E2C\u968E\u6BB5\u5C31\u751F\u6548)\u2500\u2500
480
+ * 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
481
+ * \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
482
+ * \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
483
+ * \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
484
+ * \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
485
+ * \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
486
+ * \u53EA scope \u6211\u5011\u81EA\u5DF1\u6E32\u67D3\u51FA\u7684 svg,\u6545\u4E0D\u6703\u6C61\u67D3 host \u9801\u9762\u5176\u5B83 mermaid\u3002 */
487
+ svg[id^="rsm-"] g.node text,
488
+ svg[id^="rsm-"] g.node tspan,
489
+ svg[id^="rsm-"] g.node .nodeLabel,
490
+ svg[id^="rsm-"] g.mindmap-node text,
491
+ svg[id^="rsm-"] g.mindmap-node .nodeLabel,
492
+ svg[id^="rsm-"] g[class*="timeline-node"] text,
493
+ svg[id^="rsm-"] text.actor { font-weight: 600 !important; }
494
+ svg[id^="rsm-"] .cluster-label text,
495
+ svg[id^="rsm-"] .cluster-label .nodeLabel,
496
+ svg[id^="rsm-"] text.pieTitleText { font-weight: 700 !important; }
315
497
  `;
316
498
 
317
499
  // src/core/ensure-styles.ts
@@ -319,7 +501,11 @@ function ensureStyles() {
319
501
  if (typeof document === "undefined") {
320
502
  return;
321
503
  }
322
- if (document.getElementById(RSM_STYLE_ID)) {
504
+ const existing = document.getElementById(RSM_STYLE_ID);
505
+ if (existing) {
506
+ if (existing.textContent !== RSM_CSS) {
507
+ existing.textContent = RSM_CSS;
508
+ }
323
509
  return;
324
510
  }
325
511
  const style = document.createElement("style");
@@ -1430,7 +1616,7 @@ async function rasterizeToBlob(prepared, opts = {}) {
1430
1616
  throw new Error("\u53D6\u4E0D\u5230 Canvas 2D context\u3002");
1431
1617
  }
1432
1618
  if (!transparent || mime === "image/jpeg") {
1433
- ctx.fillStyle = opts.background ?? (opts.dark ? "#111827" : "#ffffff");
1619
+ ctx.fillStyle = opts.background ?? (opts.dark ? "#1e1e1e" : "#ffffff");
1434
1620
  ctx.fillRect(0, 0, canvas.width, canvas.height);
1435
1621
  }
1436
1622
  ctx.scale(scale, scale);
@@ -1671,11 +1857,19 @@ function useMermaidViewer(opts) {
1671
1857
  getSvg
1672
1858
  };
1673
1859
  }
1674
- var BACKGROUND_LABELS = {
1675
- transparent: { icon: "\u25A6", label: "\u900F\u660E" },
1676
- solid: { icon: "\u25FB", label: "\u7D14\u8272" },
1677
- grid: { icon: "\u229E", label: "\u683C\u7DDA" }
1678
- };
1860
+ var BACKGROUND_PRESETS = [
1861
+ { value: null, label: "\u9810\u8A2D / \u900F\u660E" },
1862
+ { value: "#FFFFFF", label: "\u767D" },
1863
+ { value: "#F3F4F6", label: "\u6DFA\u7070" },
1864
+ { value: "#EFF6FF", label: "\u6DFA\u85CD" },
1865
+ { value: "#FEFCE8", label: "\u6DFA\u9EC3" },
1866
+ { value: "#FDF2F8", label: "\u6DFA\u73AB\u7470" }
1867
+ ];
1868
+ var PATTERN_OPTIONS = [
1869
+ { value: "none", glyph: "\u25A2", label: "\u7121" },
1870
+ { value: "dots", glyph: "\u283F", label: "\u7DB2\u9EDE" },
1871
+ { value: "grid", glyph: "\u229E", label: "\u7DB2\u683C" }
1872
+ ];
1679
1873
  var DEFAULT_THEME_OPTIONS = [
1680
1874
  { value: "colorful", label: "Colorful" },
1681
1875
  { value: "sketch", label: "Excalidraw" },
@@ -1685,6 +1879,126 @@ var DEFAULT_THEME_OPTIONS = [
1685
1879
  { value: "neutral", label: "Neutral" },
1686
1880
  { value: "forest", label: "Forest" }
1687
1881
  ];
1882
+ var HEX6 = /^#[0-9a-fA-F]{6}$/;
1883
+ function isSameColor(a, b) {
1884
+ if (a === null || b === null) {
1885
+ return a === b;
1886
+ }
1887
+ return a.toLowerCase() === b.toLowerCase();
1888
+ }
1889
+ function BackgroundPicker(props) {
1890
+ const [open, setOpen] = react.useState(false);
1891
+ const rootRef = react.useRef(null);
1892
+ react.useEffect(() => {
1893
+ if (!open) {
1894
+ return void 0;
1895
+ }
1896
+ const onDocPointer = (e) => {
1897
+ if (rootRef.current && !rootRef.current.contains(e.target)) {
1898
+ setOpen(false);
1899
+ }
1900
+ };
1901
+ const onKey = (e) => {
1902
+ if (e.key === "Escape") {
1903
+ setOpen(false);
1904
+ }
1905
+ };
1906
+ document.addEventListener("mousedown", onDocPointer);
1907
+ document.addEventListener("keydown", onKey);
1908
+ return () => {
1909
+ document.removeEventListener("mousedown", onDocPointer);
1910
+ document.removeEventListener("keydown", onKey);
1911
+ };
1912
+ }, [open]);
1913
+ const isPreset = BACKGROUND_PRESETS.some((p) => isSameColor(p.value, props.surface));
1914
+ const customActive = props.surface !== null && !isPreset;
1915
+ const customInputValue = props.surface && HEX6.test(props.surface) ? props.surface : "#1e293b";
1916
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rsm-bg", ref: rootRef, children: [
1917
+ /* @__PURE__ */ jsxRuntime.jsxs(
1918
+ "button",
1919
+ {
1920
+ type: "button",
1921
+ className: "rsm-btn rsm-bg-trigger",
1922
+ "aria-expanded": open,
1923
+ "aria-haspopup": "dialog",
1924
+ onClick: () => setOpen((o) => !o),
1925
+ title: "\u756B\u5E03\u80CC\u666F\uFF08\u5E95\u8272 + \u7DB2\u9EDE / \u7DB2\u683C\uFF0CB\uFF09",
1926
+ children: [
1927
+ /* @__PURE__ */ jsxRuntime.jsx(
1928
+ "span",
1929
+ {
1930
+ className: "rsm-bg-well",
1931
+ "data-empty": props.surface === null ? "true" : void 0,
1932
+ style: props.surface ? { ["--rsm-well-color"]: props.surface } : void 0
1933
+ }
1934
+ ),
1935
+ "\u80CC\u666F"
1936
+ ]
1937
+ }
1938
+ ),
1939
+ open ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rsm-bg-pop", role: "dialog", "aria-label": "\u756B\u5E03\u80CC\u666F\u8A2D\u5B9A", children: [
1940
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rsm-bg-section", children: [
1941
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rsm-bg-section-label", children: "\u5E95\u8272" }),
1942
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rsm-bg-swatches", children: [
1943
+ BACKGROUND_PRESETS.map((preset) => {
1944
+ const selected = isSameColor(preset.value, props.surface);
1945
+ return /* @__PURE__ */ jsxRuntime.jsx(
1946
+ "button",
1947
+ {
1948
+ type: "button",
1949
+ className: `rsm-swatch${selected ? " rsm-selected" : ""}`,
1950
+ "data-empty": preset.value === null ? "true" : void 0,
1951
+ style: preset.value ? { backgroundColor: preset.value } : void 0,
1952
+ title: preset.label,
1953
+ "aria-label": `\u5E95\u8272\uFF1A${preset.label}`,
1954
+ "aria-pressed": selected,
1955
+ onClick: () => props.onSurfaceChange(preset.value)
1956
+ },
1957
+ preset.label
1958
+ );
1959
+ }),
1960
+ /* @__PURE__ */ jsxRuntime.jsxs(
1961
+ "span",
1962
+ {
1963
+ className: `rsm-swatch rsm-swatch-custom${customActive ? " rsm-has-color rsm-selected" : ""}`,
1964
+ style: customActive ? { backgroundColor: props.surface } : void 0,
1965
+ title: "\u81EA\u8A02\u984F\u8272",
1966
+ children: [
1967
+ !customActive ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rsm-swatch-custom-icon", children: "\u{1F3A8}" }) : null,
1968
+ /* @__PURE__ */ jsxRuntime.jsx(
1969
+ "input",
1970
+ {
1971
+ type: "color",
1972
+ value: customInputValue,
1973
+ "aria-label": "\u81EA\u8A02\u5E95\u8272",
1974
+ onChange: (e) => props.onSurfaceChange(e.target.value)
1975
+ }
1976
+ )
1977
+ ]
1978
+ }
1979
+ )
1980
+ ] })
1981
+ ] }),
1982
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rsm-bg-section", children: [
1983
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rsm-bg-section-label", children: "\u5716\u6A23" }),
1984
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rsm-seg", role: "group", "aria-label": "\u80CC\u666F\u5716\u6A23", children: PATTERN_OPTIONS.map((opt) => /* @__PURE__ */ jsxRuntime.jsxs(
1985
+ "button",
1986
+ {
1987
+ type: "button",
1988
+ "aria-pressed": props.pattern === opt.value,
1989
+ title: opt.label,
1990
+ onClick: () => props.onPatternChange(opt.value),
1991
+ children: [
1992
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rsm-seg-glyph", "aria-hidden": "true", children: opt.glyph }),
1993
+ opt.label
1994
+ ]
1995
+ },
1996
+ opt.value
1997
+ )) })
1998
+ ] })
1999
+ ] }) : null
2000
+ ] });
2001
+ }
1688
2002
  function Toolbar(props) {
1689
2003
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rsm-toolbar", children: [
1690
2004
  /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "rsm-label", children: [
@@ -1699,18 +2013,13 @@ function Toolbar(props) {
1699
2013
  }
1700
2014
  )
1701
2015
  ] }),
1702
- props.backgroundEnabled ? /* @__PURE__ */ jsxRuntime.jsxs(
1703
- "button",
2016
+ props.backgroundEnabled ? /* @__PURE__ */ jsxRuntime.jsx(
2017
+ BackgroundPicker,
1704
2018
  {
1705
- type: "button",
1706
- className: "rsm-btn",
1707
- onClick: props.onCycleBackground,
1708
- title: "\u5207\u63DB\u756B\u5E03\u80CC\u666F\uFF08\u900F\u660E / \u7D14\u8272 / \u683C\u7DDA\uFF0CB\uFF09",
1709
- children: [
1710
- BACKGROUND_LABELS[props.background].icon,
1711
- " \u80CC\u666F\uFF1A",
1712
- BACKGROUND_LABELS[props.background].label
1713
- ]
2019
+ surface: props.surface,
2020
+ onSurfaceChange: props.onSurfaceChange,
2021
+ pattern: props.pattern,
2022
+ onPatternChange: props.onPatternChange
1714
2023
  }
1715
2024
  ) : null,
1716
2025
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rsm-toolbar-spacer" }),
@@ -1780,7 +2089,24 @@ function Toolbar(props) {
1780
2089
  ) : null
1781
2090
  ] });
1782
2091
  }
1783
- var BACKGROUND_CYCLE = ["transparent", "solid", "grid"];
2092
+ var PATTERN_CYCLE = ["none", "dots", "grid"];
2093
+ function defaultSolidColor(dark) {
2094
+ return dark ? "#1e1e1e" : "#ffffff";
2095
+ }
2096
+ function patternInk(surface, dark) {
2097
+ const m = surface ? /^#([0-9a-fA-F]{6})$/.exec(surface) : null;
2098
+ let lightCanvas;
2099
+ if (m) {
2100
+ const r = parseInt(m[1].slice(0, 2), 16);
2101
+ const g = parseInt(m[1].slice(2, 4), 16);
2102
+ const b = parseInt(m[1].slice(4, 6), 16);
2103
+ lightCanvas = (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255 > 0.5;
2104
+ } else {
2105
+ lightCanvas = !dark;
2106
+ }
2107
+ const ink = lightCanvas ? "15, 23, 42" : "226, 232, 240";
2108
+ return { dot: `rgba(${ink}, 0.34)`, line: `rgba(${ink}, 0.2)` };
2109
+ }
1784
2110
  function usePrefersDark(explicit) {
1785
2111
  const [autoDark, setAutoDark] = react.useState(false);
1786
2112
  react.useEffect(() => {
@@ -1830,14 +2156,18 @@ var MermaidViewer = react.forwardRef(
1830
2156
  const [query, setQuery] = react.useState("");
1831
2157
  const [matchInfo, setMatchInfo] = react.useState({ current: 0, total: 0 });
1832
2158
  const [exporting, setExporting] = react.useState(false);
1833
- const [background, setBackgroundState] = react.useState(
1834
- props.backgroundMode ?? "transparent"
1835
- );
2159
+ const [pattern, setPatternState] = react.useState(props.pattern ?? "dots");
2160
+ react.useEffect(() => {
2161
+ if (props.pattern) {
2162
+ setPatternState(props.pattern);
2163
+ }
2164
+ }, [props.pattern]);
2165
+ const [solidColor, setSolidColorState] = react.useState(props.solidColor ?? null);
1836
2166
  react.useEffect(() => {
1837
- if (props.backgroundMode) {
1838
- setBackgroundState(props.backgroundMode);
2167
+ if (props.solidColor !== void 0) {
2168
+ setSolidColorState(props.solidColor);
1839
2169
  }
1840
- }, [props.backgroundMode]);
2170
+ }, [props.solidColor]);
1841
2171
  const [isFullscreen, setIsFullscreen] = react.useState(false);
1842
2172
  const rootRef = react.useRef(null);
1843
2173
  const searchInputRef = react.useRef(null);
@@ -1894,21 +2224,27 @@ var MermaidViewer = react.forwardRef(
1894
2224
  const exportPng = react.useCallback(async () => {
1895
2225
  setExporting(true);
1896
2226
  try {
1897
- await vm.downloadPng("diagram.png", { scale: 2 });
2227
+ const paper = solidColor ?? defaultSolidColor(dark);
2228
+ const transparent = solidColor === null && pattern === "none";
2229
+ const bgOpt = transparent ? { transparent: true } : { background: paper };
2230
+ await vm.downloadPng("diagram.png", { scale: 2, ...bgOpt });
1898
2231
  } catch (e) {
1899
2232
  onError?.(e instanceof Error ? e : new Error(String(e)));
1900
2233
  } finally {
1901
2234
  setExporting(false);
1902
2235
  }
1903
- }, [vm, onError]);
1904
- const cycleBackground = react.useCallback(() => {
1905
- setBackgroundState((prev) => {
1906
- const i = BACKGROUND_CYCLE.indexOf(prev);
1907
- return BACKGROUND_CYCLE[(i + 1) % BACKGROUND_CYCLE.length];
2236
+ }, [vm, onError, pattern, solidColor, dark]);
2237
+ const cyclePattern = react.useCallback(() => {
2238
+ setPatternState((prev) => {
2239
+ const i = PATTERN_CYCLE.indexOf(prev);
2240
+ return PATTERN_CYCLE[(i + 1) % PATTERN_CYCLE.length];
1908
2241
  });
1909
2242
  }, []);
1910
- const setBackground = react.useCallback((mode) => {
1911
- setBackgroundState(mode);
2243
+ const setPattern = react.useCallback((next) => {
2244
+ setPatternState(next);
2245
+ }, []);
2246
+ const setSolidColor = react.useCallback((color) => {
2247
+ setSolidColorState(color);
1912
2248
  }, []);
1913
2249
  const enterFullscreen = react.useCallback(() => {
1914
2250
  setIsFullscreen((prev) => {
@@ -2028,7 +2364,7 @@ var MermaidViewer = react.forwardRef(
2028
2364
  toggleFullscreen();
2029
2365
  } else if (backgroundEnabled && (e.key === "b" || e.key === "B")) {
2030
2366
  e.preventDefault();
2031
- cycleBackground();
2367
+ cyclePattern();
2032
2368
  }
2033
2369
  };
2034
2370
  root.addEventListener("keydown", onKey);
@@ -2042,7 +2378,7 @@ var MermaidViewer = react.forwardRef(
2042
2378
  isFullscreen,
2043
2379
  exitFullscreen,
2044
2380
  toggleFullscreen,
2045
- cycleBackground,
2381
+ cyclePattern,
2046
2382
  fullscreenEnabled,
2047
2383
  backgroundEnabled
2048
2384
  ]);
@@ -2068,9 +2404,11 @@ var MermaidViewer = react.forwardRef(
2068
2404
  exitFullscreen,
2069
2405
  toggleFullscreen,
2070
2406
  isFullscreen: () => isFullscreen,
2071
- setBackground,
2072
- cycleBackground,
2073
- getBackground: () => background
2407
+ setPattern,
2408
+ cyclePattern,
2409
+ getPattern: () => pattern,
2410
+ setSolidColor,
2411
+ getSolidColor: () => solidColor
2074
2412
  }),
2075
2413
  [
2076
2414
  vm,
@@ -2078,9 +2416,11 @@ var MermaidViewer = react.forwardRef(
2078
2416
  exitFullscreen,
2079
2417
  toggleFullscreen,
2080
2418
  isFullscreen,
2081
- setBackground,
2082
- cycleBackground,
2083
- background
2419
+ setPattern,
2420
+ cyclePattern,
2421
+ pattern,
2422
+ setSolidColor,
2423
+ solidColor
2084
2424
  ]
2085
2425
  );
2086
2426
  let countText = "";
@@ -2092,16 +2432,23 @@ var MermaidViewer = react.forwardRef(
2092
2432
  const rootClassName = [
2093
2433
  "rsm-root",
2094
2434
  dark ? "rsm-dark" : "",
2095
- `rsm-bg-${background}`,
2435
+ `rsm-pattern-${pattern}`,
2096
2436
  isFullscreen ? "rsm-fullscreen" : "",
2097
2437
  className ?? ""
2098
2438
  ].filter(Boolean).join(" ");
2439
+ const ink = patternInk(solidColor, dark);
2440
+ const rootStyle = {
2441
+ ...style ?? {},
2442
+ ["--rsm-grid-dot"]: ink.dot,
2443
+ ["--rsm-grid-line"]: ink.line,
2444
+ ...solidColor ? { ["--rsm-canvas-bg"]: solidColor } : {}
2445
+ };
2099
2446
  return /* @__PURE__ */ jsxRuntime.jsxs(
2100
2447
  "div",
2101
2448
  {
2102
2449
  ref: rootRef,
2103
2450
  className: rootClassName,
2104
- style,
2451
+ style: rootStyle,
2105
2452
  tabIndex: keyboard ? 0 : void 0,
2106
2453
  children: [
2107
2454
  toolbar ? /* @__PURE__ */ jsxRuntime.jsx(
@@ -2123,8 +2470,10 @@ var MermaidViewer = react.forwardRef(
2123
2470
  onExportSvg: exportSvg,
2124
2471
  onExportPng: exportPng,
2125
2472
  backgroundEnabled,
2126
- background,
2127
- onCycleBackground: cycleBackground,
2473
+ surface: solidColor,
2474
+ onSurfaceChange: setSolidColor,
2475
+ pattern,
2476
+ onPatternChange: setPattern,
2128
2477
  fullscreenEnabled,
2129
2478
  fullscreen: isFullscreen,
2130
2479
  onToggleFullscreen: toggleFullscreen
@@ -2191,10 +2540,12 @@ var MermaidDiagram = react.forwardRef(
2191
2540
  }
2192
2541
  );
2193
2542
 
2543
+ exports.BACKGROUND_PRESETS = BACKGROUND_PRESETS;
2194
2544
  exports.DEFAULT_THEME_OPTIONS = DEFAULT_THEME_OPTIONS;
2195
2545
  exports.DEFAULT_VIRGIL_FONT_URL = DEFAULT_VIRGIL_FONT_URL;
2196
2546
  exports.MermaidDiagram = MermaidDiagram;
2197
2547
  exports.MermaidViewer = MermaidViewer;
2548
+ exports.PATTERN_OPTIONS = PATTERN_OPTIONS;
2198
2549
  exports.SKETCH_FONT = SKETCH_FONT;
2199
2550
  exports.Toolbar = Toolbar;
2200
2551
  exports.boostLegibility = boostLegibility;