react-super-mermaid 0.1.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/dist/index.js ADDED
@@ -0,0 +1,1651 @@
1
+ 'use client';
2
+ import { forwardRef, useState, useEffect, useRef, useCallback, useImperativeHandle, useMemo } from 'react';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+
5
+ // src/react/MermaidViewer.tsx
6
+
7
+ // src/env.ts
8
+ function isBrowser() {
9
+ return typeof document !== "undefined" && typeof window !== "undefined";
10
+ }
11
+ function assertBrowser(api) {
12
+ if (!isBrowser()) {
13
+ throw new Error(`[react-super-mermaid] ${api} \u9700\u8981\u700F\u89BD\u5668 DOM(document / window)\u3002`);
14
+ }
15
+ }
16
+
17
+ // src/core/load-mermaid.ts
18
+ var cached = null;
19
+ function loadMermaid(opts = {}) {
20
+ const source = opts.source ?? {};
21
+ if (source.instance) {
22
+ cached = Promise.resolve(normalize(source.instance));
23
+ return cached;
24
+ }
25
+ if (opts.fresh) {
26
+ cached = null;
27
+ }
28
+ if (cached) {
29
+ return cached;
30
+ }
31
+ cached = resolve(source).catch((err) => {
32
+ cached = null;
33
+ throw err;
34
+ });
35
+ return cached;
36
+ }
37
+ async function resolve(source) {
38
+ if (source.cdnUrl) {
39
+ assertBrowser("loadMermaid({ cdnUrl })");
40
+ const mod = await import(
41
+ /* @vite-ignore */
42
+ /* webpackIgnore: true */
43
+ source.cdnUrl
44
+ );
45
+ return normalize(mod.default ?? mod);
46
+ }
47
+ try {
48
+ const mod = await import('mermaid');
49
+ return normalize(mod.default ?? mod);
50
+ } catch (peerErr) {
51
+ throw new Error(
52
+ `[react-super-mermaid] \u627E\u4E0D\u5230 mermaid\u3002\u8ACB\u5B89\u88DD mermaid(peer dependency)\u3001\u50B3\u5165 { instance },\u6216\u50B3\u5165 { cdnUrl }\u3002
53
+ \u539F\u59CB\u932F\u8AA4:${peerErr instanceof Error ? peerErr.message : String(peerErr)}`
54
+ );
55
+ }
56
+ }
57
+ function normalize(m) {
58
+ if (typeof m.render !== "function" || typeof m.initialize !== "function") {
59
+ throw new Error("[react-super-mermaid] \u89E3\u6790\u5230\u7684\u7269\u4EF6\u4E0D\u662F\u6709\u6548\u7684 mermaid \u5BE6\u4F8B\u3002");
60
+ }
61
+ return m;
62
+ }
63
+
64
+ // src/core/load-svg-pan-zoom.ts
65
+ var cached2 = null;
66
+ function loadSvgPanZoom(source = {}) {
67
+ if (source.instance) {
68
+ cached2 = Promise.resolve(source.instance);
69
+ return cached2;
70
+ }
71
+ if (cached2) {
72
+ return cached2;
73
+ }
74
+ cached2 = resolve2(source).catch(() => {
75
+ cached2 = null;
76
+ return null;
77
+ });
78
+ return cached2;
79
+ }
80
+ async function resolve2(source) {
81
+ if (source.cdnUrl) {
82
+ assertBrowser("loadSvgPanZoom({ cdnUrl })");
83
+ const mod = await import(
84
+ /* @vite-ignore */
85
+ /* webpackIgnore: true */
86
+ source.cdnUrl
87
+ );
88
+ return mod.default ?? mod;
89
+ }
90
+ try {
91
+ const mod = await import('svg-pan-zoom');
92
+ return mod.default ?? mod;
93
+ } catch {
94
+ return null;
95
+ }
96
+ }
97
+
98
+ // src/core/styles.css.ts
99
+ var RSM_STYLE_ID = "react-super-mermaid-styles";
100
+ var RSM_CSS = `
101
+ .rsm-root {
102
+ --rsm-border: #e5e7eb;
103
+ --rsm-fg: #374151;
104
+ --rsm-muted: #6b7280;
105
+ --rsm-accent: #2563eb;
106
+ --rsm-hover: #f3f4f6;
107
+ --rsm-surface: #ffffff;
108
+ --rsm-canvas-bg: transparent;
109
+ --rsm-radius: 8px;
110
+ display: flex;
111
+ flex-direction: column;
112
+ height: 100%;
113
+ width: 100%;
114
+ box-sizing: border-box;
115
+ border: 1px solid var(--rsm-border);
116
+ border-radius: var(--rsm-radius);
117
+ background: var(--rsm-surface);
118
+ color: var(--rsm-fg);
119
+ font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
120
+ overflow: hidden;
121
+ }
122
+ .rsm-root *, .rsm-root *::before, .rsm-root *::after { box-sizing: border-box; }
123
+
124
+ .rsm-toolbar {
125
+ display: flex;
126
+ flex-wrap: wrap;
127
+ align-items: center;
128
+ gap: 8px;
129
+ padding: 8px 10px;
130
+ border-bottom: 1px solid var(--rsm-border);
131
+ }
132
+ .rsm-toolbar-spacer { margin-left: auto; }
133
+ .rsm-toolbar-group { display: inline-flex; align-items: center; gap: 6px; }
134
+
135
+ .rsm-label { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--rsm-muted); }
136
+
137
+ .rsm-select {
138
+ border: 1px solid var(--rsm-border);
139
+ background: var(--rsm-surface);
140
+ color: var(--rsm-fg);
141
+ border-radius: 6px;
142
+ padding: 4px 8px;
143
+ font-size: 13px;
144
+ cursor: pointer;
145
+ }
146
+ .rsm-select:focus { outline: none; border-color: var(--rsm-accent); }
147
+
148
+ .rsm-btn {
149
+ display: inline-flex;
150
+ align-items: center;
151
+ gap: 4px;
152
+ border: 1px solid var(--rsm-border);
153
+ background: var(--rsm-surface);
154
+ color: var(--rsm-fg);
155
+ border-radius: 6px;
156
+ padding: 4px 10px;
157
+ font-size: 13px;
158
+ line-height: 1.4;
159
+ cursor: pointer;
160
+ transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
161
+ }
162
+ .rsm-btn:hover { background: var(--rsm-hover); }
163
+ .rsm-btn[aria-pressed="true"] {
164
+ border-color: var(--rsm-accent);
165
+ color: var(--rsm-accent);
166
+ background: color-mix(in srgb, var(--rsm-accent) 10%, transparent);
167
+ }
168
+ .rsm-btn:disabled { opacity: 0.6; cursor: default; }
169
+
170
+ .rsm-zoom {
171
+ display: inline-flex;
172
+ align-items: stretch;
173
+ border: 1px solid var(--rsm-border);
174
+ border-radius: 6px;
175
+ overflow: hidden;
176
+ }
177
+ .rsm-zoom > button {
178
+ border: 0;
179
+ background: var(--rsm-surface);
180
+ color: var(--rsm-fg);
181
+ padding: 4px 10px;
182
+ font-size: 13px;
183
+ cursor: pointer;
184
+ }
185
+ .rsm-zoom > button:hover { background: var(--rsm-hover); }
186
+ .rsm-zoom > button + button { border-left: 1px solid var(--rsm-border); }
187
+ .rsm-zoom-percent { min-width: 52px; text-align: center; font-variant-numeric: tabular-nums; font-size: 12px; }
188
+
189
+ .rsm-searchbar {
190
+ display: flex;
191
+ align-items: center;
192
+ gap: 8px;
193
+ padding: 8px 10px;
194
+ border-bottom: 1px solid var(--rsm-border);
195
+ background: var(--rsm-hover);
196
+ }
197
+ .rsm-input {
198
+ flex: 0 1 320px;
199
+ border: 1px solid var(--rsm-border);
200
+ background: var(--rsm-surface);
201
+ color: var(--rsm-fg);
202
+ border-radius: 6px;
203
+ padding: 5px 10px;
204
+ font-size: 13px;
205
+ }
206
+ .rsm-input:focus { outline: none; border-color: var(--rsm-accent); }
207
+ .rsm-count { min-width: 48px; font-size: 12px; color: var(--rsm-muted); font-variant-numeric: tabular-nums; }
208
+ .rsm-searchbar-spacer { margin-left: auto; }
209
+
210
+ .rsm-canvas {
211
+ position: relative;
212
+ flex: 1 1 auto;
213
+ min-height: 0;
214
+ overflow: hidden;
215
+ background: var(--rsm-canvas-bg);
216
+ }
217
+ .rsm-stage { width: 100%; height: 100%; }
218
+ .rsm-root svg { cursor: grab; user-select: none; }
219
+ .rsm-root svg.rsm-grabbing { cursor: grabbing; }
220
+
221
+ .rsm-overlay {
222
+ position: absolute;
223
+ inset: 0;
224
+ display: flex;
225
+ align-items: center;
226
+ justify-content: center;
227
+ padding: 0 16px;
228
+ text-align: center;
229
+ font-size: 13px;
230
+ color: var(--rsm-muted);
231
+ pointer-events: none;
232
+ }
233
+ .rsm-overlay.rsm-error { color: #dc2626; }
234
+
235
+ /* \u641C\u5C0B:\u547D\u4E2D\u4EE5\u5916\u7684\u7BC0\u9EDE\u8B8A\u6697,\u7576\u524D\u547D\u4E2D\u52A0\u6696\u8272\u5149\u6688\u3002 */
236
+ .rsm-root .rsm-dim { opacity: 0.22; transition: opacity 0.15s ease; }
237
+ .rsm-root .rsm-hit { filter: drop-shadow(0 0 5px #f59e0b) drop-shadow(0 0 1.5px #f59e0b); }
238
+
239
+ .rsm-root.rsm-dark {
240
+ --rsm-border: #374151;
241
+ --rsm-fg: #e5e7eb;
242
+ --rsm-muted: #9ca3af;
243
+ --rsm-accent: #60a5fa;
244
+ --rsm-hover: #1f2937;
245
+ --rsm-surface: #111827;
246
+ }
247
+ `;
248
+
249
+ // src/core/ensure-styles.ts
250
+ function ensureStyles() {
251
+ if (typeof document === "undefined") {
252
+ return;
253
+ }
254
+ if (document.getElementById(RSM_STYLE_ID)) {
255
+ return;
256
+ }
257
+ const style = document.createElement("style");
258
+ style.id = RSM_STYLE_ID;
259
+ style.textContent = RSM_CSS;
260
+ document.head.appendChild(style);
261
+ }
262
+
263
+ // src/core/themes/colorize.ts
264
+ var NODE_PALETTE = [
265
+ { fill: "#DBEAFE", stroke: "#3B82F6" },
266
+ // blue
267
+ { fill: "#DCFCE7", stroke: "#22C55E" },
268
+ // green
269
+ { fill: "#FFEDD5", stroke: "#F97316" },
270
+ // orange
271
+ { fill: "#F3E8FF", stroke: "#A855F7" },
272
+ // purple
273
+ { fill: "#FEE2E2", stroke: "#EF4444" },
274
+ // red
275
+ { fill: "#CFFAFE", stroke: "#06B6D4" },
276
+ // cyan
277
+ { fill: "#FEF9C3", stroke: "#EAB308" },
278
+ // yellow
279
+ { fill: "#EDE9FE", stroke: "#8B5CF6" }
280
+ // violet
281
+ ];
282
+ var CLUSTER_PALETTE = [
283
+ { fill: "rgba(59, 130, 246, 0.07)", stroke: "#93C5FD" },
284
+ { fill: "rgba(34, 197, 94, 0.07)", stroke: "#86EFAC" },
285
+ { fill: "rgba(249, 115, 22, 0.07)", stroke: "#FDBA74" },
286
+ { fill: "rgba(168, 85, 247, 0.07)", stroke: "#D8B4FE" },
287
+ { fill: "rgba(6, 182, 212, 0.07)", stroke: "#67E8F9" },
288
+ { fill: "rgba(239, 68, 68, 0.07)", stroke: "#FCA5A5" }
289
+ ];
290
+ var NODE_TEXT = "#1F2937";
291
+ var SHADOW_FILTER_ID = "rsm-soft-shadow";
292
+ var SVG_NS = "http://www.w3.org/2000/svg";
293
+ function ensureShadowFilter(svg) {
294
+ if (svg.querySelector(`#${SHADOW_FILTER_ID}`)) {
295
+ return;
296
+ }
297
+ let defs = svg.querySelector(":scope > defs");
298
+ if (!defs) {
299
+ defs = document.createElementNS(SVG_NS, "defs");
300
+ svg.insertBefore(defs, svg.firstChild);
301
+ }
302
+ const filter = document.createElementNS(SVG_NS, "filter");
303
+ filter.setAttribute("id", SHADOW_FILTER_ID);
304
+ filter.setAttribute("x", "-25%");
305
+ filter.setAttribute("y", "-25%");
306
+ filter.setAttribute("width", "150%");
307
+ filter.setAttribute("height", "150%");
308
+ const drop = document.createElementNS(SVG_NS, "feDropShadow");
309
+ drop.setAttribute("dx", "0");
310
+ drop.setAttribute("dy", "1.5");
311
+ drop.setAttribute("stdDeviation", "2");
312
+ drop.setAttribute("flood-color", "#0F172A");
313
+ drop.setAttribute("flood-opacity", "0.22");
314
+ filter.appendChild(drop);
315
+ defs.appendChild(filter);
316
+ }
317
+ function roundRect(shape, radius) {
318
+ if (shape.tagName.toLowerCase() !== "rect") {
319
+ return;
320
+ }
321
+ const rx = Number(shape.getAttribute("rx") ?? "0");
322
+ if (rx < radius + 1) {
323
+ shape.setAttribute("rx", String(radius));
324
+ shape.setAttribute("ry", String(radius));
325
+ }
326
+ }
327
+ function paintShapes(group, entry) {
328
+ const direct = Array.from(
329
+ group.querySelectorAll(
330
+ ":scope > rect, :scope > polygon, :scope > circle, :scope > ellipse, :scope > path"
331
+ )
332
+ );
333
+ if (direct.length > 0) {
334
+ for (const shape of direct) {
335
+ shape.style.fill = entry.fill;
336
+ shape.style.stroke = entry.stroke;
337
+ shape.style.strokeWidth = "1.4px";
338
+ roundRect(shape, 8);
339
+ }
340
+ direct[0].setAttribute("filter", `url(#${SHADOW_FILTER_ID})`);
341
+ return;
342
+ }
343
+ for (const path of Array.from(group.querySelectorAll(":scope > g.outer-path > *"))) {
344
+ if (path.getAttribute("fill") && path.getAttribute("fill") !== "none") {
345
+ path.style.fill = entry.fill;
346
+ path.setAttribute("filter", `url(#${SHADOW_FILTER_ID})`);
347
+ }
348
+ if (path.getAttribute("stroke") && path.getAttribute("stroke") !== "none") {
349
+ path.style.stroke = entry.stroke;
350
+ path.style.strokeWidth = "1.4px";
351
+ }
352
+ }
353
+ for (const row of Array.from(group.querySelectorAll(":scope > g.row-rect-odd > *"))) {
354
+ row.style.fill = "rgba(255, 255, 255, 0.55)";
355
+ }
356
+ for (const row of Array.from(group.querySelectorAll(":scope > g.row-rect-even > *"))) {
357
+ row.style.fill = "rgba(255, 255, 255, 0.3)";
358
+ }
359
+ for (const divider of Array.from(group.querySelectorAll(":scope > g.divider > *"))) {
360
+ divider.style.stroke = entry.stroke;
361
+ }
362
+ }
363
+ function darkenNodeText(group) {
364
+ for (const el of Array.from(group.querySelectorAll("text, tspan"))) {
365
+ el.style.fill = NODE_TEXT;
366
+ }
367
+ for (const el of Array.from(group.querySelectorAll(".nodeLabel, span, p"))) {
368
+ el.style.color = NODE_TEXT;
369
+ }
370
+ }
371
+ function styleEdges(svg, dark) {
372
+ const edgeColor = dark ? "#94A3B8" : "#64748B";
373
+ const edgeSelectors = [
374
+ ".edgePaths path",
375
+ "g.edgePath path",
376
+ "path.flowchart-link",
377
+ "path.relationshipLine"
378
+ ].join(", ");
379
+ for (const edge of Array.from(svg.querySelectorAll(edgeSelectors))) {
380
+ edge.style.stroke = edgeColor;
381
+ edge.style.strokeWidth = "1.7px";
382
+ edge.style.strokeLinecap = "round";
383
+ }
384
+ for (const marker of Array.from(svg.querySelectorAll("marker path"))) {
385
+ marker.style.fill = edgeColor;
386
+ marker.style.stroke = edgeColor;
387
+ }
388
+ }
389
+ function styleEdgeLabels(svg) {
390
+ for (const label of Array.from(svg.querySelectorAll(".edgeLabel span, .edgeLabel p"))) {
391
+ label.style.borderRadius = "6px";
392
+ }
393
+ for (const rect of Array.from(svg.querySelectorAll(".edgeLabel rect"))) {
394
+ rect.setAttribute("rx", "4");
395
+ rect.setAttribute("ry", "4");
396
+ }
397
+ }
398
+ function colorizeLegacyEr(svg) {
399
+ const erGroups = [];
400
+ for (const rect of Array.from(svg.querySelectorAll("rect.er.entityBox"))) {
401
+ const group = rect.parentElement;
402
+ if (group && !erGroups.includes(group)) {
403
+ erGroups.push(group);
404
+ }
405
+ }
406
+ erGroups.forEach((group, i) => {
407
+ const entry = NODE_PALETTE[i % NODE_PALETTE.length];
408
+ for (const rect of Array.from(group.querySelectorAll("rect.er.entityBox")).slice(0, 1)) {
409
+ rect.style.fill = entry.fill;
410
+ rect.style.stroke = entry.stroke;
411
+ }
412
+ for (const label of Array.from(group.querySelectorAll("text.er.entityLabel"))) {
413
+ label.style.fill = NODE_TEXT;
414
+ }
415
+ });
416
+ }
417
+ function colorizeSequence(svg, dark) {
418
+ const actorRects = Array.from(svg.querySelectorAll("rect.actor"));
419
+ if (actorRects.length === 0) {
420
+ return;
421
+ }
422
+ const colorByName = /* @__PURE__ */ new Map();
423
+ let nextIndex = 0;
424
+ for (const rect of actorRects) {
425
+ const name = rect.getAttribute("name") ?? `#${nextIndex}`;
426
+ let entry = colorByName.get(name);
427
+ if (!entry) {
428
+ entry = NODE_PALETTE[nextIndex % NODE_PALETTE.length];
429
+ colorByName.set(name, entry);
430
+ nextIndex += 1;
431
+ }
432
+ rect.style.fill = entry.fill;
433
+ rect.style.stroke = entry.stroke;
434
+ rect.style.strokeWidth = "1.4px";
435
+ rect.setAttribute("rx", "8");
436
+ rect.setAttribute("ry", "8");
437
+ rect.setAttribute("filter", `url(#${SHADOW_FILTER_ID})`);
438
+ const group = rect.parentElement;
439
+ if (group) {
440
+ for (const text of Array.from(group.querySelectorAll("text, tspan"))) {
441
+ text.style.fill = NODE_TEXT;
442
+ }
443
+ }
444
+ }
445
+ const lanesByX = [];
446
+ for (const line of Array.from(svg.querySelectorAll("line.actor-line"))) {
447
+ const entry = colorByName.get(line.getAttribute("name") ?? "");
448
+ if (!entry) {
449
+ continue;
450
+ }
451
+ line.style.stroke = entry.stroke;
452
+ line.style.strokeOpacity = "0.45";
453
+ line.style.strokeWidth = "1.3px";
454
+ line.style.strokeDasharray = "4 5";
455
+ const x = Number(line.getAttribute("x1"));
456
+ if (Number.isFinite(x)) {
457
+ lanesByX.push({ x, entry });
458
+ }
459
+ }
460
+ const activations = Array.from(
461
+ svg.querySelectorAll("rect.activation0, rect.activation1, rect.activation2")
462
+ );
463
+ for (const bar of activations) {
464
+ const x = Number(bar.getAttribute("x"));
465
+ const w = Number(bar.getAttribute("width"));
466
+ if (!Number.isFinite(x) || !Number.isFinite(w) || lanesByX.length === 0) {
467
+ continue;
468
+ }
469
+ const center = x + w / 2;
470
+ let best = lanesByX[0];
471
+ for (const lane of lanesByX) {
472
+ if (Math.abs(lane.x - center) < Math.abs(best.x - center)) {
473
+ best = lane;
474
+ }
475
+ }
476
+ bar.style.fill = best.entry.fill;
477
+ bar.style.stroke = best.entry.stroke;
478
+ bar.style.strokeWidth = "1px";
479
+ bar.setAttribute("rx", "3");
480
+ bar.setAttribute("ry", "3");
481
+ }
482
+ for (const note of Array.from(svg.querySelectorAll("rect.note"))) {
483
+ note.style.fill = "#FEF9C3";
484
+ note.style.stroke = "#EAB308";
485
+ note.style.strokeWidth = "1.2px";
486
+ note.setAttribute("rx", "6");
487
+ note.setAttribute("ry", "6");
488
+ note.setAttribute("filter", `url(#${SHADOW_FILTER_ID})`);
489
+ }
490
+ for (const text of Array.from(svg.querySelectorAll(".noteText, .noteText tspan"))) {
491
+ text.style.fill = NODE_TEXT;
492
+ }
493
+ const ink = dark ? "#94A3B8" : "#64748B";
494
+ for (const line of Array.from(svg.querySelectorAll(".messageLine0, .messageLine1"))) {
495
+ line.style.stroke = ink;
496
+ line.style.strokeWidth = "1.6px";
497
+ line.style.strokeLinecap = "round";
498
+ }
499
+ for (const loop of Array.from(svg.querySelectorAll(".loopLine"))) {
500
+ loop.style.stroke = ink;
501
+ loop.style.strokeOpacity = "0.55";
502
+ }
503
+ for (const text of Array.from(
504
+ svg.querySelectorAll(".messageText, .loopText, .labelText")
505
+ )) {
506
+ text.style.fill = dark ? "#E2E8F0" : NODE_TEXT;
507
+ }
508
+ }
509
+ function colorizeDiagram(root, opts = {}) {
510
+ const svg = root instanceof Element && root.tagName.toLowerCase() === "svg" ? root : root.querySelector("svg");
511
+ if (!svg) {
512
+ return;
513
+ }
514
+ ensureShadowFilter(svg);
515
+ Array.from(svg.querySelectorAll("g.node")).forEach((node, i) => {
516
+ paintShapes(node, NODE_PALETTE[i % NODE_PALETTE.length]);
517
+ darkenNodeText(node);
518
+ });
519
+ Array.from(svg.querySelectorAll("g.cluster")).forEach((cluster, i) => {
520
+ const entry = CLUSTER_PALETTE[i % CLUSTER_PALETTE.length];
521
+ for (const rect of Array.from(cluster.querySelectorAll(":scope > rect"))) {
522
+ rect.style.fill = entry.fill;
523
+ rect.style.stroke = entry.stroke;
524
+ rect.style.strokeWidth = "1.2px";
525
+ roundRect(rect, 10);
526
+ }
527
+ });
528
+ colorizeLegacyEr(svg);
529
+ colorizeSequence(svg, opts.dark === true);
530
+ styleEdges(svg, opts.dark === true);
531
+ styleEdgeLabels(svg);
532
+ }
533
+
534
+ // src/core/themes/sketch.ts
535
+ var SVG_NS2 = "http://www.w3.org/2000/svg";
536
+ var SKETCH_FILTER_ID = "rsm-sketch-wobble";
537
+ var FONT_FACE_ID = "rsm-sketch-fontface";
538
+ var DEFAULT_SEED = 42;
539
+ var SKETCH_FONT = "'Virgil', 'KaiTi', 'Comic Sans MS', cursive";
540
+ var DEFAULT_VIRGIL_FONT_URL = "https://cdn.jsdelivr.net/npm/react-super-mermaid/dist/Virgil.woff2";
541
+ var ACTOR_PALETTE = [
542
+ { fill: "#a5d8ff", stroke: "#1971c2" },
543
+ // blue
544
+ { fill: "#b2f2bb", stroke: "#2f9e44" },
545
+ // green
546
+ { fill: "#ffd8a8", stroke: "#e8590c" },
547
+ // orange
548
+ { fill: "#d0bfff", stroke: "#6741d9" },
549
+ // violet
550
+ { fill: "#99e9f2", stroke: "#0c8599" },
551
+ // cyan
552
+ { fill: "#ffc9c9", stroke: "#e03131" },
553
+ // red
554
+ { fill: "#ffec99", stroke: "#f08c00" },
555
+ // yellow
556
+ { fill: "#eebefa", stroke: "#9c36b5" }
557
+ // grape
558
+ ];
559
+ var NOTE_FILL = "#ffec99";
560
+ var NOTE_STROKE = "#f08c00";
561
+ var INK_DARK = "#1e1e1e";
562
+ function isSequenceSvg(svg) {
563
+ return svg.getAttribute("aria-roledescription") === "sequence" || svg.querySelector(".actor, .messageLine0, .actor-line") !== null;
564
+ }
565
+ function ensureWobbleFilter(svg, seed) {
566
+ if (svg.querySelector(`#${SKETCH_FILTER_ID}`)) {
567
+ return;
568
+ }
569
+ let defs = svg.querySelector(":scope > defs");
570
+ if (!defs) {
571
+ defs = document.createElementNS(SVG_NS2, "defs");
572
+ svg.insertBefore(defs, svg.firstChild);
573
+ }
574
+ const filter = document.createElementNS(SVG_NS2, "filter");
575
+ filter.setAttribute("id", SKETCH_FILTER_ID);
576
+ filter.setAttribute("x", "-3%");
577
+ filter.setAttribute("y", "-3%");
578
+ filter.setAttribute("width", "106%");
579
+ filter.setAttribute("height", "106%");
580
+ const turbulence = document.createElementNS(SVG_NS2, "feTurbulence");
581
+ turbulence.setAttribute("type", "fractalNoise");
582
+ turbulence.setAttribute("baseFrequency", "0.012");
583
+ turbulence.setAttribute("numOctaves", "2");
584
+ turbulence.setAttribute("seed", String(seed));
585
+ turbulence.setAttribute("result", "noise");
586
+ const displace = document.createElementNS(SVG_NS2, "feDisplacementMap");
587
+ displace.setAttribute("in", "SourceGraphic");
588
+ displace.setAttribute("in2", "noise");
589
+ displace.setAttribute("scale", "2.2");
590
+ displace.setAttribute("xChannelSelector", "R");
591
+ displace.setAttribute("yChannelSelector", "G");
592
+ filter.appendChild(turbulence);
593
+ filter.appendChild(displace);
594
+ defs.appendChild(filter);
595
+ }
596
+ function wrapWithWobble(svg) {
597
+ if (svg.querySelector(":scope > g.rsm-sketch-layer")) {
598
+ return;
599
+ }
600
+ const layer = document.createElementNS(SVG_NS2, "g");
601
+ layer.setAttribute("class", "rsm-sketch-layer");
602
+ layer.style.filter = `url(#${SKETCH_FILTER_ID})`;
603
+ const movable = Array.from(svg.childNodes).filter((node) => {
604
+ if (node.nodeType !== Node.ELEMENT_NODE) {
605
+ return false;
606
+ }
607
+ const tag = node.tagName.toLowerCase();
608
+ return tag !== "defs" && tag !== "style";
609
+ });
610
+ for (const node of movable) {
611
+ layer.appendChild(node);
612
+ }
613
+ svg.appendChild(layer);
614
+ }
615
+ function applyHandwritingFont(svg) {
616
+ for (const text of Array.from(svg.querySelectorAll("text, tspan"))) {
617
+ text.style.fontFamily = SKETCH_FONT;
618
+ }
619
+ for (const html of Array.from(
620
+ svg.querySelectorAll(".messageText, .noteText, .loopText, .labelText, span, p")
621
+ )) {
622
+ html.style.fontFamily = SKETCH_FONT;
623
+ }
624
+ }
625
+ function colorizeActors(svg) {
626
+ const rects = Array.from(svg.querySelectorAll("rect.actor"));
627
+ if (rects.length === 0) {
628
+ return;
629
+ }
630
+ const colorByName = /* @__PURE__ */ new Map();
631
+ let nextIndex = 0;
632
+ for (const rect of rects) {
633
+ const name = rect.getAttribute("name") ?? `#${nextIndex}`;
634
+ let entry = colorByName.get(name);
635
+ if (!entry) {
636
+ entry = ACTOR_PALETTE[nextIndex % ACTOR_PALETTE.length];
637
+ colorByName.set(name, entry);
638
+ nextIndex += 1;
639
+ }
640
+ rect.style.fill = entry.fill;
641
+ rect.style.stroke = entry.stroke;
642
+ rect.style.strokeWidth = "2px";
643
+ rect.setAttribute("rx", "8");
644
+ rect.setAttribute("ry", "8");
645
+ const group = rect.parentElement;
646
+ if (group) {
647
+ for (const text of Array.from(group.querySelectorAll("text, tspan"))) {
648
+ text.style.fill = INK_DARK;
649
+ }
650
+ }
651
+ }
652
+ }
653
+ function colorizeNotes(svg) {
654
+ for (const note of Array.from(svg.querySelectorAll("rect.note"))) {
655
+ note.style.fill = NOTE_FILL;
656
+ note.style.stroke = NOTE_STROKE;
657
+ note.style.strokeWidth = "1.8px";
658
+ note.setAttribute("rx", "6");
659
+ note.setAttribute("ry", "6");
660
+ }
661
+ for (const text of Array.from(svg.querySelectorAll(".noteText, .noteText tspan"))) {
662
+ text.style.fill = INK_DARK;
663
+ }
664
+ }
665
+ function styleLines(svg, ink) {
666
+ for (const line of Array.from(svg.querySelectorAll(".messageLine0, .messageLine1"))) {
667
+ line.style.stroke = ink;
668
+ line.style.strokeWidth = "1.8px";
669
+ line.style.strokeLinecap = "round";
670
+ line.style.strokeLinejoin = "round";
671
+ }
672
+ for (const marker of Array.from(
673
+ svg.querySelectorAll("marker path, .arrowhead, path.arrowMarkerPath")
674
+ )) {
675
+ marker.style.fill = ink;
676
+ marker.style.stroke = ink;
677
+ }
678
+ for (const life of Array.from(svg.querySelectorAll(".actor-line"))) {
679
+ life.style.stroke = ink;
680
+ life.style.strokeOpacity = "0.3";
681
+ life.style.strokeDasharray = "2 5";
682
+ }
683
+ for (const loop of Array.from(svg.querySelectorAll(".loopLine"))) {
684
+ loop.style.stroke = ink;
685
+ loop.style.strokeOpacity = "0.5";
686
+ }
687
+ for (const box of Array.from(svg.querySelectorAll("polygon.labelBox"))) {
688
+ box.style.fill = NOTE_FILL;
689
+ box.style.stroke = ink;
690
+ box.style.strokeOpacity = "0.6";
691
+ }
692
+ for (const text of Array.from(
693
+ svg.querySelectorAll(".messageText, .loopText, .labelText")
694
+ )) {
695
+ text.style.fill = ink;
696
+ }
697
+ }
698
+ function sketchifyDiagram(root, opts = {}) {
699
+ const svg = root instanceof SVGSVGElement ? root : root.querySelector("svg");
700
+ if (!svg || !isSequenceSvg(svg)) {
701
+ return false;
702
+ }
703
+ const ink = opts.dark === true ? "#e9ecef" : INK_DARK;
704
+ colorizeActors(svg);
705
+ colorizeNotes(svg);
706
+ styleLines(svg, ink);
707
+ applyHandwritingFont(svg);
708
+ ensureWobbleFilter(svg, opts.seed ?? DEFAULT_SEED);
709
+ wrapWithWobble(svg);
710
+ return true;
711
+ }
712
+ var fontPromises = /* @__PURE__ */ new Map();
713
+ function ensureSketchFont(fontUrl = DEFAULT_VIRGIL_FONT_URL) {
714
+ if (typeof document === "undefined") {
715
+ return Promise.resolve();
716
+ }
717
+ const cached3 = fontPromises.get(fontUrl);
718
+ if (cached3) {
719
+ return cached3;
720
+ }
721
+ const promise = (async () => {
722
+ const styleId = `${FONT_FACE_ID}-${hashUrl(fontUrl)}`;
723
+ if (!document.getElementById(styleId)) {
724
+ const style = document.createElement("style");
725
+ style.id = styleId;
726
+ style.textContent = `@font-face{font-family:'Virgil';src:url("${fontUrl}") format('woff2');font-display:swap;}`;
727
+ document.head.appendChild(style);
728
+ }
729
+ try {
730
+ await document.fonts.load("16px 'Virgil'");
731
+ } catch {
732
+ }
733
+ })();
734
+ fontPromises.set(fontUrl, promise);
735
+ return promise;
736
+ }
737
+ function hashUrl(url) {
738
+ let hash = 0;
739
+ for (let i = 0; i < url.length; i += 1) {
740
+ hash = hash * 31 + url.charCodeAt(i) | 0;
741
+ }
742
+ return (hash >>> 0).toString(36);
743
+ }
744
+
745
+ // src/core/resolve-theme.ts
746
+ function resolveTheme(theme, dark) {
747
+ if (theme === "colorful") {
748
+ return { base: dark ? "dark" : "default", look: "classic", postProcess: "colorful" };
749
+ }
750
+ if (theme === "sketch") {
751
+ return { base: dark ? "dark" : "default", look: "handDrawn", postProcess: "sketch" };
752
+ }
753
+ if (theme === "auto") {
754
+ return { base: dark ? "dark" : "default", look: "classic", postProcess: "none" };
755
+ }
756
+ return { base: theme, look: "classic", postProcess: "none" };
757
+ }
758
+
759
+ // src/core/render-pipeline.ts
760
+ var renderSeq = 0;
761
+ function buildConfig(args) {
762
+ const resolved = resolveTheme(args.theme, args.dark);
763
+ const htmlLabels = args.pristine ? false : true;
764
+ const config = {
765
+ startOnLoad: false,
766
+ theme: resolved.base,
767
+ securityLevel: "loose",
768
+ htmlLabels,
769
+ flowchart: { htmlLabels, useMaxWidth: false }
770
+ };
771
+ if (args.theme === "colorful") {
772
+ config.flowchart = { htmlLabels, useMaxWidth: false, nodeSpacing: 60, rankSpacing: 65, padding: 12 };
773
+ }
774
+ if (resolved.look === "handDrawn") {
775
+ config.look = "handDrawn";
776
+ config.handDrawnSeed = args.seed;
777
+ config.fontFamily = SKETCH_FONT;
778
+ }
779
+ if (args.mermaidConfig) {
780
+ const userFlowchart = args.mermaidConfig.flowchart ?? {};
781
+ Object.assign(config, args.mermaidConfig);
782
+ config.flowchart = { ...config.flowchart, ...userFlowchart };
783
+ }
784
+ return config;
785
+ }
786
+ async function renderToSvg(args) {
787
+ const resolved = resolveTheme(args.theme, args.dark);
788
+ if (resolved.look === "handDrawn") {
789
+ await ensureSketchFont(args.fontUrl);
790
+ }
791
+ args.mermaid.initialize(buildConfig(args));
792
+ renderSeq += 1;
793
+ const id = `rsm-${renderSeq}`;
794
+ const { svg } = await args.mermaid.render(id, args.code);
795
+ return { svgString: svg, id, postProcess: args.pristine ? "none" : resolved.postProcess };
796
+ }
797
+ function mountSvg(host, svgString, postProcess, opts) {
798
+ host.innerHTML = svgString;
799
+ const svg = host.querySelector("svg");
800
+ if (!svg) {
801
+ return null;
802
+ }
803
+ applyPostProcess(svg, postProcess, opts);
804
+ svg.style.maxWidth = "none";
805
+ svg.style.width = "100%";
806
+ svg.style.height = "100%";
807
+ return svg;
808
+ }
809
+ function applyPostProcess(svg, postProcess, opts) {
810
+ if (postProcess === "colorful") {
811
+ colorizeDiagram(svg, { dark: opts.dark });
812
+ } else if (postProcess === "sketch") {
813
+ sketchifyDiagram(svg, { dark: opts.dark, seed: opts.seed });
814
+ }
815
+ }
816
+ async function renderDiagram(opts) {
817
+ assertBrowser("renderDiagram");
818
+ const dark = opts.dark ?? false;
819
+ const seed = opts.seed ?? 42;
820
+ if (opts.injectStyles !== false) {
821
+ ensureStyles();
822
+ }
823
+ const mermaid = await loadMermaid({ source: opts.mermaid });
824
+ const { svgString, id, postProcess } = await renderToSvg({
825
+ code: opts.code,
826
+ theme: opts.theme ?? "colorful",
827
+ dark,
828
+ seed,
829
+ fontUrl: opts.fontUrl,
830
+ mermaidConfig: opts.mermaidConfig,
831
+ mermaid
832
+ });
833
+ const host = typeof opts.container === "string" ? document.querySelector(opts.container) : opts.container ?? document.createElement("div");
834
+ if (!host) {
835
+ throw new Error(`[react-super-mermaid] \u627E\u4E0D\u5230 container:${String(opts.container)}`);
836
+ }
837
+ const svg = mountSvg(host, svgString, postProcess, { dark, seed });
838
+ if (!svg) {
839
+ throw new Error("[react-super-mermaid] mermaid \u672A\u8F38\u51FA SVG\u3002");
840
+ }
841
+ return { svg, svgString, id };
842
+ }
843
+
844
+ // src/core/pan-zoom.ts
845
+ function attachPanZoom(svg, factory, opts = {}) {
846
+ let baseZoom = 1;
847
+ const pz = factory(svg, {
848
+ zoomEnabled: true,
849
+ panEnabled: true,
850
+ controlIconsEnabled: false,
851
+ dblClickZoomEnabled: false,
852
+ fit: true,
853
+ center: true,
854
+ minZoom: opts.minZoom ?? 0.05,
855
+ maxZoom: opts.maxZoom ?? 40,
856
+ zoomScaleSensitivity: opts.zoomScaleSensitivity ?? 0.25,
857
+ onZoom: () => opts.onZoom?.()
858
+ });
859
+ baseZoom = pz.getZoom() || 1;
860
+ const controller = {
861
+ zoomIn: () => pz.zoomBy(1.25),
862
+ zoomOut: () => pz.zoomBy(0.8),
863
+ getZoomPercent: () => Math.round(pz.getZoom() / (baseZoom || 1) * 100),
864
+ actualSize: () => pz.zoom(baseZoom),
865
+ reset: () => {
866
+ pz.resize();
867
+ pz.fit();
868
+ pz.center();
869
+ baseZoom = pz.getZoom() || 1;
870
+ opts.onZoom?.();
871
+ },
872
+ fit: () => {
873
+ const sizes = pz.getSizes();
874
+ const targetReal = (sizes.width - 48) / sizes.viewBox.width;
875
+ pz.zoomBy(targetReal / sizes.realZoom);
876
+ opts.onZoom?.();
877
+ },
878
+ panToElement: (el) => {
879
+ const vp = svg.querySelector(".svg-pan-zoom_viewport");
880
+ const g = el;
881
+ if (!vp || typeof g.getBBox !== "function") {
882
+ return;
883
+ }
884
+ const vpCtm = vp.getCTM();
885
+ const elCtm = g.getCTM();
886
+ if (!vpCtm || !elCtm) {
887
+ return;
888
+ }
889
+ let bb;
890
+ try {
891
+ bb = g.getBBox();
892
+ } catch {
893
+ return;
894
+ }
895
+ const m = vpCtm.inverse().multiply(elCtm);
896
+ const c = new DOMPoint(bb.x + bb.width / 2, bb.y + bb.height / 2).matrixTransform(m);
897
+ const sizes = pz.getSizes();
898
+ pz.pan({
899
+ x: sizes.width / 2 - c.x * sizes.realZoom,
900
+ y: sizes.height / 2 - c.y * sizes.realZoom
901
+ });
902
+ },
903
+ capture: () => {
904
+ try {
905
+ return { zoom: pz.getZoom(), pan: pz.getPan() };
906
+ } catch {
907
+ return null;
908
+ }
909
+ },
910
+ restore: (view) => {
911
+ pz.zoom(view.zoom);
912
+ pz.pan(view.pan);
913
+ },
914
+ destroy: () => {
915
+ try {
916
+ pz.destroy();
917
+ } catch {
918
+ }
919
+ }
920
+ };
921
+ return controller;
922
+ }
923
+
924
+ // src/core/search.ts
925
+ var DIMMABLE_SELECTOR = "g.node, g.cluster, .actor";
926
+ var EMPTY = { current: 0, total: 0 };
927
+ function createSearch(getSvg, panTo) {
928
+ let matches = [];
929
+ let current = -1;
930
+ let query = "";
931
+ function clearHighlights() {
932
+ const svg = getSvg();
933
+ if (!svg) {
934
+ return;
935
+ }
936
+ for (const el of Array.from(svg.querySelectorAll(".rsm-dim, .rsm-hit"))) {
937
+ el.classList.remove("rsm-dim", "rsm-hit");
938
+ }
939
+ }
940
+ function setCurrent(i, pan) {
941
+ if (matches.length === 0) {
942
+ return EMPTY;
943
+ }
944
+ if (current >= 0) {
945
+ matches[current]?.classList.remove("rsm-hit");
946
+ }
947
+ const next = (i % matches.length + matches.length) % matches.length;
948
+ current = next;
949
+ const el = matches[next];
950
+ el.classList.add("rsm-hit");
951
+ if (pan) {
952
+ panTo?.(el);
953
+ }
954
+ return { current: next + 1, total: matches.length };
955
+ }
956
+ function run(term, pan) {
957
+ query = term;
958
+ clearHighlights();
959
+ matches = [];
960
+ current = -1;
961
+ const svg = getSvg();
962
+ const q = term.trim().toLowerCase();
963
+ if (!svg || !q) {
964
+ return EMPTY;
965
+ }
966
+ const seen = /* @__PURE__ */ new Set();
967
+ for (const textEl of Array.from(svg.querySelectorAll("text, .nodeLabel"))) {
968
+ if (!(textEl.textContent ?? "").toLowerCase().includes(q)) {
969
+ continue;
970
+ }
971
+ const target = textEl.closest(DIMMABLE_SELECTOR) ?? textEl;
972
+ if (!seen.has(target)) {
973
+ seen.add(target);
974
+ matches.push(target);
975
+ }
976
+ }
977
+ if (matches.length === 0) {
978
+ return EMPTY;
979
+ }
980
+ for (const el of Array.from(svg.querySelectorAll(DIMMABLE_SELECTOR))) {
981
+ const dimTarget = el.classList.contains("actor") ? el.parentElement ?? el : el;
982
+ dimTarget.classList.add("rsm-dim");
983
+ }
984
+ for (const match of matches) {
985
+ match.classList.remove("rsm-dim");
986
+ }
987
+ return setCurrent(0, pan);
988
+ }
989
+ return {
990
+ search: (term, pan = true) => run(term, pan),
991
+ next: (pan = true) => setCurrent(current + 1, pan),
992
+ prev: (pan = true) => setCurrent(current - 1, pan),
993
+ rerun: (pan = false) => query.trim() ? run(query, pan) : EMPTY,
994
+ clear: () => {
995
+ clearHighlights();
996
+ matches = [];
997
+ current = -1;
998
+ query = "";
999
+ },
1000
+ getQuery: () => query
1001
+ };
1002
+ }
1003
+
1004
+ // src/core/export.ts
1005
+ var RASTER_MIME = {
1006
+ png: "image/png",
1007
+ jpeg: "image/jpeg",
1008
+ webp: "image/webp"
1009
+ };
1010
+ function sizeFromViewBox(svgEl) {
1011
+ const viewBox = (svgEl.getAttribute("viewBox") ?? "0 0 800 600").split(/[\s,]+/).map(Number);
1012
+ return {
1013
+ width: Math.max(1, Math.ceil(viewBox[2] || 800)),
1014
+ height: Math.max(1, Math.ceil(viewBox[3] || 600))
1015
+ };
1016
+ }
1017
+ function finalizePrepared(svgEl) {
1018
+ const { width, height } = sizeFromViewBox(svgEl);
1019
+ svgEl.setAttribute("width", String(width));
1020
+ svgEl.setAttribute("height", String(height));
1021
+ svgEl.removeAttribute("style");
1022
+ const serialized = new XMLSerializer().serializeToString(svgEl);
1023
+ return { serialized, width, height, hasForeignObject: serialized.includes("<foreignObject") };
1024
+ }
1025
+ function prepareSvgString(svgText) {
1026
+ const holder = document.createElement("div");
1027
+ holder.innerHTML = svgText;
1028
+ const svgEl = holder.querySelector("svg");
1029
+ if (!svgEl) {
1030
+ return void 0;
1031
+ }
1032
+ return finalizePrepared(svgEl);
1033
+ }
1034
+ function prepareSvgElement(svg) {
1035
+ return finalizePrepared(svg);
1036
+ }
1037
+ function serializeLiveSvg(svg) {
1038
+ const clone = svg.cloneNode(true);
1039
+ const vp = clone.querySelector(".svg-pan-zoom_viewport");
1040
+ if (vp) {
1041
+ vp.removeAttribute("transform");
1042
+ vp.style.transform = "";
1043
+ }
1044
+ return finalizePrepared(clone);
1045
+ }
1046
+ function svgBlob(serialized) {
1047
+ const xml = serialized.startsWith("<?xml") ? serialized : '<?xml version="1.0" encoding="UTF-8"?>\n' + serialized;
1048
+ return new Blob([xml], { type: "image/svg+xml;charset=utf-8" });
1049
+ }
1050
+ async function rasterizeToBlob(prepared, opts = {}) {
1051
+ const scale = opts.scale ?? 2;
1052
+ const type = opts.type ?? "png";
1053
+ const mime = RASTER_MIME[type];
1054
+ const transparent = opts.transparent === true;
1055
+ const blob = new Blob([prepared.serialized], { type: "image/svg+xml;charset=utf-8" });
1056
+ const url = URL.createObjectURL(blob);
1057
+ try {
1058
+ const img = new Image();
1059
+ await new Promise((resolve3, reject) => {
1060
+ img.onload = () => resolve3();
1061
+ img.onerror = () => reject(new Error("\u7121\u6CD5\u9EDE\u9663\u5316 SVG(\u5716\u7247\u8F09\u5165\u5931\u6557)\u3002"));
1062
+ img.src = url;
1063
+ });
1064
+ const canvas = document.createElement("canvas");
1065
+ canvas.width = Math.max(1, Math.round(prepared.width * scale));
1066
+ canvas.height = Math.max(1, Math.round(prepared.height * scale));
1067
+ const ctx = canvas.getContext("2d");
1068
+ if (!ctx) {
1069
+ throw new Error("\u53D6\u4E0D\u5230 Canvas 2D context\u3002");
1070
+ }
1071
+ if (!transparent || mime === "image/jpeg") {
1072
+ ctx.fillStyle = opts.background ?? (opts.dark ? "#111827" : "#ffffff");
1073
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
1074
+ }
1075
+ ctx.scale(scale, scale);
1076
+ ctx.drawImage(img, 0, 0, prepared.width, prepared.height);
1077
+ return await canvasToBlob(canvas, mime, type === "png" ? void 0 : opts.quality ?? 0.92);
1078
+ } finally {
1079
+ URL.revokeObjectURL(url);
1080
+ }
1081
+ }
1082
+ function canvasToBlob(canvas, mime, quality) {
1083
+ return new Promise((resolve3, reject) => {
1084
+ canvas.toBlob(
1085
+ (blob) => {
1086
+ if (blob) {
1087
+ resolve3(blob);
1088
+ } else {
1089
+ reject(new Error("\u532F\u51FA\u5931\u6557:\u6B64\u5716\u542B HTML \u6A19\u7C64(foreignObject),\u8ACB\u6539\u7528 SVG \u532F\u51FA\u3002"));
1090
+ }
1091
+ },
1092
+ mime,
1093
+ quality
1094
+ );
1095
+ });
1096
+ }
1097
+ function downloadBlob(blob, filename) {
1098
+ const url = URL.createObjectURL(blob);
1099
+ const a = document.createElement("a");
1100
+ a.href = url;
1101
+ a.download = filename;
1102
+ document.body.appendChild(a);
1103
+ a.click();
1104
+ a.remove();
1105
+ setTimeout(() => URL.revokeObjectURL(url), 1e3);
1106
+ }
1107
+
1108
+ // src/react/useMermaidViewer.ts
1109
+ function useMermaidViewer(opts) {
1110
+ const {
1111
+ code,
1112
+ theme,
1113
+ dark,
1114
+ seed,
1115
+ fontUrl,
1116
+ mermaidConfig,
1117
+ mermaid,
1118
+ svgPanZoom,
1119
+ panZoom,
1120
+ injectStyles,
1121
+ onRender,
1122
+ onError
1123
+ } = opts;
1124
+ const [status, setStatus] = useState("loading");
1125
+ const [error, setError] = useState("");
1126
+ const [zoomPercent, setZoomPercent] = useState(100);
1127
+ const stageRef = useRef(null);
1128
+ const svgRef = useRef(null);
1129
+ const pzRef = useRef(null);
1130
+ const prevCodeRef = useRef(null);
1131
+ const mermaidSourceRef = useRef(mermaid);
1132
+ const cfgRef = useRef({ theme, dark, seed, fontUrl, mermaidConfig, code });
1133
+ mermaidSourceRef.current = mermaid;
1134
+ cfgRef.current = { theme, dark, seed, fontUrl, mermaidConfig, code };
1135
+ const getSvg = useCallback(() => svgRef.current, []);
1136
+ const searchRef = useRef(null);
1137
+ const searchController = searchRef.current ?? (searchRef.current = createSearch(
1138
+ getSvg,
1139
+ (el) => pzRef.current?.panToElement(el)
1140
+ ));
1141
+ const syncZoom = useCallback(() => {
1142
+ const pct = pzRef.current?.getZoomPercent();
1143
+ if (typeof pct === "number" && Number.isFinite(pct)) {
1144
+ setZoomPercent(pct);
1145
+ }
1146
+ }, []);
1147
+ const mermaidConfigKey = useMemo(
1148
+ () => mermaidConfig ? JSON.stringify(mermaidConfig) : "",
1149
+ [mermaidConfig]
1150
+ );
1151
+ useEffect(() => {
1152
+ let cancelled = false;
1153
+ const host = stageRef.current;
1154
+ if (!host) {
1155
+ return void 0;
1156
+ }
1157
+ if (injectStyles) {
1158
+ ensureStyles();
1159
+ }
1160
+ const keepView = prevCodeRef.current === code;
1161
+ const run = async () => {
1162
+ setStatus("loading");
1163
+ setError("");
1164
+ try {
1165
+ const mermaidInst = await loadMermaid({ source: mermaidSourceRef.current });
1166
+ const { svgString, postProcess } = await renderToSvg({
1167
+ code,
1168
+ theme,
1169
+ dark,
1170
+ seed,
1171
+ fontUrl,
1172
+ mermaidConfig,
1173
+ mermaid: mermaidInst
1174
+ });
1175
+ if (cancelled) {
1176
+ return;
1177
+ }
1178
+ const prevView = keepView ? pzRef.current?.capture() ?? null : null;
1179
+ pzRef.current?.destroy();
1180
+ pzRef.current = null;
1181
+ const svg = mountSvg(host, svgString, postProcess, { dark, seed });
1182
+ if (!svg) {
1183
+ throw new Error("mermaid \u672A\u8F38\u51FA SVG\u3002");
1184
+ }
1185
+ svgRef.current = svg;
1186
+ if (panZoom) {
1187
+ const factory = await loadSvgPanZoom(svgPanZoom);
1188
+ if (cancelled) {
1189
+ return;
1190
+ }
1191
+ if (factory) {
1192
+ pzRef.current = attachPanZoom(svg, factory, { onZoom: syncZoom });
1193
+ if (prevView) {
1194
+ pzRef.current.restore(prevView);
1195
+ }
1196
+ syncZoom();
1197
+ }
1198
+ }
1199
+ prevCodeRef.current = code;
1200
+ setStatus("ready");
1201
+ onRender?.(svg);
1202
+ searchController.rerun(false);
1203
+ } catch (e) {
1204
+ if (cancelled) {
1205
+ return;
1206
+ }
1207
+ const err = e instanceof Error ? e : new Error(String(e));
1208
+ setError(err.message);
1209
+ setStatus("error");
1210
+ onError?.(err);
1211
+ }
1212
+ };
1213
+ void run();
1214
+ return () => {
1215
+ cancelled = true;
1216
+ };
1217
+ }, [code, theme, dark, seed, fontUrl, mermaidConfigKey, panZoom, injectStyles]);
1218
+ useEffect(() => {
1219
+ return () => {
1220
+ pzRef.current?.destroy();
1221
+ pzRef.current = null;
1222
+ };
1223
+ }, []);
1224
+ const zoomIn = useCallback(() => pzRef.current?.zoomIn(), []);
1225
+ const zoomOut = useCallback(() => pzRef.current?.zoomOut(), []);
1226
+ const fit = useCallback(() => pzRef.current?.fit(), []);
1227
+ const reset = useCallback(() => pzRef.current?.reset(), []);
1228
+ const actualSize = useCallback(() => pzRef.current?.actualSize(), []);
1229
+ const getZoomPercent = useCallback(() => pzRef.current?.getZoomPercent() ?? 100, []);
1230
+ const search = useCallback(
1231
+ (term, pan = true) => searchController.search(term, pan),
1232
+ [searchController]
1233
+ );
1234
+ const next = useCallback((pan = true) => searchController.next(pan), [searchController]);
1235
+ const prev = useCallback((pan = true) => searchController.prev(pan), [searchController]);
1236
+ const clearSearch = useCallback(() => searchController.clear(), [searchController]);
1237
+ const exportSvg = useCallback(() => {
1238
+ const svg = svgRef.current;
1239
+ if (!svg) {
1240
+ throw new Error("[react-super-mermaid] \u5C1A\u7121\u53EF\u532F\u51FA\u7684\u5716\u8868\u3002");
1241
+ }
1242
+ const prepared = serializeLiveSvg(svg);
1243
+ return '<?xml version="1.0" encoding="UTF-8"?>\n' + prepared.serialized;
1244
+ }, []);
1245
+ const exportPng = useCallback(async (rasterOpts = {}) => {
1246
+ const svg = svgRef.current;
1247
+ if (!svg) {
1248
+ throw new Error("[react-super-mermaid] \u5C1A\u7121\u53EF\u532F\u51FA\u7684\u5716\u8868\u3002");
1249
+ }
1250
+ const cfg = cfgRef.current;
1251
+ let prepared = serializeLiveSvg(svg);
1252
+ try {
1253
+ const mermaidInst = await loadMermaid({ source: mermaidSourceRef.current });
1254
+ const { svgString } = await renderToSvg({
1255
+ code: cfg.code,
1256
+ theme: cfg.theme,
1257
+ dark: cfg.dark,
1258
+ seed: cfg.seed,
1259
+ fontUrl: cfg.fontUrl,
1260
+ mermaidConfig: cfg.mermaidConfig,
1261
+ mermaid: mermaidInst,
1262
+ pristine: true
1263
+ });
1264
+ const holder = document.createElement("div");
1265
+ holder.innerHTML = svgString;
1266
+ const pristineSvg = holder.querySelector("svg");
1267
+ if (pristineSvg) {
1268
+ applyPostProcess(pristineSvg, resolveTheme(cfg.theme, cfg.dark).postProcess, {
1269
+ dark: cfg.dark,
1270
+ seed: cfg.seed
1271
+ });
1272
+ prepared = prepareSvgElement(pristineSvg);
1273
+ }
1274
+ } catch {
1275
+ }
1276
+ return rasterizeToBlob(prepared, { ...rasterOpts, dark: cfg.dark });
1277
+ }, []);
1278
+ const downloadSvg = useCallback(
1279
+ (filename = "diagram.svg") => {
1280
+ downloadBlob(svgBlob(exportSvg()), filename);
1281
+ },
1282
+ [exportSvg]
1283
+ );
1284
+ const downloadPng = useCallback(
1285
+ async (filename = "diagram.png", rasterOpts = {}) => {
1286
+ const blob = await exportPng(rasterOpts);
1287
+ downloadBlob(blob, filename);
1288
+ },
1289
+ [exportPng]
1290
+ );
1291
+ return {
1292
+ stageRef,
1293
+ status,
1294
+ error,
1295
+ zoomPercent,
1296
+ zoomIn,
1297
+ zoomOut,
1298
+ fit,
1299
+ reset,
1300
+ actualSize,
1301
+ getZoomPercent,
1302
+ search,
1303
+ next,
1304
+ prev,
1305
+ clearSearch,
1306
+ exportSvg,
1307
+ exportPng,
1308
+ downloadSvg,
1309
+ downloadPng,
1310
+ getSvg
1311
+ };
1312
+ }
1313
+ var DEFAULT_THEME_OPTIONS = [
1314
+ { value: "colorful", label: "Colorful" },
1315
+ { value: "sketch", label: "Excalidraw" },
1316
+ { value: "auto", label: "Auto" },
1317
+ { value: "default", label: "Light" },
1318
+ { value: "dark", label: "Dark" },
1319
+ { value: "neutral", label: "Neutral" },
1320
+ { value: "forest", label: "Forest" }
1321
+ ];
1322
+ function Toolbar(props) {
1323
+ return /* @__PURE__ */ jsxs("div", { className: "rsm-toolbar", children: [
1324
+ /* @__PURE__ */ jsxs("label", { className: "rsm-label", children: [
1325
+ "\u6A23\u5F0F",
1326
+ /* @__PURE__ */ jsx(
1327
+ "select",
1328
+ {
1329
+ className: "rsm-select",
1330
+ value: props.theme,
1331
+ onChange: (e) => props.onThemeChange(e.target.value),
1332
+ children: props.themeOptions.map((o) => /* @__PURE__ */ jsx("option", { value: o.value, children: o.label }, o.value))
1333
+ }
1334
+ )
1335
+ ] }),
1336
+ /* @__PURE__ */ jsx("div", { className: "rsm-toolbar-spacer" }),
1337
+ props.searchEnabled ? /* @__PURE__ */ jsx(
1338
+ "button",
1339
+ {
1340
+ type: "button",
1341
+ className: "rsm-btn",
1342
+ "aria-pressed": props.searchOpen,
1343
+ onClick: props.onToggleSearch,
1344
+ title: "\u5728\u5716\u4E2D\u641C\u5C0B\uFF08/ \u6216 Ctrl+F\uFF09",
1345
+ children: "\u{1F50D} \u641C\u5C0B"
1346
+ }
1347
+ ) : null,
1348
+ props.exportEnabled ? /* @__PURE__ */ jsxs("div", { className: "rsm-toolbar-group", children: [
1349
+ /* @__PURE__ */ jsx(
1350
+ "button",
1351
+ {
1352
+ type: "button",
1353
+ className: "rsm-btn",
1354
+ onClick: props.onExportSvg,
1355
+ disabled: props.exporting,
1356
+ title: "\u532F\u51FA SVG",
1357
+ children: "\u2B07 SVG"
1358
+ }
1359
+ ),
1360
+ /* @__PURE__ */ jsx(
1361
+ "button",
1362
+ {
1363
+ type: "button",
1364
+ className: "rsm-btn",
1365
+ onClick: props.onExportPng,
1366
+ disabled: props.exporting,
1367
+ title: "\u532F\u51FA PNG\uFF082x\uFF09",
1368
+ children: props.exporting ? "\u532F\u51FA\u4E2D\u2026" : "\u2B07 PNG"
1369
+ }
1370
+ )
1371
+ ] }) : null,
1372
+ /* @__PURE__ */ jsxs("div", { className: "rsm-zoom", children: [
1373
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: props.onZoomOut, title: "\u7E2E\u5C0F\uFF08-\uFF09", children: "\uFF0D" }),
1374
+ /* @__PURE__ */ jsxs(
1375
+ "button",
1376
+ {
1377
+ type: "button",
1378
+ className: "rsm-zoom-percent",
1379
+ onClick: props.onActualSize,
1380
+ title: "\u5BE6\u969B\u5927\u5C0F\uFF081\uFF09",
1381
+ children: [
1382
+ props.zoomPercent,
1383
+ "%"
1384
+ ]
1385
+ }
1386
+ ),
1387
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: props.onZoomIn, title: "\u653E\u5927\uFF08+\uFF09", children: "\uFF0B" }),
1388
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: props.onReset, title: "\u7B26\u5408\u8996\u7A97\uFF080\uFF09", children: "\u2922" })
1389
+ ] })
1390
+ ] });
1391
+ }
1392
+ function usePrefersDark(explicit) {
1393
+ const [autoDark, setAutoDark] = useState(false);
1394
+ useEffect(() => {
1395
+ if (explicit !== void 0 || typeof window === "undefined" || !window.matchMedia) {
1396
+ return void 0;
1397
+ }
1398
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
1399
+ setAutoDark(mq.matches);
1400
+ const handler = (e) => setAutoDark(e.matches);
1401
+ mq.addEventListener("change", handler);
1402
+ return () => mq.removeEventListener("change", handler);
1403
+ }, [explicit]);
1404
+ return explicit ?? autoDark;
1405
+ }
1406
+ var MermaidViewer = forwardRef(
1407
+ function MermaidViewer2(props, ref) {
1408
+ const {
1409
+ code,
1410
+ toolbar = true,
1411
+ themeOptions = DEFAULT_THEME_OPTIONS,
1412
+ panZoom = true,
1413
+ search: searchEnabled = true,
1414
+ exportable = true,
1415
+ keyboard = true,
1416
+ seed = 42,
1417
+ fontUrl,
1418
+ mermaid,
1419
+ mermaidConfig,
1420
+ svgPanZoom,
1421
+ injectStyles = true,
1422
+ className,
1423
+ style,
1424
+ onRender,
1425
+ onError
1426
+ } = props;
1427
+ const dark = usePrefersDark(props.dark);
1428
+ const [theme, setTheme] = useState(props.theme ?? "colorful");
1429
+ useEffect(() => {
1430
+ if (props.theme) {
1431
+ setTheme(props.theme);
1432
+ }
1433
+ }, [props.theme]);
1434
+ const [searchOpen, setSearchOpen] = useState(false);
1435
+ const [query, setQuery] = useState("");
1436
+ const [matchInfo, setMatchInfo] = useState({ current: 0, total: 0 });
1437
+ const [exporting, setExporting] = useState(false);
1438
+ const rootRef = useRef(null);
1439
+ const searchInputRef = useRef(null);
1440
+ const vm = useMermaidViewer({
1441
+ code,
1442
+ theme,
1443
+ dark,
1444
+ seed,
1445
+ fontUrl,
1446
+ mermaidConfig,
1447
+ mermaid,
1448
+ svgPanZoom,
1449
+ panZoom,
1450
+ injectStyles,
1451
+ onRender,
1452
+ onError
1453
+ });
1454
+ const runSearch = useCallback(
1455
+ (term, pan) => {
1456
+ setMatchInfo(vm.search(term, pan));
1457
+ },
1458
+ [vm]
1459
+ );
1460
+ const openSearch = useCallback(() => {
1461
+ setSearchOpen(true);
1462
+ window.setTimeout(() => {
1463
+ searchInputRef.current?.focus();
1464
+ searchInputRef.current?.select();
1465
+ }, 0);
1466
+ if (query.trim()) {
1467
+ runSearch(query, true);
1468
+ }
1469
+ }, [query, runSearch]);
1470
+ const closeSearch = useCallback(() => {
1471
+ setSearchOpen(false);
1472
+ vm.clearSearch();
1473
+ setQuery("");
1474
+ setMatchInfo({ current: 0, total: 0 });
1475
+ }, [vm]);
1476
+ const toggleSearch = useCallback(() => {
1477
+ if (searchOpen) {
1478
+ closeSearch();
1479
+ } else {
1480
+ openSearch();
1481
+ }
1482
+ }, [searchOpen, openSearch, closeSearch]);
1483
+ const exportSvg = useCallback(() => {
1484
+ try {
1485
+ vm.downloadSvg("diagram.svg");
1486
+ } catch (e) {
1487
+ onError?.(e instanceof Error ? e : new Error(String(e)));
1488
+ }
1489
+ }, [vm, onError]);
1490
+ const exportPng = useCallback(async () => {
1491
+ setExporting(true);
1492
+ try {
1493
+ await vm.downloadPng("diagram.png", { scale: 2 });
1494
+ } catch (e) {
1495
+ onError?.(e instanceof Error ? e : new Error(String(e)));
1496
+ } finally {
1497
+ setExporting(false);
1498
+ }
1499
+ }, [vm, onError]);
1500
+ useEffect(() => {
1501
+ if (!keyboard) {
1502
+ return void 0;
1503
+ }
1504
+ const root = rootRef.current;
1505
+ if (!root) {
1506
+ return void 0;
1507
+ }
1508
+ const onKey = (e) => {
1509
+ const target = e.target;
1510
+ const typing = target ? /^(input|textarea|select)$/i.test(target.tagName) : false;
1511
+ if ((e.ctrlKey || e.metaKey) && (e.key === "f" || e.key === "F")) {
1512
+ e.preventDefault();
1513
+ openSearch();
1514
+ return;
1515
+ }
1516
+ if (e.key === "/" && !typing) {
1517
+ e.preventDefault();
1518
+ openSearch();
1519
+ return;
1520
+ }
1521
+ if (e.key === "Escape" && searchOpen) {
1522
+ closeSearch();
1523
+ return;
1524
+ }
1525
+ if (typing) {
1526
+ return;
1527
+ }
1528
+ if (e.key === "+" || e.key === "=") {
1529
+ vm.zoomIn();
1530
+ } else if (e.key === "-" || e.key === "_") {
1531
+ vm.zoomOut();
1532
+ } else if (e.key === "0") {
1533
+ vm.reset();
1534
+ } else if (e.key === "1") {
1535
+ vm.actualSize();
1536
+ } else if (e.key === "w" || e.key === "W") {
1537
+ vm.fit();
1538
+ }
1539
+ };
1540
+ root.addEventListener("keydown", onKey);
1541
+ return () => root.removeEventListener("keydown", onKey);
1542
+ }, [keyboard, searchOpen, openSearch, closeSearch, vm]);
1543
+ useImperativeHandle(
1544
+ ref,
1545
+ () => ({
1546
+ zoomIn: vm.zoomIn,
1547
+ zoomOut: vm.zoomOut,
1548
+ fit: vm.fit,
1549
+ reset: vm.reset,
1550
+ actualSize: vm.actualSize,
1551
+ getZoomPercent: vm.getZoomPercent,
1552
+ search: (term) => vm.search(term),
1553
+ next: () => vm.next(),
1554
+ prev: () => vm.prev(),
1555
+ clearSearch: vm.clearSearch,
1556
+ exportSvg: vm.exportSvg,
1557
+ exportPng: vm.exportPng,
1558
+ downloadSvg: vm.downloadSvg,
1559
+ downloadPng: vm.downloadPng,
1560
+ getSvg: vm.getSvg
1561
+ }),
1562
+ [vm]
1563
+ );
1564
+ let countText = "";
1565
+ if (matchInfo.total > 0) {
1566
+ countText = `${matchInfo.current}/${matchInfo.total}`;
1567
+ } else if (query.trim()) {
1568
+ countText = "0";
1569
+ }
1570
+ const rootClassName = ["rsm-root", dark ? "rsm-dark" : "", className ?? ""].filter(Boolean).join(" ");
1571
+ return /* @__PURE__ */ jsxs(
1572
+ "div",
1573
+ {
1574
+ ref: rootRef,
1575
+ className: rootClassName,
1576
+ style,
1577
+ tabIndex: keyboard ? 0 : void 0,
1578
+ children: [
1579
+ toolbar ? /* @__PURE__ */ jsx(
1580
+ Toolbar,
1581
+ {
1582
+ theme,
1583
+ themeOptions,
1584
+ onThemeChange: setTheme,
1585
+ zoomPercent: vm.zoomPercent,
1586
+ onZoomIn: vm.zoomIn,
1587
+ onZoomOut: vm.zoomOut,
1588
+ onActualSize: vm.actualSize,
1589
+ onReset: vm.reset,
1590
+ searchEnabled,
1591
+ searchOpen,
1592
+ onToggleSearch: toggleSearch,
1593
+ exportEnabled: exportable,
1594
+ exporting,
1595
+ onExportSvg: exportSvg,
1596
+ onExportPng: exportPng
1597
+ }
1598
+ ) : null,
1599
+ toolbar && searchEnabled && searchOpen ? /* @__PURE__ */ jsxs("div", { className: "rsm-searchbar", children: [
1600
+ /* @__PURE__ */ jsx(
1601
+ "input",
1602
+ {
1603
+ ref: searchInputRef,
1604
+ className: "rsm-input",
1605
+ type: "text",
1606
+ value: query,
1607
+ spellCheck: false,
1608
+ placeholder: "\u5728\u5716\u4E2D\u641C\u5C0B\u2026\uFF08Enter \u4E0B\u4E00\u500B / Shift+Enter \u4E0A\u4E00\u500B\uFF09",
1609
+ onChange: (e) => {
1610
+ setQuery(e.target.value);
1611
+ runSearch(e.target.value, true);
1612
+ },
1613
+ onKeyDown: (e) => {
1614
+ if (e.key === "Enter") {
1615
+ e.preventDefault();
1616
+ setMatchInfo(e.shiftKey ? vm.prev() : vm.next());
1617
+ } else if (e.key === "Escape") {
1618
+ e.preventDefault();
1619
+ closeSearch();
1620
+ }
1621
+ }
1622
+ }
1623
+ ),
1624
+ /* @__PURE__ */ jsx("span", { className: "rsm-count", children: countText }),
1625
+ /* @__PURE__ */ jsx("button", { type: "button", className: "rsm-btn", onClick: () => setMatchInfo(vm.prev()), children: "\u4E0A\u4E00\u500B" }),
1626
+ /* @__PURE__ */ jsx("button", { type: "button", className: "rsm-btn", onClick: () => setMatchInfo(vm.next()), children: "\u4E0B\u4E00\u500B" }),
1627
+ /* @__PURE__ */ jsx("div", { className: "rsm-searchbar-spacer" }),
1628
+ /* @__PURE__ */ jsx("button", { type: "button", className: "rsm-btn", onClick: closeSearch, children: "\u2715 \u95DC\u9589" })
1629
+ ] }) : null,
1630
+ /* @__PURE__ */ jsxs("div", { className: "rsm-canvas", children: [
1631
+ vm.status === "loading" ? /* @__PURE__ */ jsx("div", { className: "rsm-overlay", children: "\u5716\u8868\u6E32\u67D3\u4E2D\u2026" }) : null,
1632
+ vm.status === "error" ? /* @__PURE__ */ jsxs("div", { className: "rsm-overlay rsm-error", children: [
1633
+ "\u5716\u8868\u8F09\u5165\u5931\u6557\uFF1A",
1634
+ vm.error
1635
+ ] }) : null,
1636
+ /* @__PURE__ */ jsx("div", { ref: vm.stageRef, className: "rsm-stage" })
1637
+ ] })
1638
+ ]
1639
+ }
1640
+ );
1641
+ }
1642
+ );
1643
+ var MermaidDiagram = forwardRef(
1644
+ function MermaidDiagram2(props, ref) {
1645
+ return /* @__PURE__ */ jsx(MermaidViewer, { ref, toolbar: false, ...props });
1646
+ }
1647
+ );
1648
+
1649
+ export { DEFAULT_THEME_OPTIONS, DEFAULT_VIRGIL_FONT_URL, MermaidDiagram, MermaidViewer, SKETCH_FONT, Toolbar, colorizeDiagram, downloadBlob, ensureSketchFont, ensureStyles, loadMermaid, loadSvgPanZoom, prepareSvgElement, prepareSvgString, rasterizeToBlob, renderDiagram, resolveTheme, serializeLiveSvg, sketchifyDiagram, svgBlob, useMermaidViewer };
1650
+ //# sourceMappingURL=index.js.map
1651
+ //# sourceMappingURL=index.js.map