@zombie-mermaid/core 2.2.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 +22 -0
- package/dist/index.cjs +29 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1236 -0
- package/dist/index.d.ts +1236 -0
- package/dist/index.js +888 -0
- package/dist/index.js.map +1 -0
- package/package.json +35 -0
- package/src/__tests__/click-directive.test.ts +63 -0
- package/src/__tests__/color-utils.test.ts +186 -0
- package/src/__tests__/diagram-type.test.ts +28 -0
- package/src/__tests__/text-metrics.test.ts +486 -0
- package/src/__tests__/theme-palette-contrast.test.ts +84 -0
- package/src/__tests__/theme.test.ts +162 -0
- package/src/click-directive.ts +106 -0
- package/src/color-utils.ts +205 -0
- package/src/diagram-type.ts +35 -0
- package/src/direction-override.ts +46 -0
- package/src/direction.ts +56 -0
- package/src/generated/mono-font-subset.ts +32 -0
- package/src/index.ts +34 -0
- package/src/init-directive.ts +248 -0
- package/src/multiline-utils.ts +275 -0
- package/src/statements.ts +197 -0
- package/src/style-directives.ts +214 -0
- package/src/text-metrics.ts +520 -0
- package/src/theme.ts +712 -0
- package/src/types.ts +670 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Parsed graph — logical structure extracted from Mermaid text
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import type { ElkNode } from 'elkjs'
|
|
6
|
+
import type { InitConfig, CurveStyle } from './init-directive.ts'
|
|
7
|
+
|
|
8
|
+
// `LayoutCache`'s shape is declared here rather than beside
|
|
9
|
+
// `createLayoutCache()`/`elkLayoutSync()` in
|
|
10
|
+
// `@zombie-mermaid/svg-renderer`'s `elk-instance.ts` because
|
|
11
|
+
// `RenderOptions.layoutCache` below references it: a `core` module that
|
|
12
|
+
// type-imported `svg-renderer` would put a cycle in the type graph, which
|
|
13
|
+
// `core` — a dependency of both renderers — must not have
|
|
14
|
+
// (zombie-mermaid#625, umbrella #620). Only the *shape* moved; every
|
|
15
|
+
// function that builds or reads a cache still lives in `svg-renderer`, and
|
|
16
|
+
// `elkjs` is a type-only import here, erased before anything is bundled.
|
|
17
|
+
//
|
|
18
|
+
// The doc comment below is deliberately byte-identical to the one this
|
|
19
|
+
// interface carried in `elk-instance.ts` — api-extractor copies it into
|
|
20
|
+
// the published `dist/index.d.ts`, so editing it would change the shipped
|
|
21
|
+
// declarations. Its "outside this module" still means `elk-instance.ts`,
|
|
22
|
+
// which remains the only place a `LayoutCache` is created or read.
|
|
23
|
+
/**
|
|
24
|
+
* Opt-in bounded LRU cache for `elkLayoutSync()` results.
|
|
25
|
+
*
|
|
26
|
+
* Off by default — `elkLayoutSync()` only consults a cache when one is
|
|
27
|
+
* explicitly passed in, so existing callers see no behavior change.
|
|
28
|
+
* Create one with `createLayoutCache()` and reuse it across renders (e.g.
|
|
29
|
+
* module scope, or a `useRef` in React) — a fresh cache per render defeats
|
|
30
|
+
* the point.
|
|
31
|
+
*
|
|
32
|
+
* The `map`/`maxSize` fields are implementation detail exposed only so
|
|
33
|
+
* `elkLayoutSync()` (and tests) can read/mutate them directly without a
|
|
34
|
+
* class; treat a `LayoutCache` as opaque from outside this module.
|
|
35
|
+
*/
|
|
36
|
+
export interface LayoutCache {
|
|
37
|
+
/** @internal */
|
|
38
|
+
readonly map: Map<string, ElkNode>
|
|
39
|
+
/** @internal */
|
|
40
|
+
readonly maxSize: number
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface MermaidGraph {
|
|
44
|
+
direction: Direction
|
|
45
|
+
nodes: Map<string, MermaidNode>
|
|
46
|
+
edges: MermaidEdge[]
|
|
47
|
+
subgraphs: MermaidSubgraph[]
|
|
48
|
+
classDefs: Map<string, Record<string, string>>
|
|
49
|
+
/** Maps node IDs to their class names (from `class X className` or `:::className` shorthand) */
|
|
50
|
+
classAssignments: Map<string, string>
|
|
51
|
+
/** Maps node IDs to inline styles (from `style X fill:#f00,stroke:#333`) */
|
|
52
|
+
nodeStyles: Map<string, Record<string, string>>
|
|
53
|
+
/** Maps edge indices (or 'default') to inline styles from `linkStyle` directives */
|
|
54
|
+
linkStyles: Map<number | 'default', Record<string, string>>
|
|
55
|
+
/** Maps node IDs to interactions declared by `click` statements */
|
|
56
|
+
interactions: Map<string, NodeInteraction>
|
|
57
|
+
/** Configuration the diagram set for itself via `%%{init: ...}%%` */
|
|
58
|
+
initConfig?: InitConfig
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type Direction = 'TD' | 'TB' | 'LR' | 'BT' | 'RL'
|
|
62
|
+
|
|
63
|
+
export interface MermaidNode {
|
|
64
|
+
id: string
|
|
65
|
+
label: string
|
|
66
|
+
shape: NodeShape
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type NodeShape =
|
|
70
|
+
| 'rectangle'
|
|
71
|
+
| 'rounded'
|
|
72
|
+
| 'diamond'
|
|
73
|
+
| 'stadium'
|
|
74
|
+
| 'circle'
|
|
75
|
+
// Batch 1 additions
|
|
76
|
+
| 'subroutine' // [[text]] — double-bordered rectangle
|
|
77
|
+
| 'doublecircle' // (((text))) — concentric circles
|
|
78
|
+
| 'hexagon' // {{text}} — six-sided polygon
|
|
79
|
+
// Batch 2 additions
|
|
80
|
+
| 'cylinder' // [(text)] — database cylinder
|
|
81
|
+
| 'asymmetric' // >text] — flag/banner shape
|
|
82
|
+
| 'trapezoid' // [/text\] — wider bottom
|
|
83
|
+
| 'trapezoid-alt' // [\text/] — wider top
|
|
84
|
+
// Parallelogram — note the delimiters mirror rather than oppose, which is
|
|
85
|
+
// what distinguishes these from the trapezoids above: [/…/] not [/…\].
|
|
86
|
+
| 'parallelogram' // [/text/] — leans right
|
|
87
|
+
| 'parallelogram-alt' // [\text\] — leans left
|
|
88
|
+
// Batch 3 state diagram pseudostates
|
|
89
|
+
| 'state-start' // filled circle (start pseudostate)
|
|
90
|
+
| 'state-end' // bullseye circle (end pseudostate)
|
|
91
|
+
// ---------------------------------------------------------------------
|
|
92
|
+
// Expanded-syntax shapes — reachable via `A@{ shape: ... }` only; the
|
|
93
|
+
// classic bracket syntax has no spelling for them. See
|
|
94
|
+
// packages/mermaid-parser/src/expanded-shapes.ts for the full name→shape
|
|
95
|
+
// alias table.
|
|
96
|
+
// ---------------------------------------------------------------------
|
|
97
|
+
| 'document' // wavy-bottomed page
|
|
98
|
+
| 'stacked-document' // document with offset copies behind it
|
|
99
|
+
| 'stacked-process' // rectangle with offset copies behind it
|
|
100
|
+
| 'card' // rectangle with a notched top-left corner
|
|
101
|
+
| 'lined-process' // rectangle with a vertical rule inset from the left
|
|
102
|
+
| 'divided-process' // rectangle split by a horizontal rule
|
|
103
|
+
| 'window-pane' // rectangle quartered by a cross
|
|
104
|
+
| 'triangle' // apex up
|
|
105
|
+
| 'flipped-triangle' // apex down
|
|
106
|
+
| 'filled-circle' // solid dot — junction
|
|
107
|
+
| 'crossed-circle' // circle with an X through it — summary
|
|
108
|
+
| 'fork-join' // solid bar
|
|
109
|
+
| 'notched-pentagon' // rectangle with clipped top corners — loop limit
|
|
110
|
+
| 'sloped-rectangle' // sloped top edge — manual input
|
|
111
|
+
| 'flag' // wavy top and bottom — paper tape
|
|
112
|
+
| 'bow-tie-rectangle' // concave left and right edges — stored data
|
|
113
|
+
| 'half-rounded-rectangle' // one rounded end — delay
|
|
114
|
+
| 'brace' // left brace only
|
|
115
|
+
| 'brace-right' // right brace only
|
|
116
|
+
| 'braces' // braces on both sides
|
|
117
|
+
| 'bolt' // lightning bolt — communication link
|
|
118
|
+
| 'text' // label with no outline
|
|
119
|
+
| 'anchor' // invisible point
|
|
120
|
+
|
|
121
|
+
export interface MermaidEdge {
|
|
122
|
+
source: string
|
|
123
|
+
target: string
|
|
124
|
+
label?: string
|
|
125
|
+
style: EdgeStyle
|
|
126
|
+
/** Whether to render an arrowhead at the start (source end) of the edge */
|
|
127
|
+
hasArrowStart: boolean
|
|
128
|
+
/** Whether to render an arrowhead at the end (target end) of the edge */
|
|
129
|
+
hasArrowEnd: boolean
|
|
130
|
+
/**
|
|
131
|
+
* Terminator shape at the source end when it's a circle/cross marker
|
|
132
|
+
* (`o--`/`x--`) rather than a plain arrowhead. Undefined for a regular
|
|
133
|
+
* `<`-style arrowhead or no marker at all — `hasArrowStart` alone still
|
|
134
|
+
* governs whether anything is drawn there. Currently consumed by the
|
|
135
|
+
* ASCII renderer only (see issue #330); the SVG renderer still draws a
|
|
136
|
+
* plain arrowhead for these.
|
|
137
|
+
*/
|
|
138
|
+
startMarker?: 'circle' | 'cross'
|
|
139
|
+
/** Terminator shape at the target end (`--o`/`--x`). See `startMarker`. */
|
|
140
|
+
endMarker?: 'circle' | 'cross'
|
|
141
|
+
/** Edge id from `A e1@--> B` (Mermaid v11.10.0+), for `e1@{ ... }` and CSS targeting */
|
|
142
|
+
id?: string
|
|
143
|
+
/** Set by `e1@{ animate: true }` — renders as a marching-ants dash */
|
|
144
|
+
animate?: boolean
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* An interaction attached to a node by a `click` statement.
|
|
149
|
+
*
|
|
150
|
+
* This renderer emits static SVG and never executes diagram-supplied script,
|
|
151
|
+
* so a `call`/callback binding is parsed but not invoked — see
|
|
152
|
+
* docs/diagrams.md and docs/decisions/no-script-interactivity.md. An `href`
|
|
153
|
+
* becomes a real SVG link and a tooltip becomes a `<title>`.
|
|
154
|
+
*/
|
|
155
|
+
export interface NodeInteraction {
|
|
156
|
+
/** `click A "https://..."` — rendered as an <a> wrapper */
|
|
157
|
+
href?: string
|
|
158
|
+
/** Link target, e.g. `_blank` */
|
|
159
|
+
target?: string
|
|
160
|
+
/** Tooltip text — rendered as a <title> child */
|
|
161
|
+
tooltip?: string
|
|
162
|
+
/**
|
|
163
|
+
* `click A call fn()` — the raw expression text (`fn()`), exposed as data
|
|
164
|
+
* only. Nothing in the rendered SVG carries it and the library never
|
|
165
|
+
* evaluates it; a host that trusts its diagram source reads it from
|
|
166
|
+
* `parseMermaid(source).interactions` and binds behaviour to the node's
|
|
167
|
+
* `data-id` attribute itself:
|
|
168
|
+
*
|
|
169
|
+
* ```ts
|
|
170
|
+
* for (const [id, { callback }] of parseMermaid(source).interactions) {
|
|
171
|
+
* if (callback === 'showDetail()') {
|
|
172
|
+
* svgRoot.querySelector(`[data-id="${id}"]`)
|
|
173
|
+
* ?.addEventListener('click', () => showDetail(id))
|
|
174
|
+
* }
|
|
175
|
+
* }
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
callback?: string
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export type EdgeStyle =
|
|
182
|
+
| 'solid'
|
|
183
|
+
| 'dotted'
|
|
184
|
+
| 'thick'
|
|
185
|
+
/** `A ~~~ B` — participates in layout but draws no line or arrowhead. */
|
|
186
|
+
| 'invisible'
|
|
187
|
+
|
|
188
|
+
export interface MermaidSubgraph {
|
|
189
|
+
id: string
|
|
190
|
+
label: string
|
|
191
|
+
nodeIds: string[]
|
|
192
|
+
children: MermaidSubgraph[]
|
|
193
|
+
/** Optional direction override for this subgraph's internal layout */
|
|
194
|
+
direction?: Direction
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ============================================================================
|
|
198
|
+
// Positioned graph — after ELK layout, ready for SVG rendering
|
|
199
|
+
// ============================================================================
|
|
200
|
+
|
|
201
|
+
export interface PositionedGraph {
|
|
202
|
+
width: number
|
|
203
|
+
height: number
|
|
204
|
+
nodes: PositionedNode[]
|
|
205
|
+
edges: PositionedEdge[]
|
|
206
|
+
groups: PositionedGroup[]
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export interface PositionedNode {
|
|
210
|
+
id: string
|
|
211
|
+
label: string
|
|
212
|
+
shape: NodeShape
|
|
213
|
+
x: number
|
|
214
|
+
y: number
|
|
215
|
+
width: number
|
|
216
|
+
height: number
|
|
217
|
+
/** Inline styles resolved from classDef + explicit `style` statements — override theme defaults */
|
|
218
|
+
inlineStyle?: Record<string, string>
|
|
219
|
+
/** Custom class name assigned via `class A className` or `:::className` shorthand — emitted onto the rendered element's `class` attribute so external CSS can target it */
|
|
220
|
+
className?: string
|
|
221
|
+
/** Interaction from a `click` statement — an href wraps the node in an <a> */
|
|
222
|
+
interaction?: NodeInteraction
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export interface PositionedEdge {
|
|
226
|
+
source: string
|
|
227
|
+
target: string
|
|
228
|
+
label?: string
|
|
229
|
+
style: EdgeStyle
|
|
230
|
+
hasArrowStart: boolean
|
|
231
|
+
hasArrowEnd: boolean
|
|
232
|
+
/** Full path including bends — array of {x, y} points */
|
|
233
|
+
points: Point[]
|
|
234
|
+
/** Layout-computed label center position (avoids label-label collisions) */
|
|
235
|
+
labelPosition?: Point
|
|
236
|
+
/** Inline styles resolved from `linkStyle` directives — override theme defaults */
|
|
237
|
+
inlineStyle?: Record<string, string>
|
|
238
|
+
/** Edge id from `A e1@--> B`, emitted as data-id for CSS targeting */
|
|
239
|
+
id?: string
|
|
240
|
+
/** Set by `e1@{ animate: true }` — renders as a marching-ants dash */
|
|
241
|
+
animate?: boolean
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export interface Point {
|
|
245
|
+
x: number
|
|
246
|
+
y: number
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export interface PositionedGroup {
|
|
250
|
+
id: string
|
|
251
|
+
label: string
|
|
252
|
+
x: number
|
|
253
|
+
y: number
|
|
254
|
+
width: number
|
|
255
|
+
height: number
|
|
256
|
+
children: PositionedGroup[]
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ============================================================================
|
|
260
|
+
// Render options — user-facing configuration
|
|
261
|
+
//
|
|
262
|
+
// Color theming uses CSS custom properties: --bg and --fg are required,
|
|
263
|
+
// optional enrichment variables (--line, --accent, --muted, --surface,
|
|
264
|
+
// --border) add richer color from Shiki themes or custom palettes.
|
|
265
|
+
// See packages/core/src/theme.ts for the full variable system.
|
|
266
|
+
// ============================================================================
|
|
267
|
+
|
|
268
|
+
export interface RenderOptions {
|
|
269
|
+
/** Background color → CSS variable --bg. Default: '#FFFFFF' */
|
|
270
|
+
bg?: string
|
|
271
|
+
/** Foreground / primary text color → CSS variable --fg. Default: '#27272A' */
|
|
272
|
+
fg?: string
|
|
273
|
+
|
|
274
|
+
// -- Optional enrichment colors (fall back to color-mix from bg/fg) --
|
|
275
|
+
|
|
276
|
+
/** Edge/connector color → CSS variable --line */
|
|
277
|
+
line?: string
|
|
278
|
+
/** Arrow heads, highlights → CSS variable --accent */
|
|
279
|
+
accent?: string
|
|
280
|
+
/** Secondary text, edge labels → CSS variable --muted */
|
|
281
|
+
muted?: string
|
|
282
|
+
/** Node/box fill tint → CSS variable --surface */
|
|
283
|
+
surface?: string
|
|
284
|
+
/** Node/group stroke color → CSS variable --border */
|
|
285
|
+
border?: string
|
|
286
|
+
|
|
287
|
+
/** Font family for all text. Default: 'Inter' */
|
|
288
|
+
font?: string
|
|
289
|
+
/** Canvas padding in px. Default: 40. Flowchart/state diagrams only — class/ER diagrams use fixed internal padding. */
|
|
290
|
+
padding?: number
|
|
291
|
+
/** Horizontal spacing between sibling nodes. Default: 28. Flowchart/state diagrams only — class/ER diagrams use fixed internal spacing. */
|
|
292
|
+
nodeSpacing?: number
|
|
293
|
+
/** Vertical spacing between layers. Default: 48. Flowchart/state diagrams only — class/ER diagrams use fixed internal spacing. */
|
|
294
|
+
layerSpacing?: number
|
|
295
|
+
/** Currently unused — accepted for forward compatibility but not read anywhere. */
|
|
296
|
+
componentSpacing?: number
|
|
297
|
+
/** Whether to bundle overlapping fan-out/fan-in edge paths into shared trunks to reduce visual clutter. Default: true */
|
|
298
|
+
mergeEdges?: boolean
|
|
299
|
+
/**
|
|
300
|
+
* Force the diagram's layout direction, overriding the one its source
|
|
301
|
+
* declares — a flowchart's `graph LR` / `flowchart TD` header, or a state
|
|
302
|
+
* diagram's / ER diagram's top-level `direction LR` line. Applied after
|
|
303
|
+
* parsing and before layout, so the source text is never rewritten and
|
|
304
|
+
* `parseMermaid()` output is unaffected.
|
|
305
|
+
*
|
|
306
|
+
* Replaces only the *top-level* direction. A nested subgraph's or
|
|
307
|
+
* composite state's own `direction` line still applies on top of this
|
|
308
|
+
* override, exactly as it does on top of the diagram's own header — the
|
|
309
|
+
* override behaves as if the caller had written that direction in the
|
|
310
|
+
* source header, nothing more.
|
|
311
|
+
*
|
|
312
|
+
* Flowchart, state, and ER diagrams only — the three diagram types that
|
|
313
|
+
* have a direction concept to override. Sequence, class, and XY-chart
|
|
314
|
+
* diagrams ignore it (no error; output is identical with or without it).
|
|
315
|
+
*
|
|
316
|
+
* Unset (the default) keeps the source's direction, so existing output is
|
|
317
|
+
* unchanged. See issue #276.
|
|
318
|
+
*/
|
|
319
|
+
direction?: Direction
|
|
320
|
+
/** Render with transparent background (no background style on SVG). Default: false */
|
|
321
|
+
transparent?: boolean
|
|
322
|
+
/**
|
|
323
|
+
* Render-target-scoped interactivity level. Declarative only — this
|
|
324
|
+
* library never emits `<script>`; see
|
|
325
|
+
* docs/decisions/no-script-interactivity.md for the tier model this maps
|
|
326
|
+
* to (tier 1: `<title>`/text; tier 2: `<a href>`, CSS `:hover`, CSS
|
|
327
|
+
* animation; tier 3: `click ... call fn()`, recorded as data, never
|
|
328
|
+
* executed). Default: `'static'`.
|
|
329
|
+
*
|
|
330
|
+
* - `'none'` — strips flowchart/state-diagram edge animation
|
|
331
|
+
* (`e1@{ animate: true }`), and strips `click`-based links
|
|
332
|
+
* (`<a href>`) and `<title>` tooltips. Intended for print/rasterized
|
|
333
|
+
* output: a CSS `@keyframes` animation would otherwise silently
|
|
334
|
+
* render as a single static frame with no indication that motion
|
|
335
|
+
* was intended, and a link is meaningless once rasterized.
|
|
336
|
+
* - `'static'` — default. Tier 1 + tier 2 minus motion: `click`-based
|
|
337
|
+
* links and `<title>` tooltips still render, but flowchart/state-diagram
|
|
338
|
+
* edge animation does not — a diagram that opts into
|
|
339
|
+
* `e1@{ animate: true }` renders as a still line unless `'full'` is
|
|
340
|
+
* requested. xychart hover tooltips stay off unless requested via
|
|
341
|
+
* `'full'` or the deprecated `interactive: true` below. This is a
|
|
342
|
+
* breaking change from earlier releases, where `'static'` (and the
|
|
343
|
+
* default) still animated — animation is tier-2 *motion*, which the
|
|
344
|
+
* stricter `'static'` reading excludes; see the ADR.
|
|
345
|
+
* - `'full'` — also enables flowchart/state-diagram edge animation and
|
|
346
|
+
* xychart hover tooltips.
|
|
347
|
+
*/
|
|
348
|
+
interactivity?: 'none' | 'static' | 'full'
|
|
349
|
+
/**
|
|
350
|
+
* @deprecated Use `interactivity` instead. Enable hover tooltips on chart
|
|
351
|
+
* data points (xychart only). Default: false.
|
|
352
|
+
*
|
|
353
|
+
* When `interactivity` is not set, this boolean still controls xychart
|
|
354
|
+
* tooltips as before (`true` behaves like `interactivity: 'full'` for
|
|
355
|
+
* that one effect). When `interactivity` *is* set, it takes precedence
|
|
356
|
+
* and this field is ignored.
|
|
357
|
+
*/
|
|
358
|
+
interactive?: boolean
|
|
359
|
+
/** Stamp the original diagram source onto the root `<svg>` as a `data-src` attribute (HTML-escaped). Default: false */
|
|
360
|
+
embedSource?: boolean
|
|
361
|
+
|
|
362
|
+
// The `src/theme.ts` path below is deliberately left at its pre-#625
|
|
363
|
+
// spelling (this file's `MIX` table now lives at
|
|
364
|
+
// packages/core/src/theme.ts). api-extractor copies this JSDoc verbatim
|
|
365
|
+
// into the published `dist/index.d.ts`, and #625 is verified by that
|
|
366
|
+
// file being byte-identical to the pre-split build — rewording it would
|
|
367
|
+
// break the check that proves the move changed nothing. Correct it in a
|
|
368
|
+
// change that is allowed to move those bytes.
|
|
369
|
+
/**
|
|
370
|
+
* Replace every CSS `var(--…)` and `color-mix(…)` in the output with its
|
|
371
|
+
* computed sRGB value (`#rrggbb`, or `rgba()` when translucent), using
|
|
372
|
+
* the same mix percentages the `<style>` block declares (see
|
|
373
|
+
* `MIX` in src/theme.ts — there is one table, not a copy). Default: false.
|
|
374
|
+
*
|
|
375
|
+
* Browsers evaluate both natively, so the default output stays a live
|
|
376
|
+
* function of its CSS custom properties (docs/theming.md). Rasterizers
|
|
377
|
+
* and non-browser SVG consumers — resvg, librsvg, Inkscape, ImageMagick —
|
|
378
|
+
* implement neither and render the whole theme as black; turn this on
|
|
379
|
+
* for any output headed to one of them (GitHub issue #456).
|
|
380
|
+
*
|
|
381
|
+
* Trade-off: the result is a fixed palette. Overriding `--bg`/`--fg` on
|
|
382
|
+
* the embedded SVG no longer restyles it, and passing a `var(...)`
|
|
383
|
+
* reference as a color (the React live-theming pattern) has nothing to
|
|
384
|
+
* resolve against — such references, and any `var()` this library did
|
|
385
|
+
* not declare itself (e.g. a host-page font variable), are left as-is.
|
|
386
|
+
*/
|
|
387
|
+
resolveColors?: boolean
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* CSP nonce to stamp on every `<style>` element in the output (see GitHub
|
|
391
|
+
* issue #216).
|
|
392
|
+
*
|
|
393
|
+
* The renderer styles its SVG with an inline `<style>` element (the theme
|
|
394
|
+
* block; flowcharts with `e1@{ animate: true }` edges and xycharts add a
|
|
395
|
+
* second one). A host page whose `Content-Security-Policy` has a
|
|
396
|
+
* `style-src` without `'unsafe-inline'` blocks those, and the diagram
|
|
397
|
+
* silently renders unstyled. The standard fix is a per-response nonce:
|
|
398
|
+
* the host generates one, lists it as `style-src 'nonce-<value>'`, and
|
|
399
|
+
* puts `nonce="<value>"` on each element it wants to allow. Pass that
|
|
400
|
+
* value here and every emitted `<style>` gets it — one `<style>` left
|
|
401
|
+
* un-nonced is enough to lose the whole diagram's styling, so this is
|
|
402
|
+
* applied at the single shared emission point rather than per diagram
|
|
403
|
+
* type.
|
|
404
|
+
*
|
|
405
|
+
* The value is attribute-escaped on output. An empty string is treated as
|
|
406
|
+
* unset. Default: undefined (no `nonce` attribute).
|
|
407
|
+
*
|
|
408
|
+
* A nonce only authorises `<style>` *elements* — it cannot authorise a
|
|
409
|
+
* `style="…"` *attribute* (browsers apply nonces to elements only), so the
|
|
410
|
+
* root `<svg style="--bg: …">` attribute stays blocked under the same
|
|
411
|
+
* policy. Pair this with `styleAttribute: false` and put the theme
|
|
412
|
+
* variables in your own stylesheet; see that option.
|
|
413
|
+
*/
|
|
414
|
+
nonce?: string
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Emit the root `<svg style="--bg: …; --fg: …; background: var(--bg)">`
|
|
418
|
+
* attribute. Default: true.
|
|
419
|
+
*
|
|
420
|
+
* That attribute is how the theme colours reach the SVG: every rule in
|
|
421
|
+
* the embedded `<style>` block resolves against the `--bg`/`--fg`/…
|
|
422
|
+
* custom properties it sets. Under a strict `Content-Security-Policy`
|
|
423
|
+
* (`style-src` without `'unsafe-inline'`) the browser drops it, and no
|
|
424
|
+
* `nonce` can rescue it — nonces apply to elements, never attributes —
|
|
425
|
+
* so the diagram loses its colours even when the `<style>` element itself
|
|
426
|
+
* is allowed (see GitHub issue #216).
|
|
427
|
+
*
|
|
428
|
+
* Set this to `false` to leave the attribute out entirely. The host must
|
|
429
|
+
* then define the same custom properties on the SVG or an ancestor from
|
|
430
|
+
* its own (nonced or external) stylesheet; `themeCssVariables(options)`
|
|
431
|
+
* returns the exact declaration list the attribute would have carried,
|
|
432
|
+
* so the two can't drift:
|
|
433
|
+
*
|
|
434
|
+
* ```ts
|
|
435
|
+
* const opts = { bg: '#fff', fg: '#000', nonce, styleAttribute: false }
|
|
436
|
+
* const svg = renderMermaidSVG(code, opts)
|
|
437
|
+
* const css = `.diagram svg { ${themeCssVariables(opts)} }`
|
|
438
|
+
* // `<style nonce="…">${css}</style>` + `<div class="diagram">${svg}</div>`
|
|
439
|
+
* ```
|
|
440
|
+
*
|
|
441
|
+
* With the attribute gone, the SVG has no inline `background` either; the
|
|
442
|
+
* declarations from `themeCssVariables()` include it (unless
|
|
443
|
+
* `transparent`) so the host's rule restores it. Only the *root* `style`
|
|
444
|
+
* attribute is affected: a per-node `style A font-family:…` override
|
|
445
|
+
* still renders as that node's own `style` attribute, since it's diagram
|
|
446
|
+
* content rather than theming, and under such a CSP it is simply ignored
|
|
447
|
+
* by the browser. `nonce` and this option are independent — a host using
|
|
448
|
+
* hashes rather than nonces may want only this one.
|
|
449
|
+
*/
|
|
450
|
+
styleAttribute?: boolean
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Accessible name for the rendered SVG (see GitHub issue #215). Rendered
|
|
454
|
+
* as `role="img"` + `aria-labelledby` pointing at a `<title>` child
|
|
455
|
+
* holding this text — the standard SVG/WAI-ARIA technique for naming an
|
|
456
|
+
* inline image. Without a name, assistive tech either treats the SVG as a
|
|
457
|
+
* plain group (every node/edge label announced individually, out of
|
|
458
|
+
* reading order) or skips it — a WCAG 1.1.1 failure for diagrams embedded
|
|
459
|
+
* in a page.
|
|
460
|
+
*
|
|
461
|
+
* Supply your own description of what the diagram shows (e.g. "Flowchart:
|
|
462
|
+
* Build → Test → Ship") — this library does not auto-generate one, since a
|
|
463
|
+
* fabricated summary like "flowchart with 3 nodes" would be a confidently
|
|
464
|
+
* useless accessible name. When omitted, the SVG still gets `role="img"`
|
|
465
|
+
* (so it's read as one image, not a leaky group) but claims no name — the
|
|
466
|
+
* same as an `<img>` with no `alt`.
|
|
467
|
+
*
|
|
468
|
+
* Ignored when `decorative` is true. Default: undefined (no name).
|
|
469
|
+
*
|
|
470
|
+
* If the diagram has a `click A "url"` link, `role="img"` is never
|
|
471
|
+
* applied regardless of this option — see `decorative` below.
|
|
472
|
+
*/
|
|
473
|
+
title?: string
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Mark the diagram as decorative — already described in surrounding
|
|
477
|
+
* prose, so it shouldn't be announced as its own image. Emits
|
|
478
|
+
* `aria-hidden="true"` on the root `<svg>` instead of
|
|
479
|
+
* `role`/`aria-labelledby`/`<title>`; `title`, if also given, is ignored.
|
|
480
|
+
* Default: false
|
|
481
|
+
*
|
|
482
|
+
* Silently overridden (no `aria-hidden`) if the diagram has any
|
|
483
|
+
* `click A "url"` link: that renders as a real, focusable `<a href>`
|
|
484
|
+
* inside the SVG, and `aria-hidden="true"` on an ancestor of a focusable
|
|
485
|
+
* element is an explicit WAI-ARIA violation — assistive tech would drop
|
|
486
|
+
* the link from the accessibility tree while it stays reachable by Tab.
|
|
487
|
+
* `title`, if also given, still applies in that case (see #239).
|
|
488
|
+
*/
|
|
489
|
+
decorative?: boolean
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Edge path interpolation for flowcharts and state diagrams.
|
|
493
|
+
* Default: 'linear'. A diagram's own
|
|
494
|
+
* `%%{init: {"flowchart": {"curve": ...}}}%%` supplies this when the caller
|
|
495
|
+
* does not; an explicit value here always wins.
|
|
496
|
+
*/
|
|
497
|
+
curve?: CurveStyle
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Font size overrides (px). Fields left unspecified fall back to their
|
|
501
|
+
* default. Applies to all diagram types (flowchart, sequence, class, ER).
|
|
502
|
+
*/
|
|
503
|
+
fontSizes?: {
|
|
504
|
+
/** Node label text. Default: 13 */
|
|
505
|
+
nodeLabel?: number
|
|
506
|
+
/** Edge label text. Default: 11 */
|
|
507
|
+
edgeLabel?: number
|
|
508
|
+
/** Subgraph header text. Default: 12 */
|
|
509
|
+
groupHeader?: number
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Sequence-diagram layout overrides (px). Fields left unspecified fall
|
|
514
|
+
* back to their default. Sequence diagrams only.
|
|
515
|
+
*/
|
|
516
|
+
sequence?: {
|
|
517
|
+
/** Vertical space per message row. Default: 40 */
|
|
518
|
+
messageRowHeight?: number
|
|
519
|
+
/** Vertical space between actor boxes and the first message. Default: 20 */
|
|
520
|
+
headerGap?: number
|
|
521
|
+
/** Actor box height. Default: 40 */
|
|
522
|
+
actorHeight?: number
|
|
523
|
+
/** Gap between a message arrow and a note positioned directly after it. Default: 8 */
|
|
524
|
+
noteOffsetAfterMessage?: number
|
|
525
|
+
/** Gap between consecutively stacked notes. Default: 4 */
|
|
526
|
+
noteStackGap?: number
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Opt-in ELK layout cache (see `createLayoutCache()`). When set,
|
|
531
|
+
* `layoutGraphSync()` / `layoutClassDiagramSync()` / `layoutErDiagramSync()`
|
|
532
|
+
* (flowchart/state, class, and ER diagrams — the ELK-based layout
|
|
533
|
+
* engines) skip re-running ELK layout on a cache hit, keyed on a
|
|
534
|
+
* deterministic serialization of the fully-resolved ELK input graph
|
|
535
|
+
* (diagram structure + every layout-affecting option already baked in).
|
|
536
|
+
*
|
|
537
|
+
* Unset (the default) preserves the original always-recompute behavior
|
|
538
|
+
* exactly — this cache is entirely opt-in. Create one instance and
|
|
539
|
+
* reuse it across renders (e.g. module scope, or a `useRef` in React);
|
|
540
|
+
* passing a freshly-created cache on every call defeats the point,
|
|
541
|
+
* since it starts empty each time.
|
|
542
|
+
*/
|
|
543
|
+
layoutCache?: LayoutCache
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// ============================================================================
|
|
547
|
+
// Per-diagram-type option subsets — issue #534
|
|
548
|
+
//
|
|
549
|
+
// `RenderOptions` above is one flat interface consumed by all five diagram
|
|
550
|
+
// types (flowchart, sequence, class, ER, xychart — see `DiagramType` in
|
|
551
|
+
// packages/core/src/diagram-type.ts; state diagrams share the flowchart pipeline and so
|
|
552
|
+
// share `FlowchartRenderOptions`), but most fields apply to only a subset.
|
|
553
|
+
// Historically that applicability was discoverable only via the prose
|
|
554
|
+
// comments above — passing `curve` to an ER render was silently ignored,
|
|
555
|
+
// never rejected.
|
|
556
|
+
//
|
|
557
|
+
// These `Pick<RenderOptions, ...>` types make each diagram type's actual
|
|
558
|
+
// option surface structural rather than prose-only, confirmed field-by-field
|
|
559
|
+
// against real consumption (grepped across packages/svg-renderer/src/renderer.ts,
|
|
560
|
+
// packages/svg-renderer/src/sequence/renderer.ts, packages/svg-renderer/src/class/renderer.ts,
|
|
561
|
+
// packages/svg-renderer/src/er/renderer.ts, packages/svg-renderer/src/xychart/renderer.ts,
|
|
562
|
+
// and the layout modules each render path calls through —
|
|
563
|
+
// packages/svg-renderer/src/layout-engine.ts, packages/svg-renderer/src/sequence/layout.ts,
|
|
564
|
+
// packages/svg-renderer/src/class/layout.ts, packages/svg-renderer/src/er/layout.ts,
|
|
565
|
+
// packages/svg-renderer/src/xychart/layout.ts).
|
|
566
|
+
//
|
|
567
|
+
// IMPORTANT — this does not narrow `renderMermaidSVG(text, options)` itself.
|
|
568
|
+
// That entry point detects the diagram type from `text` *inside* the
|
|
569
|
+
// function, so the type checker cannot know which per-type subset applies
|
|
570
|
+
// at the call site — `options` there necessarily stays the full flat
|
|
571
|
+
// `RenderOptions` for backwards compatibility. These types exist for:
|
|
572
|
+
// (a) callers who already know their diagram type and want the narrower,
|
|
573
|
+
// self-documenting shape for a local variable or helper signature,
|
|
574
|
+
// and
|
|
575
|
+
// (b) internal module signatures (the `layout*Sync`/`layoutXYChart`
|
|
576
|
+
// functions) that used to accept the full `RenderOptions` and
|
|
577
|
+
// cherry-pick a handful of fields — now typed to only the fields that
|
|
578
|
+
// diagram type actually reads.
|
|
579
|
+
// A structural, compile-time-enforced union at the `renderMermaidSVG` call
|
|
580
|
+
// site itself would need a breaking API change (see the design doc from
|
|
581
|
+
// issue #534's scoping pass) and is out of scope here.
|
|
582
|
+
// ============================================================================
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Options every diagram type honors: theming (colors, font), the shared
|
|
586
|
+
* strict-CSP/accessibility/output controls, and the post-render
|
|
587
|
+
* `resolveColors` pass. Every other `RenderOptions` field applies to only a
|
|
588
|
+
* subset of diagram types — see the per-type types below.
|
|
589
|
+
*/
|
|
590
|
+
export type CommonRenderOptions = Pick<
|
|
591
|
+
RenderOptions,
|
|
592
|
+
| 'bg'
|
|
593
|
+
| 'fg'
|
|
594
|
+
| 'line'
|
|
595
|
+
| 'accent'
|
|
596
|
+
| 'muted'
|
|
597
|
+
| 'surface'
|
|
598
|
+
| 'border'
|
|
599
|
+
| 'font'
|
|
600
|
+
| 'transparent'
|
|
601
|
+
| 'embedSource'
|
|
602
|
+
| 'resolveColors'
|
|
603
|
+
| 'nonce'
|
|
604
|
+
| 'styleAttribute'
|
|
605
|
+
| 'title'
|
|
606
|
+
| 'decorative'
|
|
607
|
+
>
|
|
608
|
+
|
|
609
|
+
// Same as the `MIX` JSDoc above: the two `src/…` paths below keep their
|
|
610
|
+
// pre-#625 spelling (they are now under packages/svg-renderer/src/)
|
|
611
|
+
// because this comment ships verbatim in `dist/index.d.ts`, whose
|
|
612
|
+
// byte-identity is what verifies the move.
|
|
613
|
+
/**
|
|
614
|
+
* Options applicable to flowchart (`graph` / `flowchart`) and state
|
|
615
|
+
* (`stateDiagram-v2`) diagrams — both share `DiagramType: 'flowchart'` and
|
|
616
|
+
* the same layout/render pipeline (src/layout-engine.ts, src/renderer.ts),
|
|
617
|
+
* so they share one option shape. `componentSpacing` is currently a no-op
|
|
618
|
+
* everywhere (accepted for forward compatibility only) but grouped here
|
|
619
|
+
* since it's spacing-shaped like `padding`/`nodeSpacing`/`layerSpacing`.
|
|
620
|
+
*/
|
|
621
|
+
export type FlowchartRenderOptions = CommonRenderOptions &
|
|
622
|
+
Pick<
|
|
623
|
+
RenderOptions,
|
|
624
|
+
| 'padding'
|
|
625
|
+
| 'nodeSpacing'
|
|
626
|
+
| 'layerSpacing'
|
|
627
|
+
| 'componentSpacing'
|
|
628
|
+
| 'mergeEdges'
|
|
629
|
+
| 'direction'
|
|
630
|
+
| 'curve'
|
|
631
|
+
| 'fontSizes'
|
|
632
|
+
| 'interactivity'
|
|
633
|
+
| 'layoutCache'
|
|
634
|
+
>
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Options applicable to sequence diagrams (`sequenceDiagram`). Sequence
|
|
638
|
+
* diagrams ignore `direction`, `curve`, spacing/`layoutCache` (a
|
|
639
|
+
* non-ELK layout), and `interactivity` (no animated edges or `click` links
|
|
640
|
+
* in this renderer's sequence support).
|
|
641
|
+
*/
|
|
642
|
+
export type SequenceRenderOptions = CommonRenderOptions &
|
|
643
|
+
Pick<RenderOptions, 'fontSizes' | 'sequence'>
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* Options applicable to class diagrams (`classDiagram`). Class diagrams use
|
|
647
|
+
* fixed internal padding/spacing (not `padding`/`nodeSpacing`/
|
|
648
|
+
* `layerSpacing`) and have no `direction` or `curve` concept; `interactivity`
|
|
649
|
+
* gates `click`-based links only — there's no edge-animation concept to gate.
|
|
650
|
+
*/
|
|
651
|
+
export type ClassRenderOptions = CommonRenderOptions &
|
|
652
|
+
Pick<RenderOptions, 'fontSizes' | 'interactivity' | 'layoutCache'>
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Options applicable to ER diagrams (`erDiagram`). ER diagrams use fixed
|
|
656
|
+
* internal padding/spacing and ignore `curve` and `interactivity` (no
|
|
657
|
+
* animated edges or `click` links in this renderer's ER support), but do
|
|
658
|
+
* honor `direction` (applied before layout via `withDirectionOverride`).
|
|
659
|
+
*/
|
|
660
|
+
export type ErRenderOptions = CommonRenderOptions &
|
|
661
|
+
Pick<RenderOptions, 'direction' | 'fontSizes' | 'layoutCache'>
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Options applicable to XY charts (`xychart-beta`). XY charts have no
|
|
665
|
+
* spacing/`direction`/`curve`/`fontSizes`/`layoutCache` concept (layout is
|
|
666
|
+
* a fixed pixel computation, not ELK-based); `interactivity` (preferred) and
|
|
667
|
+
* the deprecated `interactive` boolean both gate hover tooltips only.
|
|
668
|
+
*/
|
|
669
|
+
export type XyChartRenderOptions = CommonRenderOptions &
|
|
670
|
+
Pick<RenderOptions, 'interactivity' | 'interactive'>
|