@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/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'>