@localix-ai/flowdown 0.0.0-stage → 0.5.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/LICENSE +21 -0
- package/README.md +105 -2
- package/dist/browser/file-actions.js +80 -0
- package/dist/browser/file-actions.js.map +1 -0
- package/dist/core/block-facts.js +72 -0
- package/dist/core/block-facts.js.map +1 -0
- package/dist/core/blocks.d.ts +25 -0
- package/dist/core/blocks.js +464 -0
- package/dist/core/blocks.js.map +1 -0
- package/dist/core/bounded-cache.js +42 -0
- package/dist/core/bounded-cache.js.map +1 -0
- package/dist/core/continues.js +15 -0
- package/dist/core/continues.js.map +1 -0
- package/dist/core/dollars.d.ts +12 -0
- package/dist/core/dollars.js +88 -0
- package/dist/core/dollars.js.map +1 -0
- package/dist/core/fence-scan.js +62 -0
- package/dist/core/fence-scan.js.map +1 -0
- package/dist/core/file-tree-parse.d.ts +26 -0
- package/dist/core/file-tree-parse.js +51 -0
- package/dist/core/file-tree-parse.js.map +1 -0
- package/dist/core/forms-html.js +63 -0
- package/dist/core/forms-html.js.map +1 -0
- package/dist/core/forms.d.ts +28 -0
- package/dist/core/forms.js +23 -0
- package/dist/core/forms.js.map +1 -0
- package/dist/core/headings.d.ts +11 -0
- package/dist/core/headings.js +74 -0
- package/dist/core/headings.js.map +1 -0
- package/dist/core/heal.d.ts +11 -0
- package/dist/core/heal.js +220 -0
- package/dist/core/heal.js.map +1 -0
- package/dist/core/load-once.js +23 -0
- package/dist/core/load-once.js.map +1 -0
- package/dist/core/math-delimiters.js +33 -0
- package/dist/core/math-delimiters.js.map +1 -0
- package/dist/core/math-syntax.js +56 -0
- package/dist/core/math-syntax.js.map +1 -0
- package/dist/core/prose.d.ts +10 -0
- package/dist/core/prose.js +77 -0
- package/dist/core/prose.js.map +1 -0
- package/dist/core/remark-answer-blocks.d.ts +5 -0
- package/dist/core/remark-answer-blocks.js +172 -0
- package/dist/core/remark-answer-blocks.js.map +1 -0
- package/dist/core/remark-answer-containers.js +299 -0
- package/dist/core/remark-answer-containers.js.map +1 -0
- package/dist/core/sanitize-schema.d.ts +18 -0
- package/dist/core/sanitize-schema.js +62 -0
- package/dist/core/sanitize-schema.js.map +1 -0
- package/dist/core/syntax.d.ts +8 -0
- package/dist/core/syntax.js +32 -0
- package/dist/core/syntax.js.map +1 -0
- package/dist/dialect.d.ts +92 -0
- package/dist/dialect.js +176 -0
- package/dist/dialect.js.map +1 -0
- package/dist/document/fence.d.ts +25 -0
- package/dist/document/fence.js +102 -0
- package/dist/document/fence.js.map +1 -0
- package/dist/document/index.d.ts +46 -0
- package/dist/document/index.js +66 -0
- package/dist/document/index.js.map +1 -0
- package/dist/document/table.d.ts +11 -0
- package/dist/document/table.js +35 -0
- package/dist/document/table.js.map +1 -0
- package/dist/dom/index.d.ts +21 -0
- package/dist/dom/index.js +141 -0
- package/dist/dom/index.js.map +1 -0
- package/dist/dom/patch.js +104 -0
- package/dist/dom/patch.js.map +1 -0
- package/dist/element/index.d.ts +35 -0
- package/dist/element/index.js +64 -0
- package/dist/element/index.js.map +1 -0
- package/dist/engine/blocks.js +785 -0
- package/dist/engine/blocks.js.map +1 -0
- package/dist/engine/entities.js +83 -0
- package/dist/engine/entities.js.map +1 -0
- package/dist/engine/entity-core.js +9 -0
- package/dist/engine/entity-core.js.map +1 -0
- package/dist/engine/entity-table.js +9 -0
- package/dist/engine/entity-table.js.map +1 -0
- package/dist/engine/html.d.ts +7 -0
- package/dist/engine/html.js +207 -0
- package/dist/engine/html.js.map +1 -0
- package/dist/engine/index.d.ts +7 -0
- package/dist/engine/index.js +74 -0
- package/dist/engine/index.js.map +1 -0
- package/dist/engine/inlines.js +887 -0
- package/dist/engine/inlines.js.map +1 -0
- package/dist/engine/types.d.ts +109 -0
- package/dist/engine/url.d.ts +11 -0
- package/dist/engine/url.js +16 -0
- package/dist/engine/url.js.map +1 -0
- package/dist/engine/visit.d.ts +10 -0
- package/dist/engine/visit.js +29 -0
- package/dist/engine/visit.js.map +1 -0
- package/dist/highlight/highlight.d.ts +4 -0
- package/dist/highlight/highlight.js +144 -0
- package/dist/highlight/highlight.js.map +1 -0
- package/dist/highlight/index.d.ts +4 -0
- package/dist/highlight/index.js +18 -0
- package/dist/highlight/index.js.map +1 -0
- package/dist/html/index.d.ts +4 -0
- package/dist/html/index.js +544 -0
- package/dist/html/index.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +13 -0
- package/dist/math/index.d.ts +17 -0
- package/dist/math/index.js +20 -0
- package/dist/math/index.js.map +1 -0
- package/dist/math/typeset.d.ts +12 -0
- package/dist/math/typeset.js +74 -0
- package/dist/math/typeset.js.map +1 -0
- package/dist/mermaid/diagram.d.ts +8 -0
- package/dist/mermaid/diagram.js +114 -0
- package/dist/mermaid/diagram.js.map +1 -0
- package/dist/mermaid/drawing.js +139 -0
- package/dist/mermaid/drawing.js.map +1 -0
- package/dist/mermaid/icons.js +16 -0
- package/dist/mermaid/icons.js.map +1 -0
- package/dist/mermaid/index.d.ts +13 -0
- package/dist/mermaid/index.js +3 -0
- package/dist/mermaid/loader.d.ts +5 -0
- package/dist/mermaid/loader.js +18 -0
- package/dist/mermaid/loader.js.map +1 -0
- package/dist/mermaid/look.d.ts +16 -0
- package/dist/mermaid/look.js +167 -0
- package/dist/mermaid/look.js.map +1 -0
- package/dist/mermaid/normalize-svg.js +44 -0
- package/dist/mermaid/normalize-svg.js.map +1 -0
- package/dist/mermaid/renderer.d.ts +22 -0
- package/dist/mermaid/renderer.js +132 -0
- package/dist/mermaid/renderer.js.map +1 -0
- package/dist/mermaid/viewer.js +309 -0
- package/dist/mermaid/viewer.js.map +1 -0
- package/dist/prompt/index.d.ts +19 -0
- package/dist/prompt/index.js +54 -0
- package/dist/prompt/index.js.map +1 -0
- package/dist/react/callout.d.ts +9 -0
- package/dist/react/callout.js +12 -0
- package/dist/react/callout.js.map +1 -0
- package/dist/react/code-block.d.ts +18 -0
- package/dist/react/code-block.js +94 -0
- package/dist/react/code-block.js.map +1 -0
- package/dist/react/context.d.ts +18 -0
- package/dist/react/context.js +17 -0
- package/dist/react/context.js.map +1 -0
- package/dist/react/draw.d.ts +2 -0
- package/dist/react/draw.js +17 -0
- package/dist/react/draw.js.map +1 -0
- package/dist/react/file-tree.d.ts +16 -0
- package/dist/react/file-tree.js +69 -0
- package/dist/react/file-tree.js.map +1 -0
- package/dist/react/growing-code.js +36 -0
- package/dist/react/growing-code.js.map +1 -0
- package/dist/react/helpers.d.ts +6 -0
- package/dist/react/helpers.js +17 -0
- package/dist/react/helpers.js.map +1 -0
- package/dist/react/icons.js +23 -0
- package/dist/react/icons.js.map +1 -0
- package/dist/react/labels.js +9 -0
- package/dist/react/labels.js.map +1 -0
- package/dist/react/markdown.d.ts +78 -0
- package/dist/react/markdown.js +206 -0
- package/dist/react/markdown.js.map +1 -0
- package/dist/react/render.d.ts +8 -0
- package/dist/react/render.js +41 -0
- package/dist/react/render.js.map +1 -0
- package/dist/react/scrolls.js +62 -0
- package/dist/react/scrolls.js.map +1 -0
- package/dist/react/table-rows.js +66 -0
- package/dist/react/table-rows.js.map +1 -0
- package/dist/react/table.js +22 -0
- package/dist/react/table.js.map +1 -0
- package/dist/react/tabs.d.ts +25 -0
- package/dist/react/tabs.js +58 -0
- package/dist/react/tabs.js.map +1 -0
- package/dist/react/use-smooth-text.d.ts +6 -0
- package/dist/react/use-smooth-text.js +157 -0
- package/dist/react/use-smooth-text.js.map +1 -0
- package/dist/react/use-stage.d.ts +9 -0
- package/dist/react/use-stage.js +57 -0
- package/dist/react/use-stage.js.map +1 -0
- package/dist/remark.d.ts +20 -0
- package/dist/remark.js +10 -0
- package/dist/render/factory.d.ts +15 -0
- package/dist/render/labels.d.ts +46 -0
- package/dist/render/labels.js +46 -0
- package/dist/render/labels.js.map +1 -0
- package/dist/render/tree.js +314 -0
- package/dist/render/tree.js.map +1 -0
- package/dist/render/view.d.ts +58 -0
- package/dist/render/view.js +306 -0
- package/dist/render/view.js.map +1 -0
- package/dist/render/views.d.ts +16 -0
- package/dist/render/views.js +261 -0
- package/dist/render/views.js.map +1 -0
- package/dist/render/vnode.d.ts +21 -0
- package/dist/render/vnode.js +194 -0
- package/dist/render/vnode.js.map +1 -0
- package/dist/static/index.d.ts +28 -0
- package/dist/static/index.js +27 -0
- package/dist/static/index.js.map +1 -0
- package/dist/stream.d.ts +12 -0
- package/dist/stream.js +3 -0
- package/dist/styles.css +1362 -0
- package/dist/tui/code.d.ts +5 -0
- package/dist/tui/code.js +240 -0
- package/dist/tui/code.js.map +1 -0
- package/dist/tui/index.d.ts +54 -0
- package/dist/tui/index.js +99 -0
- package/dist/tui/index.js.map +1 -0
- package/dist/tui/math.js +242 -0
- package/dist/tui/math.js.map +1 -0
- package/dist/tui/printable.d.ts +12 -0
- package/dist/tui/printable.js +31 -0
- package/dist/tui/printable.js.map +1 -0
- package/dist/tui/render.d.ts +12 -0
- package/dist/tui/render.js +524 -0
- package/dist/tui/render.js.map +1 -0
- package/dist/tui/style.d.ts +35 -0
- package/dist/tui/style.js +77 -0
- package/dist/tui/style.js.map +1 -0
- package/dist/tui/table.js +73 -0
- package/dist/tui/table.js.map +1 -0
- package/dist/tui/width.d.ts +2 -0
- package/dist/tui/width.js +133 -0
- package/dist/tui/width.js.map +1 -0
- package/dist/tui/wrap.d.ts +8 -0
- package/dist/tui/wrap.js +191 -0
- package/dist/tui/wrap.js.map +1 -0
- package/package.json +128 -4
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// src/mermaid/look.ts
|
|
2
|
+
/**
|
|
3
|
+
* Mermaid's own themes are fixed palettes that match no page around them, so a diagram is drawn
|
|
4
|
+
* with Mermaid's `base` theme fed from the renderer's tokens (`--fd-*`, `styles.css`), read on
|
|
5
|
+
* the element the diagram sits in: a page that recolours the Markdown recolours its diagrams.
|
|
6
|
+
*
|
|
7
|
+
* Mermaid derives shades with a colour library that cannot read `oklch()`, `color-mix()` or a
|
|
8
|
+
* system colour, so each token is resolved by the browser (a probe element inside the root) and
|
|
9
|
+
* rasterized to sRGB hex on a 1×1 canvas. A "veil", the text colour at a few percent over the
|
|
10
|
+
* ground, is composited there too, since Mermaid needs an opaque colour where the page uses alpha.
|
|
11
|
+
*/
|
|
12
|
+
function resolveMermaidLook(root) {
|
|
13
|
+
const probe = document.createElement("span");
|
|
14
|
+
probe.style.display = "none";
|
|
15
|
+
root.append(probe);
|
|
16
|
+
const canvas = document.createElement("canvas");
|
|
17
|
+
canvas.width = 1;
|
|
18
|
+
canvas.height = 1;
|
|
19
|
+
const context = canvas.getContext("2d", { willReadFrequently: true });
|
|
20
|
+
const computed = (token) => {
|
|
21
|
+
probe.style.color = `var(${token})`;
|
|
22
|
+
return getComputedStyle(probe).color;
|
|
23
|
+
};
|
|
24
|
+
const rgbOf = (base, color, alpha) => {
|
|
25
|
+
if (!context) return [
|
|
26
|
+
255,
|
|
27
|
+
255,
|
|
28
|
+
255
|
|
29
|
+
];
|
|
30
|
+
context.globalAlpha = 1;
|
|
31
|
+
context.fillStyle = base;
|
|
32
|
+
context.fillRect(0, 0, 1, 1);
|
|
33
|
+
context.globalAlpha = alpha;
|
|
34
|
+
context.fillStyle = color;
|
|
35
|
+
context.fillRect(0, 0, 1, 1);
|
|
36
|
+
const [r = 255, g = 255, b = 255] = context.getImageData(0, 0, 1, 1).data;
|
|
37
|
+
return [
|
|
38
|
+
r,
|
|
39
|
+
g,
|
|
40
|
+
b
|
|
41
|
+
];
|
|
42
|
+
};
|
|
43
|
+
/** `color` at `alpha` over the opaque `base`, as hex. */
|
|
44
|
+
const over = (base, color, alpha = 1) => `#${rgbOf(base, color, alpha).map((channel) => channel.toString(16).padStart(2, "0")).join("")}`;
|
|
45
|
+
const ground = over(computed("--fd-bg-solid, #ffffff"), computed("--fd-bg"));
|
|
46
|
+
const [r, g, b] = rgbOf("#ffffff", ground, 1);
|
|
47
|
+
const dark = .2126 * r + .7152 * g + .0722 * b < 128;
|
|
48
|
+
const paint = (color, alpha = 1) => over(ground, color, alpha);
|
|
49
|
+
const token = (name) => paint(computed(name));
|
|
50
|
+
const fgColor = computed("--fd-fg");
|
|
51
|
+
const veil = (alpha) => paint(fgColor, alpha);
|
|
52
|
+
const bg = ground;
|
|
53
|
+
const fg = token("--fd-fg");
|
|
54
|
+
const soft = token("--fd-fg-soft");
|
|
55
|
+
const faint = token("--fd-fg-faint");
|
|
56
|
+
const border = token("--fd-border");
|
|
57
|
+
const strong = token("--fd-border-strong");
|
|
58
|
+
const node = veil(.06);
|
|
59
|
+
const charts = Array.from({ length: 8 }, (_, index) => token(`--fd-chart-${index + 1}`));
|
|
60
|
+
const fontFamily = getComputedStyle(root).fontFamily;
|
|
61
|
+
probe.remove();
|
|
62
|
+
const themeVariables = {
|
|
63
|
+
darkMode: dark,
|
|
64
|
+
dropShadow: "none",
|
|
65
|
+
background: bg,
|
|
66
|
+
fontFamily,
|
|
67
|
+
fontSize: "13px",
|
|
68
|
+
primaryColor: node,
|
|
69
|
+
primaryTextColor: fg,
|
|
70
|
+
primaryBorderColor: border,
|
|
71
|
+
secondaryColor: veil(.1),
|
|
72
|
+
secondaryTextColor: fg,
|
|
73
|
+
secondaryBorderColor: border,
|
|
74
|
+
tertiaryColor: veil(.03),
|
|
75
|
+
tertiaryTextColor: fg,
|
|
76
|
+
tertiaryBorderColor: border,
|
|
77
|
+
lineColor: faint,
|
|
78
|
+
textColor: fg,
|
|
79
|
+
titleColor: fg,
|
|
80
|
+
edgeLabelBackground: bg,
|
|
81
|
+
clusterBkg: veil(.03),
|
|
82
|
+
clusterBorder: border,
|
|
83
|
+
noteBkgColor: node,
|
|
84
|
+
noteTextColor: fg,
|
|
85
|
+
noteBorderColor: border,
|
|
86
|
+
actorBkg: node,
|
|
87
|
+
actorBorder: border,
|
|
88
|
+
actorTextColor: fg,
|
|
89
|
+
actorLineColor: border,
|
|
90
|
+
signalColor: soft,
|
|
91
|
+
signalTextColor: fg,
|
|
92
|
+
labelBoxBkgColor: node,
|
|
93
|
+
labelBoxBorderColor: border,
|
|
94
|
+
labelTextColor: fg,
|
|
95
|
+
loopTextColor: soft,
|
|
96
|
+
activationBkgColor: veil(.1),
|
|
97
|
+
activationBorderColor: strong,
|
|
98
|
+
sequenceNumberColor: bg,
|
|
99
|
+
...Object.fromEntries(charts.map((color, index) => [`pie${index + 1}`, color])),
|
|
100
|
+
...Object.fromEntries(Array.from({ length: 12 }, (_, i) => [`cScale${i}`, charts[i % charts.length]])),
|
|
101
|
+
...Object.fromEntries(Array.from({ length: 12 }, (_, i) => [`cScaleLabel${i}`, fg])),
|
|
102
|
+
...Object.fromEntries(Array.from({ length: 12 }, (_, i) => [`cScalePeer${i}`, paint(charts[i % charts.length], .16)])),
|
|
103
|
+
...Object.fromEntries(charts.map((color, index) => [`git${index}`, color])),
|
|
104
|
+
...Object.fromEntries(charts.map((_, index) => [`gitBranchLabel${index}`, bg])),
|
|
105
|
+
...Object.fromEntries(Array.from({ length: 4 }, (_, i) => [`quadrant${i + 1}Fill`, veil(.03 + i * .02)])),
|
|
106
|
+
...Object.fromEntries(Array.from({ length: 4 }, (_, i) => [`quadrant${i + 1}TextFill`, soft])),
|
|
107
|
+
quadrantPointFill: charts[0],
|
|
108
|
+
quadrantPointTextFill: fg,
|
|
109
|
+
quadrantXAxisTextFill: soft,
|
|
110
|
+
quadrantYAxisTextFill: soft,
|
|
111
|
+
radar: {
|
|
112
|
+
axisColor: border,
|
|
113
|
+
axisLabelFontSize: 13,
|
|
114
|
+
axisStrokeWidth: 1,
|
|
115
|
+
graticuleColor: border,
|
|
116
|
+
graticuleOpacity: .15,
|
|
117
|
+
graticuleStrokeWidth: 1,
|
|
118
|
+
curveOpacity: .14,
|
|
119
|
+
curveStrokeWidth: 2,
|
|
120
|
+
legendBoxSize: 10,
|
|
121
|
+
legendFontSize: 13
|
|
122
|
+
},
|
|
123
|
+
pieStrokeColor: bg,
|
|
124
|
+
pieOuterStrokeColor: border,
|
|
125
|
+
pieTitleTextColor: fg,
|
|
126
|
+
pieSectionTextColor: bg,
|
|
127
|
+
pieLegendTextColor: soft,
|
|
128
|
+
xyChart: {
|
|
129
|
+
backgroundColor: "transparent",
|
|
130
|
+
titleColor: fg,
|
|
131
|
+
xAxisLabelColor: soft,
|
|
132
|
+
xAxisTitleColor: soft,
|
|
133
|
+
xAxisTickColor: border,
|
|
134
|
+
xAxisLineColor: border,
|
|
135
|
+
yAxisLabelColor: soft,
|
|
136
|
+
yAxisTitleColor: soft,
|
|
137
|
+
yAxisTickColor: border,
|
|
138
|
+
yAxisLineColor: border,
|
|
139
|
+
plotColorPalette: charts.join(",")
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
const config = {
|
|
143
|
+
theme: "base",
|
|
144
|
+
htmlLabels: false,
|
|
145
|
+
themeVariables,
|
|
146
|
+
flowchart: {
|
|
147
|
+
htmlLabels: false,
|
|
148
|
+
curve: "basis",
|
|
149
|
+
padding: 12,
|
|
150
|
+
nodeSpacing: 36,
|
|
151
|
+
rankSpacing: 44
|
|
152
|
+
},
|
|
153
|
+
sequence: {
|
|
154
|
+
mirrorActors: false,
|
|
155
|
+
messageFontSize: 13,
|
|
156
|
+
actorFontSize: 13,
|
|
157
|
+
noteFontSize: 12
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
return {
|
|
161
|
+
key: JSON.stringify(themeVariables),
|
|
162
|
+
config
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
export { resolveMermaidLook };
|
|
166
|
+
|
|
167
|
+
//# sourceMappingURL=look.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"look.js","names":[],"sources":["../../src/mermaid/look.ts"],"sourcesContent":["/** The Mermaid configuration a diagram is drawn with, and the identity it is cached under. */\nexport interface MermaidLook {\n key: string\n config: Record<string, unknown>\n}\n\n/**\n * Mermaid's own themes are fixed palettes that match no page around them, so a diagram is drawn\n * with Mermaid's `base` theme fed from the renderer's tokens (`--fd-*`, `styles.css`), read on\n * the element the diagram sits in: a page that recolours the Markdown recolours its diagrams.\n *\n * Mermaid derives shades with a colour library that cannot read `oklch()`, `color-mix()` or a\n * system colour, so each token is resolved by the browser (a probe element inside the root) and\n * rasterized to sRGB hex on a 1×1 canvas. A \"veil\", the text colour at a few percent over the\n * ground, is composited there too, since Mermaid needs an opaque colour where the page uses alpha.\n */\nexport function resolveMermaidLook(root: HTMLElement): MermaidLook {\n const probe = document.createElement('span')\n probe.style.display = 'none'\n root.append(probe)\n const canvas = document.createElement('canvas')\n canvas.width = 1\n canvas.height = 1\n const context = canvas.getContext('2d', { willReadFrequently: true })\n\n const computed = (token: string) => {\n probe.style.color = `var(${token})`\n return getComputedStyle(probe).color\n }\n const rgbOf = (base: string, color: string, alpha: number): [number, number, number] => {\n if (!context) return [255, 255, 255]\n context.globalAlpha = 1\n context.fillStyle = base\n context.fillRect(0, 0, 1, 1)\n context.globalAlpha = alpha\n context.fillStyle = color\n context.fillRect(0, 0, 1, 1)\n const [r = 255, g = 255, b = 255] = context.getImageData(0, 0, 1, 1).data\n return [r, g, b]\n }\n /** `color` at `alpha` over the opaque `base`, as hex. */\n const over = (base: string, color: string, alpha = 1): string =>\n `#${rgbOf(base, color, alpha)\n .map((channel) => channel.toString(16).padStart(2, '0'))\n .join('')}`\n // The ground may carry alpha (a glass panel); it is laid once over what shows through it,\n // `--fd-bg-solid` (white unless the page says), and every veil below is mixed over that result.\n // Over white, a dark translucent ground came out light grey, and the nodes with it.\n const ground = over(computed('--fd-bg-solid, #ffffff'), computed('--fd-bg'))\n const [r, g, b] = rgbOf('#ffffff', ground, 1)\n const dark = 0.2126 * r + 0.7152 * g + 0.0722 * b < 128\n const paint = (color: string, alpha = 1) => over(ground, color, alpha)\n const token = (name: string) => paint(computed(name))\n const fgColor = computed('--fd-fg')\n const veil = (alpha: number) => paint(fgColor, alpha)\n\n const bg = ground\n const fg = token('--fd-fg')\n const soft = token('--fd-fg-soft')\n const faint = token('--fd-fg-faint')\n const border = token('--fd-border')\n const strong = token('--fd-border-strong')\n const node = veil(0.06)\n const charts = Array.from({ length: 8 }, (_, index) => token(`--fd-chart-${index + 1}`))\n const fontFamily = getComputedStyle(root).fontFamily\n probe.remove()\n\n const themeVariables = {\n darkMode: dark,\n // Mermaid's own shadow under a node is a fixed light grey: a glow on a dark ground, and depth\n // the flat panels around a diagram do not have on a light one.\n dropShadow: 'none',\n background: bg,\n fontFamily,\n fontSize: '13px',\n primaryColor: node,\n primaryTextColor: fg,\n // The fill already separates a node from the ground; the outline only has to hold its edge,\n // so it takes the ordinary border step rather than the strong one.\n primaryBorderColor: border,\n secondaryColor: veil(0.1),\n secondaryTextColor: fg,\n secondaryBorderColor: border,\n tertiaryColor: veil(0.03),\n tertiaryTextColor: fg,\n tertiaryBorderColor: border,\n lineColor: faint,\n textColor: fg,\n titleColor: fg,\n edgeLabelBackground: bg,\n clusterBkg: veil(0.03),\n clusterBorder: border,\n noteBkgColor: node,\n noteTextColor: fg,\n noteBorderColor: border,\n actorBkg: node,\n actorBorder: border,\n actorTextColor: fg,\n actorLineColor: border,\n signalColor: soft,\n signalTextColor: fg,\n labelBoxBkgColor: node,\n labelBoxBorderColor: border,\n labelTextColor: fg,\n loopTextColor: soft,\n activationBkgColor: veil(0.1),\n activationBorderColor: strong,\n sequenceNumberColor: bg,\n ...Object.fromEntries(charts.map((color, index) => [`pie${index + 1}`, color])),\n // Radar, treemap, mindmap and timeline use cScale, while git graphs use gitN. Leaving those\n // derived from neutral node fills gave data series indistinguishable grey colours.\n ...Object.fromEntries(\n Array.from({ length: 12 }, (_, i) => [`cScale${i}`, charts[i % charts.length]]),\n ),\n ...Object.fromEntries(Array.from({ length: 12 }, (_, i) => [`cScaleLabel${i}`, fg])),\n ...Object.fromEntries(\n Array.from({ length: 12 }, (_, i) => [\n `cScalePeer${i}`,\n paint(charts[i % charts.length]!, 0.16),\n ]),\n ),\n ...Object.fromEntries(charts.map((color, index) => [`git${index}`, color])),\n ...Object.fromEntries(charts.map((_, index) => [`gitBranchLabel${index}`, bg])),\n ...Object.fromEntries(\n Array.from({ length: 4 }, (_, i) => [`quadrant${i + 1}Fill`, veil(0.03 + i * 0.02)]),\n ),\n ...Object.fromEntries(Array.from({ length: 4 }, (_, i) => [`quadrant${i + 1}TextFill`, soft])),\n quadrantPointFill: charts[0],\n quadrantPointTextFill: fg,\n quadrantXAxisTextFill: soft,\n quadrantYAxisTextFill: soft,\n radar: {\n axisColor: border,\n axisLabelFontSize: 13,\n axisStrokeWidth: 1,\n graticuleColor: border,\n graticuleOpacity: 0.15,\n graticuleStrokeWidth: 1,\n curveOpacity: 0.14,\n curveStrokeWidth: 2,\n legendBoxSize: 10,\n legendFontSize: 13,\n },\n pieStrokeColor: bg,\n pieOuterStrokeColor: border,\n pieTitleTextColor: fg,\n pieSectionTextColor: bg,\n pieLegendTextColor: soft,\n xyChart: {\n // Mermaid paints this as a plot-sized slab; transparent lets the page show through.\n backgroundColor: 'transparent',\n titleColor: fg,\n xAxisLabelColor: soft,\n xAxisTitleColor: soft,\n xAxisTickColor: border,\n xAxisLineColor: border,\n yAxisLabelColor: soft,\n yAxisTitleColor: soft,\n yAxisTickColor: border,\n yAxisLineColor: border,\n plotColorPalette: charts.join(','),\n },\n }\n const config = {\n theme: 'base',\n // SVG labels avoid HTML measurement frames whose temporary 2000px boxes can enter layout.\n // The same vector geometry works in a transcript, an embedded board and the canvas viewer.\n htmlLabels: false,\n themeVariables,\n flowchart: { htmlLabels: false, curve: 'basis', padding: 12, nodeSpacing: 36, rankSpacing: 44 },\n sequence: { mirrorActors: false, messageFontSize: 13, actorFontSize: 13, noteFontSize: 12 },\n }\n // Keyed by the resolved colours, not the theme name: a palette change repaints every diagram\n // even when \"dark\" stays \"dark\".\n return { key: JSON.stringify(themeVariables), config }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// src/mermaid/normalize-svg.ts
|
|
2
|
+
/**
|
|
3
|
+
* Mermaid may serialize a flowchart's viewBox before its last node transforms have reached the
|
|
4
|
+
* browser's geometry cache. Measure the finished vector once, before caching or displaying it.
|
|
5
|
+
* This is drawing work; opening the viewer never reparses or measures the diagram.
|
|
6
|
+
*/
|
|
7
|
+
function normalizeMermaidSvg(vector) {
|
|
8
|
+
if (typeof document === "undefined" || !vector.includes("class=\"root\"")) return vector;
|
|
9
|
+
const holder = document.createElement("div");
|
|
10
|
+
holder.setAttribute("aria-hidden", "true");
|
|
11
|
+
Object.assign(holder.style, {
|
|
12
|
+
position: "fixed",
|
|
13
|
+
left: "-100000px",
|
|
14
|
+
top: "0",
|
|
15
|
+
width: "1000px",
|
|
16
|
+
opacity: "0",
|
|
17
|
+
pointerEvents: "none"
|
|
18
|
+
});
|
|
19
|
+
holder.innerHTML = vector;
|
|
20
|
+
document.body.append(holder);
|
|
21
|
+
try {
|
|
22
|
+
const svg = holder.querySelector("svg");
|
|
23
|
+
if (!svg || typeof svg.getBBox !== "function") return vector;
|
|
24
|
+
const bounds = svg.getBBox();
|
|
25
|
+
if (![
|
|
26
|
+
bounds.x,
|
|
27
|
+
bounds.y,
|
|
28
|
+
bounds.width,
|
|
29
|
+
bounds.height
|
|
30
|
+
].every(Number.isFinite) || bounds.width <= 0 || bounds.height <= 0) return vector;
|
|
31
|
+
const width = bounds.width + 16;
|
|
32
|
+
const height = bounds.height + 16;
|
|
33
|
+
svg.setAttribute("viewBox", `${bounds.x - 8} ${bounds.y - 8} ${width} ${height}`);
|
|
34
|
+
svg.style.maxWidth = `${width}px`;
|
|
35
|
+
return svg.outerHTML;
|
|
36
|
+
} catch {
|
|
37
|
+
return vector;
|
|
38
|
+
} finally {
|
|
39
|
+
holder.remove();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export { normalizeMermaidSvg };
|
|
43
|
+
|
|
44
|
+
//# sourceMappingURL=normalize-svg.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize-svg.js","names":[],"sources":["../../src/mermaid/normalize-svg.ts"],"sourcesContent":["/**\n * Mermaid may serialize a flowchart's viewBox before its last node transforms have reached the\n * browser's geometry cache. Measure the finished vector once, before caching or displaying it.\n * This is drawing work; opening the viewer never reparses or measures the diagram.\n */\nexport function normalizeMermaidSvg(vector: string): string {\n if (typeof document === 'undefined' || !vector.includes('class=\"root\"')) return vector\n const holder = document.createElement('div')\n holder.setAttribute('aria-hidden', 'true')\n Object.assign(holder.style, {\n position: 'fixed',\n left: '-100000px',\n top: '0',\n width: '1000px',\n opacity: '0',\n pointerEvents: 'none',\n })\n // Only strict Mermaid output reaches here, never raw agent-authored SVG.\n holder.innerHTML = vector\n document.body.append(holder)\n try {\n const svg = holder.querySelector('svg')\n if (!svg || typeof svg.getBBox !== 'function') return vector\n const bounds = svg.getBBox()\n if (\n ![bounds.x, bounds.y, bounds.width, bounds.height].every(Number.isFinite) ||\n bounds.width <= 0 ||\n bounds.height <= 0\n )\n return vector\n const width = bounds.width + 16\n const height = bounds.height + 16\n svg.setAttribute('viewBox', `${bounds.x - 8} ${bounds.y - 8} ${width} ${height}`)\n svg.style.maxWidth = `${width}px`\n return svg.outerHTML\n } catch {\n return vector\n } finally {\n holder.remove()\n }\n}\n"],"mappings":";;;;;;AAKA,SAAgB,oBAAoB,QAAwB;CAC1D,IAAI,OAAO,aAAa,eAAe,CAAC,OAAO,SAAS,gBAAc,GAAG,OAAO;CAChF,MAAM,SAAS,SAAS,cAAc,KAAK;CAC3C,OAAO,aAAa,eAAe,MAAM;CACzC,OAAO,OAAO,OAAO,OAAO;EAC1B,UAAU;EACV,MAAM;EACN,KAAK;EACL,OAAO;EACP,SAAS;EACT,eAAe;CACjB,CAAC;CAED,OAAO,YAAY;CACnB,SAAS,KAAK,OAAO,MAAM;CAC3B,IAAI;EACF,MAAM,MAAM,OAAO,cAAc,KAAK;EACtC,IAAI,CAAC,OAAO,OAAO,IAAI,YAAY,YAAY,OAAO;EACtD,MAAM,SAAS,IAAI,QAAQ;EAC3B,IACE,CAAC;GAAC,OAAO;GAAG,OAAO;GAAG,OAAO;GAAO,OAAO;EAAM,CAAC,CAAC,MAAM,OAAO,QAAQ,KACxE,OAAO,SAAS,KAChB,OAAO,UAAU,GAEjB,OAAO;EACT,MAAM,QAAQ,OAAO,QAAQ;EAC7B,MAAM,SAAS,OAAO,SAAS;EAC/B,IAAI,aAAa,WAAW,GAAG,OAAO,IAAI,EAAE,GAAG,OAAO,IAAI,EAAE,GAAG,MAAM,GAAG,QAAQ;EAChF,IAAI,MAAM,WAAW,GAAG,MAAM;EAC9B,OAAO,IAAI;CACb,QAAQ;EACN,OAAO;CACT,UAAU;EACR,OAAO,OAAO;CAChB;AACF"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { MermaidLoader } from "./loader.js";
|
|
2
|
+
import { MermaidLook } from "./look.js";
|
|
3
|
+
/**
|
|
4
|
+
* The slice of the Mermaid API a diagram render needs. Narrowed so the renderer below can be
|
|
5
|
+
* driven by a stub in tests: Mermaid lays out through `getBBox`, which no DOM test runner has.
|
|
6
|
+
*/
|
|
7
|
+
export interface MermaidEngine {
|
|
8
|
+
initialize(config: Record<string, unknown>): void;
|
|
9
|
+
parse(text: string): Promise<unknown>;
|
|
10
|
+
render(id: string, text: string): Promise<{
|
|
11
|
+
svg: string;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
export interface MermaidRenderer {
|
|
15
|
+
/** The finished SVG when this source was already drawn in this look, else undefined. */
|
|
16
|
+
peek(source: string, look: MermaidLook): string | undefined;
|
|
17
|
+
/** Identical requests share one layout; aborting releases only this caller. */
|
|
18
|
+
render(source: string, look: MermaidLook, signal?: AbortSignal): Promise<string>;
|
|
19
|
+
}
|
|
20
|
+
/** The page's renderer, with `load` as the loader its engine comes from. */
|
|
21
|
+
export declare function mermaidRendererFor(load: MermaidLoader): MermaidRenderer;
|
|
22
|
+
export type { MermaidLoader };
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { createBoundedCache } from "../core/bounded-cache.js";
|
|
2
|
+
import { loadOnce } from "../core/load-once.js";
|
|
3
|
+
import { loadMermaid, setMermaidLoader } from "./loader.js";
|
|
4
|
+
import { normalizeMermaidSvg } from "./normalize-svg.js";
|
|
5
|
+
// src/mermaid/renderer.ts
|
|
6
|
+
/** Drawn diagrams kept per session, and their total SVG size (characters) — a big ER diagram
|
|
7
|
+
* is hundreds of kilobytes, so the count alone would not bound the heap. */
|
|
8
|
+
const CACHE_MAX_ENTRIES = 64;
|
|
9
|
+
const CACHE_MAX_CHARACTERS = 8e6;
|
|
10
|
+
/**
|
|
11
|
+
* One render at a time, remembered by source and look.
|
|
12
|
+
*
|
|
13
|
+
* Mermaid keeps its configuration and its scratch DOM node in module state, so two renders in
|
|
14
|
+
* flight overwrite each other's colours and ids — the second diagram of a message came out in
|
|
15
|
+
* the first one's palette, or not at all. Renders therefore run through one queue (the shape
|
|
16
|
+
* streamdown's Mermaid block uses for the same reason). Unmounted messages used to leave
|
|
17
|
+
* every queued layout running; requests now share a job and release it on unmount, dropping an
|
|
18
|
+
* unwanted queued job before it loads or lays out. The cache answers the other
|
|
19
|
+
* defect: a virtualized chat unmounts messages on scroll, and re-laying a diagram out on
|
|
20
|
+
* every pass is visible jank. The look is part of the key because its colours are baked into
|
|
21
|
+
* the SVG.
|
|
22
|
+
*/
|
|
23
|
+
function createMermaidRenderer(load) {
|
|
24
|
+
const cache = createBoundedCache({
|
|
25
|
+
maxEntries: CACHE_MAX_ENTRIES,
|
|
26
|
+
maxWeight: CACHE_MAX_CHARACTERS,
|
|
27
|
+
weigh: (svg, key) => key.length + svg.length
|
|
28
|
+
});
|
|
29
|
+
const engine = loadOnce(load);
|
|
30
|
+
const pending = /* @__PURE__ */ new Map();
|
|
31
|
+
const queue = [];
|
|
32
|
+
let running = false;
|
|
33
|
+
let sequence = 0;
|
|
34
|
+
const keyOf = (source, look) => `${look.key}\0${source}`;
|
|
35
|
+
const aborted = () => new DOMException("Diagram render cancelled", "AbortError");
|
|
36
|
+
const draw = async (job) => {
|
|
37
|
+
const mermaid = await engine();
|
|
38
|
+
if (job.consumers.size === 0) throw aborted();
|
|
39
|
+
mermaid.initialize({
|
|
40
|
+
...job.look.config,
|
|
41
|
+
startOnLoad: false,
|
|
42
|
+
securityLevel: "strict",
|
|
43
|
+
suppressErrorRendering: true
|
|
44
|
+
});
|
|
45
|
+
await mermaid.parse(job.source);
|
|
46
|
+
if (job.consumers.size === 0) throw aborted();
|
|
47
|
+
sequence += 1;
|
|
48
|
+
const { svg } = await mermaid.render(`fd-mermaid-${sequence}`, job.source);
|
|
49
|
+
return job.consumers.size === 0 ? svg : normalizeMermaidSvg(svg);
|
|
50
|
+
};
|
|
51
|
+
const drain = async () => {
|
|
52
|
+
if (running) return;
|
|
53
|
+
running = true;
|
|
54
|
+
try {
|
|
55
|
+
while (queue.length > 0) {
|
|
56
|
+
const job = queue.shift();
|
|
57
|
+
job.started = true;
|
|
58
|
+
try {
|
|
59
|
+
const svg = await draw(job);
|
|
60
|
+
if (job.consumers.size > 0) cache.set(job.key, svg);
|
|
61
|
+
for (const consumer of job.consumers) consumer.resolve(svg);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
for (const consumer of job.consumers) consumer.reject(error);
|
|
64
|
+
} finally {
|
|
65
|
+
for (const consumer of job.consumers) consumer.dispose();
|
|
66
|
+
job.consumers.clear();
|
|
67
|
+
pending.delete(job.key);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
} finally {
|
|
71
|
+
running = false;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
return {
|
|
75
|
+
peek: (source, look) => cache.get(keyOf(source, look)),
|
|
76
|
+
render(source, look, signal) {
|
|
77
|
+
if (signal?.aborted) return Promise.reject(aborted());
|
|
78
|
+
const key = keyOf(source, look);
|
|
79
|
+
const hit = cache.get(key);
|
|
80
|
+
if (hit !== void 0) return Promise.resolve(hit);
|
|
81
|
+
let job = pending.get(key);
|
|
82
|
+
if (!job) {
|
|
83
|
+
job = {
|
|
84
|
+
key,
|
|
85
|
+
source,
|
|
86
|
+
look,
|
|
87
|
+
consumers: /* @__PURE__ */ new Set(),
|
|
88
|
+
started: false
|
|
89
|
+
};
|
|
90
|
+
pending.set(key, job);
|
|
91
|
+
queue.push(job);
|
|
92
|
+
}
|
|
93
|
+
const requested = job;
|
|
94
|
+
const result = new Promise((resolve, reject) => {
|
|
95
|
+
const cancel = () => {
|
|
96
|
+
consumer.dispose();
|
|
97
|
+
requested.consumers.delete(consumer);
|
|
98
|
+
reject(aborted());
|
|
99
|
+
if (!requested.started && requested.consumers.size === 0) {
|
|
100
|
+
queue.splice(queue.indexOf(requested), 1);
|
|
101
|
+
pending.delete(key);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
const consumer = {
|
|
105
|
+
resolve,
|
|
106
|
+
reject,
|
|
107
|
+
dispose: () => signal?.removeEventListener("abort", cancel)
|
|
108
|
+
};
|
|
109
|
+
requested.consumers.add(consumer);
|
|
110
|
+
signal?.addEventListener("abort", cancel, { once: true });
|
|
111
|
+
});
|
|
112
|
+
drain();
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
let pageRenderer = null;
|
|
118
|
+
/**
|
|
119
|
+
* The page's one renderer: one queue and one cache for every diagram on it. Mermaid is a single
|
|
120
|
+
* global engine, so two renderers would interleave its `initialize` and `render` calls. The engine
|
|
121
|
+
* comes from the loader the page passed most recently (`loader.ts`); it is fetched the first time
|
|
122
|
+
* a document holds a diagram.
|
|
123
|
+
*/
|
|
124
|
+
const pageMermaidRenderer = () => pageRenderer ??= createMermaidRenderer(loadMermaid);
|
|
125
|
+
/** The page's renderer, with `load` as the loader its engine comes from. */
|
|
126
|
+
function mermaidRendererFor(load) {
|
|
127
|
+
setMermaidLoader(load);
|
|
128
|
+
return pageMermaidRenderer();
|
|
129
|
+
}
|
|
130
|
+
export { createMermaidRenderer, mermaidRendererFor, pageMermaidRenderer };
|
|
131
|
+
|
|
132
|
+
//# sourceMappingURL=renderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"renderer.js","names":[],"sources":["../../src/mermaid/renderer.ts"],"sourcesContent":["import { createBoundedCache } from '../core/bounded-cache'\nimport { loadOnce } from '../core/load-once'\nimport { loadMermaid, setMermaidLoader, type MermaidLoader } from './loader'\nimport type { MermaidLook } from './look'\nimport { normalizeMermaidSvg } from './normalize-svg'\n\n/**\n * The slice of the Mermaid API a diagram render needs. Narrowed so the renderer below can be\n * driven by a stub in tests: Mermaid lays out through `getBBox`, which no DOM test runner has.\n */\nexport interface MermaidEngine {\n initialize(config: Record<string, unknown>): void\n parse(text: string): Promise<unknown>\n render(id: string, text: string): Promise<{ svg: string }>\n}\n\nexport interface MermaidRenderer {\n /** The finished SVG when this source was already drawn in this look, else undefined. */\n peek(source: string, look: MermaidLook): string | undefined\n /** Identical requests share one layout; aborting releases only this caller. */\n render(source: string, look: MermaidLook, signal?: AbortSignal): Promise<string>\n}\n\n/** Drawn diagrams kept per session, and their total SVG size (characters) — a big ER diagram\n * is hundreds of kilobytes, so the count alone would not bound the heap. */\nconst CACHE_MAX_ENTRIES = 64\nconst CACHE_MAX_CHARACTERS = 8_000_000\n\n/**\n * One render at a time, remembered by source and look.\n *\n * Mermaid keeps its configuration and its scratch DOM node in module state, so two renders in\n * flight overwrite each other's colours and ids — the second diagram of a message came out in\n * the first one's palette, or not at all. Renders therefore run through one queue (the shape\n * streamdown's Mermaid block uses for the same reason). Unmounted messages used to leave\n * every queued layout running; requests now share a job and release it on unmount, dropping an\n * unwanted queued job before it loads or lays out. The cache answers the other\n * defect: a virtualized chat unmounts messages on scroll, and re-laying a diagram out on\n * every pass is visible jank. The look is part of the key because its colours are baked into\n * the SVG.\n */\nexport function createMermaidRenderer(load: () => Promise<MermaidEngine>): MermaidRenderer {\n const cache = createBoundedCache<string, string>({\n maxEntries: CACHE_MAX_ENTRIES,\n maxWeight: CACHE_MAX_CHARACTERS,\n weigh: (svg, key) => key.length + svg.length,\n })\n // A failed fetch of the Mermaid chunk is retried by the next diagram, not remembered.\n const engine = loadOnce(load)\n interface Consumer {\n resolve(svg: string): void\n reject(error: unknown): void\n dispose(): void\n }\n interface Job {\n key: string\n source: string\n look: MermaidLook\n consumers: Set<Consumer>\n started: boolean\n }\n const pending = new Map<string, Job>()\n const queue: Job[] = []\n let running = false\n let sequence = 0\n const keyOf = (source: string, look: MermaidLook) => `${look.key}\\0${source}`\n const aborted = () => new DOMException('Diagram render cancelled', 'AbortError')\n\n const draw = async (job: Job): Promise<string> => {\n const mermaid = await engine()\n // Loading the shared chunk cannot be cancelled, but a departed row needs no parse or layout.\n if (job.consumers.size === 0) throw aborted()\n mermaid.initialize({\n ...job.look.config,\n startOnLoad: false,\n // Labels are model-authored text: strict makes Mermaid sanitize them and drop click\n // handlers, so a diagram cannot carry script into the page.\n securityLevel: 'strict',\n // Without this a syntax error leaves Mermaid's \"bomb\" SVG appended to <body>.\n suppressErrorRendering: true,\n })\n // `parse` throws a readable error before `render` touches the DOM.\n await mermaid.parse(job.source)\n if (job.consumers.size === 0) throw aborted()\n sequence += 1\n const { svg } = await mermaid.render(`fd-mermaid-${sequence}`, job.source)\n return job.consumers.size === 0 ? svg : normalizeMermaidSvg(svg)\n }\n\n const drain = async () => {\n if (running) return\n running = true\n try {\n while (queue.length > 0) {\n const job = queue.shift()!\n job.started = true\n try {\n const svg = await draw(job)\n if (job.consumers.size > 0) cache.set(job.key, svg)\n for (const consumer of job.consumers) consumer.resolve(svg)\n } catch (error) {\n // A failed or abandoned diagram must not poison the next job.\n for (const consumer of job.consumers) consumer.reject(error)\n } finally {\n for (const consumer of job.consumers) consumer.dispose()\n job.consumers.clear()\n pending.delete(job.key)\n }\n }\n } finally {\n running = false\n }\n }\n\n return {\n peek: (source, look) => cache.get(keyOf(source, look)),\n render(source, look, signal) {\n if (signal?.aborted) return Promise.reject(aborted())\n const key = keyOf(source, look)\n const hit = cache.get(key)\n if (hit !== undefined) return Promise.resolve(hit)\n let job = pending.get(key)\n if (!job) {\n job = { key, source, look, consumers: new Set(), started: false }\n pending.set(key, job)\n queue.push(job)\n }\n const requested = job\n const result = new Promise<string>((resolve, reject) => {\n const cancel = () => {\n consumer.dispose()\n requested.consumers.delete(consumer)\n reject(aborted())\n if (!requested.started && requested.consumers.size === 0) {\n queue.splice(queue.indexOf(requested), 1)\n pending.delete(key)\n }\n }\n const consumer: Consumer = {\n resolve,\n reject,\n dispose: () => signal?.removeEventListener('abort', cancel),\n }\n requested.consumers.add(consumer)\n signal?.addEventListener('abort', cancel, { once: true })\n })\n void drain()\n return result\n },\n }\n}\n\nexport type { MermaidLoader } from './loader'\n\nlet pageRenderer: MermaidRenderer | null = null\n\n/**\n * The page's one renderer: one queue and one cache for every diagram on it. Mermaid is a single\n * global engine, so two renderers would interleave its `initialize` and `render` calls. The engine\n * comes from the loader the page passed most recently (`loader.ts`); it is fetched the first time\n * a document holds a diagram.\n */\nexport const pageMermaidRenderer = (): MermaidRenderer =>\n (pageRenderer ??= createMermaidRenderer(loadMermaid))\n\n/** The page's renderer, with `load` as the loader its engine comes from. */\nexport function mermaidRendererFor(load: MermaidLoader): MermaidRenderer {\n setMermaidLoader(load)\n return pageMermaidRenderer()\n}\n"],"mappings":"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"}
|