@hagicode/hagilight-mermaid 0.20.1-dev.194.1.664a29b → 0.20.1-dev.196.1.d67f4e4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -0
- package/dist/adapters.d.ts +25 -0
- package/dist/adapters.js +73 -0
- package/dist/engine.d.ts +14 -0
- package/dist/engine.js +38 -0
- package/dist/entrance.js +2 -0
- package/dist/geometry.d.ts +54 -0
- package/dist/geometry.js +65 -0
- package/dist/graph.d.ts +2 -0
- package/dist/graph.js +37 -14
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/interaction.js +3 -2
- package/dist/labels.js +1 -0
- package/dist/pele-degraded.d.ts +9 -0
- package/dist/pele-degraded.js +14 -0
- package/dist/pele-theme.d.ts +32 -0
- package/dist/pele-theme.js +75 -0
- package/dist/render.js +111 -10
- package/dist/svg.d.ts +6 -4
- package/dist/svg.js +25 -3
- package/dist/theme.d.ts +4 -0
- package/dist/theme.js +4 -1
- package/dist/types.d.ts +38 -0
- package/package.json +8 -2
- package/styles.css +10 -0
package/README.md
CHANGED
|
@@ -10,6 +10,8 @@ npm install @hagicode/hagilight-mermaid mermaid
|
|
|
10
10
|
|
|
11
11
|
`mermaid` is a peer dependency (`^11`). The package never bundles it, and it is imported the first time a non-empty diagram renders.
|
|
12
12
|
|
|
13
|
+
`pele` is an optional peer (`^1.13.0`) for the Pele engine described below. Install it only if a site selects Pele: `npm install pele@^1.13.0`. A site that never selects Pele does not need it.
|
|
14
|
+
|
|
13
15
|
## Quick start
|
|
14
16
|
|
|
15
17
|
```ts
|
|
@@ -46,8 +48,47 @@ handle.destroy(); // discard pending renders and empty the element
|
|
|
46
48
|
| `viewer` | `true` | Adds an enlarge button to the toolbar and makes `handle.expand()` open the viewer. `false` removes both. |
|
|
47
49
|
| `fit` | `'width'` | `'width'` fills the host's width and lets the height follow. `'contain'` fits the whole diagram in the host's height, for fixed-height tiles: give the host a height. |
|
|
48
50
|
| `loadMermaid` | `import('mermaid')` | Supplies the `mermaid` module. Use it to control chunking or to test. |
|
|
51
|
+
| `engine` | `'mermaid'` | `'mermaid'` or `'pele'`. Resolved from this option, then the stored choice (see below), then `'mermaid'`. An unrecognized value is ignored. |
|
|
52
|
+
| `peleLoader` | `import('pele')` | Supplies the `pele` module when the Pele engine renders. Use it to control chunking or to test. |
|
|
49
53
|
| `className` | | Extra class names on the diagram container. |
|
|
50
54
|
|
|
55
|
+
## Pele engine (optional)
|
|
56
|
+
|
|
57
|
+
[Pele](https://pele.run) renders the same Mermaid source to SVG without Mermaid's runtime. It is synchronous and small. Mermaid stays the default, so a site that does nothing keeps its current output.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { mountMermaidDiagrams, writeStoredEngine } from '@hagicode/hagilight-mermaid';
|
|
61
|
+
|
|
62
|
+
mountMermaidDiagrams(document, { engine: 'pele' }); // this page, whatever is stored
|
|
63
|
+
writeStoredEngine('pele'); // remember the choice in this browser for later renders
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The engine is resolved in this order, and an unrecognized value is ignored:
|
|
67
|
+
|
|
68
|
+
1. the `engine` option, when given (so `engine: 'mermaid'` outranks a stored `pele`);
|
|
69
|
+
2. the choice stored under the key `hagilight-mermaid:engine` in `localStorage`;
|
|
70
|
+
3. `mermaid`.
|
|
71
|
+
|
|
72
|
+
Rendering never writes the stored choice. Only `writeStoredEngine` does, so `update({ engine })` for one page does not change later pages. `readStoredEngine()` returns the stored value or `undefined`. Storage that throws or is absent is treated as no choice, and no call here throws.
|
|
73
|
+
|
|
74
|
+
**Version.** `pele` is pinned to `^1.13.0` as a peer. The package's tests render one sample per supported type with the installed Pele and fail if a marker the adapters need (node and edge classes, `data-id`, `aria-roledescription`) is missing. Pele has no release page, so a Pele upgrade is checked by those tests before it is accepted.
|
|
75
|
+
|
|
76
|
+
**What Pele changes.** The theme maps `--hagilight-mermaid-*` tokens to Pele's `--pele-*` variables on the container, so a mode or preset change restyles the diagram without calling Pele again. Pie series colors are set explicitly. The error panel shows Pele's position and writes every message with `textContent`.
|
|
77
|
+
|
|
78
|
+
**Flowchart, state, class and ER under Pele.** Tracing, keyboard focus and the dependency-order reveal use the geometry of the rendered SVG: each edge's first and last points are matched to the nearest node box. Edge identifiers are never read for endpoints. For class and ER relations the drawn direction, from the start of the line to its end, is the trace direction. ER relations have no true direction, so this is a convention: in the fixtures the line leaves the entity named first in the relation (`Animal` to `Duck`, `CUSTOMER` to `ORDER`).
|
|
79
|
+
|
|
80
|
+
**Other Pele types** (pie, sequence and the rest) reveal with the generic reveal and have no part focus in this version. Diagram types listed in the degraded set show a notice above the diagram.
|
|
81
|
+
|
|
82
|
+
**Degraded types.** Pele draws these types in a clearly simpler way, so the container shows the notice `peleDegradedNotice` (overridable through `labels`) above the diagram:
|
|
83
|
+
|
|
84
|
+
- `c4`: the scaled-down diagram's text is hard to read at the same width.
|
|
85
|
+
- `gitGraph`: branch labels sit on mid-tone fills and are hard to read.
|
|
86
|
+
- `treemap`: leaf labels are small and light on their fills.
|
|
87
|
+
|
|
88
|
+
The list was set from a same-size screenshot comparison of the 36 demo samples, both engines at 440 px. It is a constant in the package and is extended only with that kind of comparison.
|
|
89
|
+
|
|
90
|
+
**Phase 2 limits.** Part focus and type-specific reveals for pie, sequence and the other types are not included. Pele's own `mount()` is not used, so its zoom and resize behaviour is not available. Layouts differ from Mermaid's, so a Pele diagram is not a pixel match for the Mermaid one.
|
|
91
|
+
|
|
51
92
|
## What each diagram type does
|
|
52
93
|
|
|
53
94
|
Every Mermaid diagram type moves and responds, not only the ones with nodes.
|
|
@@ -104,6 +145,8 @@ Blue and teal lead the order, then amber and violet, so neighbouring series neve
|
|
|
104
145
|
To use a site's own names, pass `tokens` instead of redefining the package tokens:
|
|
105
146
|
|
|
106
147
|
```ts
|
|
148
|
+
import { mountMermaidDiagrams } from '@hagicode/hagilight-mermaid';
|
|
149
|
+
|
|
107
150
|
mountMermaidDiagrams(document, {
|
|
108
151
|
tokens: { background: '--background', foreground: '--foreground', primary: '--primary', accent: '--accent', primaryForeground: '--primary-foreground' },
|
|
109
152
|
});
|
|
@@ -116,6 +159,8 @@ Colors in `oklch()` or `lab()` are converted to `rgb()` through a canvas before
|
|
|
116
159
|
All visible text comes from `labels`. Unspecified keys keep the English defaults, and `{value}` is replaced by the count in trace labels.
|
|
117
160
|
|
|
118
161
|
```ts
|
|
162
|
+
import { mountMermaidDiagrams } from '@hagicode/hagilight-mermaid';
|
|
163
|
+
|
|
119
164
|
mountMermaidDiagrams(document, {
|
|
120
165
|
labels: {
|
|
121
166
|
rendering: 'Rendering diagram...',
|
|
@@ -158,6 +203,7 @@ With Mermaid 11.16.1, every diagram type the demo covers renders an SVG, and non
|
|
|
158
203
|
- **Part focus and the reveal** read Mermaid 11.16.1's SVG structure, which has no shared shape across diagram types. A later Mermaid release can rename a class: the affected type then loses its part focus, and its reveal falls back to the generic one, until the table in `src/adapters.ts` is updated. The browser checks pin the installed version.
|
|
159
204
|
- **Thin lines** (a sequence message, a git branch) are hard to hit on their own, so the part includes its label: hover the label or the text beside the line.
|
|
160
205
|
- **Block**, **kanban**, **architecture** and **Wardley** have no dependency graph the tracing can read, so they get part focus rather than upstream and downstream paths.
|
|
206
|
+
- **Pele engine**: the layout is Pele's own, not Mermaid's. The degraded list above is the only place that says where Pele draws less clearly. Part focus and type-specific reveals wait for a later phase.
|
|
161
207
|
- **Text halo**: the default preset draws a halo behind diagram text, white in light mode and `#111827` in dark mode. Set `--hagilight-mermaid-halo` to the page color when it differs. Text printed on a colored mark (pie slices, Gantt bars, timeline and branch labels) has no halo.
|
|
162
208
|
|
|
163
209
|
## Parity
|
package/dist/adapters.d.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* through the graph model instead. Every selector here was read from Mermaid 11.16.1 output; a type
|
|
11
11
|
* the table does not know falls back to its top-level pieces, so a new Mermaid diagram still moves.
|
|
12
12
|
*/
|
|
13
|
+
import type { Box, EdgeEnds } from './geometry.js';
|
|
13
14
|
export type BeatKind = 'fade' | 'rise' | 'drop' | 'pop' | 'growX' | 'growY' | 'draw' | 'swing' | 'wipe';
|
|
14
15
|
export interface Beat {
|
|
15
16
|
els: Element[];
|
|
@@ -40,3 +41,27 @@ export declare function entranceBeats(svg: Element): Beat[];
|
|
|
40
41
|
* is the second part, so they name it explicitly.
|
|
41
42
|
*/
|
|
42
43
|
export declare function describeParts(svg: Element): PartFamily[];
|
|
44
|
+
/** Pele's element for one node or edge of a graph, with what the geometry needs and nothing parsed from ids. */
|
|
45
|
+
export interface PeleGraphNodePart {
|
|
46
|
+
el: SVGGElement;
|
|
47
|
+
/** The node's `data-id`, the stable key. Never parsed for endpoints. */
|
|
48
|
+
key: string;
|
|
49
|
+
label: string;
|
|
50
|
+
box: Box;
|
|
51
|
+
}
|
|
52
|
+
export interface PeleGraphEdgePart {
|
|
53
|
+
/** The line of the edge: the first child path that is not a marker. */
|
|
54
|
+
el: SVGPathElement;
|
|
55
|
+
/** Arrowheads and other `pele-marker` children, which travel with the line. */
|
|
56
|
+
markers: SVGElement[];
|
|
57
|
+
ends: EdgeEnds;
|
|
58
|
+
}
|
|
59
|
+
export interface PeleGraphParts {
|
|
60
|
+
nodes: PeleGraphNodePart[];
|
|
61
|
+
edges: PeleGraphEdgePart[];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The nodes and edges of a Pele flowchart, state, class or ER diagram, for the geometry matcher. Other Pele
|
|
65
|
+
* types have no graph here and return null, so they reveal and focus by their own parts instead.
|
|
66
|
+
*/
|
|
67
|
+
export declare function peleGraphParts(svg: SVGSVGElement): PeleGraphParts | null;
|
package/dist/adapters.js
CHANGED
|
@@ -357,3 +357,76 @@ export function describeParts(svg) {
|
|
|
357
357
|
return family;
|
|
358
358
|
});
|
|
359
359
|
}
|
|
360
|
+
/** Pele types whose graph is read from geometry: a node box per `g.pele-node`, an edge per `g.pele-edge`. */
|
|
361
|
+
const PELE_GRAPH_TYPES = new Set(['flowchart', 'state', 'class', 'er']);
|
|
362
|
+
/**
|
|
363
|
+
* The element's matrix into the root SVG's user space (its viewBox coordinates, the units Pele draws in). A
|
|
364
|
+
* child's own CTM also carries the viewBox-to-viewport scale, so that scale is divided out here. Every
|
|
365
|
+
* geometry check uses this one space, so distances are measured in the same units as the drawing.
|
|
366
|
+
*/
|
|
367
|
+
function toUserSpace(svg, el) {
|
|
368
|
+
const root = svg.getCTM();
|
|
369
|
+
const own = el.getCTM();
|
|
370
|
+
if (!root || !own)
|
|
371
|
+
return null;
|
|
372
|
+
return root.inverse().multiply(own);
|
|
373
|
+
}
|
|
374
|
+
/** A box in the root SVG's user space: the element's bounding box, carried through its own transform. */
|
|
375
|
+
function boxInRoot(svg, el) {
|
|
376
|
+
if (typeof el.getBBox !== 'function' || typeof el.getCTM !== 'function')
|
|
377
|
+
return null;
|
|
378
|
+
const bounds = el.getBBox();
|
|
379
|
+
const matrix = toUserSpace(svg, el);
|
|
380
|
+
if (!matrix || (bounds.width === 0 && bounds.height === 0))
|
|
381
|
+
return null;
|
|
382
|
+
const corners = [
|
|
383
|
+
[bounds.x, bounds.y],
|
|
384
|
+
[bounds.x + bounds.width, bounds.y],
|
|
385
|
+
[bounds.x, bounds.y + bounds.height],
|
|
386
|
+
[bounds.x + bounds.width, bounds.y + bounds.height],
|
|
387
|
+
].map(([x, y]) => ({ x: matrix.a * x + matrix.c * y + matrix.e, y: matrix.b * x + matrix.d * y + matrix.f }));
|
|
388
|
+
const xs = corners.map((corner) => corner.x);
|
|
389
|
+
const ys = corners.map((corner) => corner.y);
|
|
390
|
+
return { left: Math.min(...xs), top: Math.min(...ys), right: Math.max(...xs), bottom: Math.max(...ys) };
|
|
391
|
+
}
|
|
392
|
+
/** A point along a path, in the same root user space as `boxInRoot`. */
|
|
393
|
+
function pointInRoot(svg, path, distance) {
|
|
394
|
+
const local = path.getPointAtLength(distance);
|
|
395
|
+
const matrix = toUserSpace(svg, path);
|
|
396
|
+
if (!matrix)
|
|
397
|
+
return { x: local.x, y: local.y };
|
|
398
|
+
return { x: matrix.a * local.x + matrix.c * local.y + matrix.e, y: matrix.b * local.x + matrix.d * local.y + matrix.f };
|
|
399
|
+
}
|
|
400
|
+
function peleLabel(node) {
|
|
401
|
+
for (const label of Array.from(node.querySelectorAll('.pele-label'))) {
|
|
402
|
+
const text = textOf(label);
|
|
403
|
+
if (text)
|
|
404
|
+
return text;
|
|
405
|
+
}
|
|
406
|
+
return '';
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* The nodes and edges of a Pele flowchart, state, class or ER diagram, for the geometry matcher. Other Pele
|
|
410
|
+
* types have no graph here and return null, so they reveal and focus by their own parts instead.
|
|
411
|
+
*/
|
|
412
|
+
export function peleGraphParts(svg) {
|
|
413
|
+
if (!PELE_GRAPH_TYPES.has(diagramType(svg)))
|
|
414
|
+
return null;
|
|
415
|
+
const nodes = [];
|
|
416
|
+
svg.querySelectorAll('g.pele-node[data-id]').forEach((el) => {
|
|
417
|
+
const box = boxInRoot(svg, el);
|
|
418
|
+
if (!box)
|
|
419
|
+
return;
|
|
420
|
+
nodes.push({ el, key: el.getAttribute('data-id') ?? '', label: peleLabel(el), box });
|
|
421
|
+
});
|
|
422
|
+
const edges = [];
|
|
423
|
+
svg.querySelectorAll('g.pele-edge').forEach((group) => {
|
|
424
|
+
const line = Array.from(group.children).find((child) => child.tagName.toLowerCase() === 'path' && !child.classList.contains('pele-marker'));
|
|
425
|
+
if (!line || typeof line.getTotalLength !== 'function')
|
|
426
|
+
return;
|
|
427
|
+
const length = line.getTotalLength();
|
|
428
|
+
const markers = Array.from(group.querySelectorAll('.pele-marker'));
|
|
429
|
+
edges.push({ el: line, markers, ends: { start: pointInRoot(svg, line, 0), end: pointInRoot(svg, line, length) } });
|
|
430
|
+
});
|
|
431
|
+
return { nodes, edges };
|
|
432
|
+
}
|
package/dist/engine.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { MermaidEngine } from './types.js';
|
|
2
|
+
/** Storage key for the engine a visitor chose. The package never writes it on its own. */
|
|
3
|
+
export declare const ENGINE_STORAGE_KEY = "hagilight-mermaid:engine";
|
|
4
|
+
/** True for `mermaid` and `pele`. Anything else counts as no choice. */
|
|
5
|
+
export declare function isMermaidEngine(value: unknown): value is MermaidEngine;
|
|
6
|
+
/**
|
|
7
|
+
* The engine a render uses: the option when it is recognized, then the stored choice, then `mermaid`.
|
|
8
|
+
* An unrecognized option is ignored, so the next source in the order decides.
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveEngine(option?: unknown): MermaidEngine;
|
|
11
|
+
/** The stored engine choice, or `undefined` when none is stored, the value is unrecognized, or storage is unavailable. */
|
|
12
|
+
export declare function readStoredEngine(): MermaidEngine | undefined;
|
|
13
|
+
/** Stores the engine choice for later renders with no explicit option. Unrecognized values and storage failures do nothing. */
|
|
14
|
+
export declare function writeStoredEngine(engine: MermaidEngine): void;
|
package/dist/engine.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** Storage key for the engine a visitor chose. The package never writes it on its own. */
|
|
2
|
+
export const ENGINE_STORAGE_KEY = 'hagilight-mermaid:engine';
|
|
3
|
+
const ENGINES = ['mermaid', 'pele'];
|
|
4
|
+
/** True for `mermaid` and `pele`. Anything else counts as no choice. */
|
|
5
|
+
export function isMermaidEngine(value) {
|
|
6
|
+
return typeof value === 'string' && ENGINES.includes(value);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The engine a render uses: the option when it is recognized, then the stored choice, then `mermaid`.
|
|
10
|
+
* An unrecognized option is ignored, so the next source in the order decides.
|
|
11
|
+
*/
|
|
12
|
+
export function resolveEngine(option) {
|
|
13
|
+
if (isMermaidEngine(option))
|
|
14
|
+
return option;
|
|
15
|
+
return readStoredEngine() ?? 'mermaid';
|
|
16
|
+
}
|
|
17
|
+
/** The stored engine choice, or `undefined` when none is stored, the value is unrecognized, or storage is unavailable. */
|
|
18
|
+
export function readStoredEngine() {
|
|
19
|
+
try {
|
|
20
|
+
const value = globalThis.localStorage?.getItem(ENGINE_STORAGE_KEY);
|
|
21
|
+
return isMermaidEngine(value) ? value : undefined;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// Blocked or throwing storage: rendering falls back to the default engine.
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Stores the engine choice for later renders with no explicit option. Unrecognized values and storage failures do nothing. */
|
|
29
|
+
export function writeStoredEngine(engine) {
|
|
30
|
+
if (!isMermaidEngine(engine))
|
|
31
|
+
return;
|
|
32
|
+
try {
|
|
33
|
+
globalThis.localStorage?.setItem(ENGINE_STORAGE_KEY, engine);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// The control still switches the engine for this page; the choice is just not remembered.
|
|
37
|
+
}
|
|
38
|
+
}
|
package/dist/entrance.js
CHANGED
|
@@ -76,6 +76,8 @@ export function playMermaidEntrance(svg, graph) {
|
|
|
76
76
|
draw.finished.then(land, land);
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
|
+
// Pele draws its arrowhead apart from the line; it arrives as the line lands, so it never leads the edge.
|
|
80
|
+
edge.markers.forEach((marker) => add(marker, [{ opacity: 0 }, { opacity: 1 }], { duration: edgeMs, delay: delay + edgeMs * 0.8 }));
|
|
79
81
|
if (edge.labelEl) {
|
|
80
82
|
add(edge.labelEl, [{ opacity: 0 }, { opacity: 1 }], { duration: LABEL_MS, delay: delay + edgeMs * 0.65 });
|
|
81
83
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure matching of edge end points to node boxes, for Pele graphs. Plain numbers in, index out: the DOM layer
|
|
3
|
+
* (adapters.ts) reads the boxes and end points in one coordinate space, and this file decides which node
|
|
4
|
+
* each end belongs to. Edge identifiers are never read, because they do not reliably name their ends.
|
|
5
|
+
*/
|
|
6
|
+
export interface Box {
|
|
7
|
+
left: number;
|
|
8
|
+
top: number;
|
|
9
|
+
right: number;
|
|
10
|
+
bottom: number;
|
|
11
|
+
}
|
|
12
|
+
export interface Point {
|
|
13
|
+
x: number;
|
|
14
|
+
y: number;
|
|
15
|
+
}
|
|
16
|
+
export interface EdgeEnds {
|
|
17
|
+
start: Point;
|
|
18
|
+
end: Point;
|
|
19
|
+
}
|
|
20
|
+
export interface LinkedEdge {
|
|
21
|
+
/** Index into the edges passed in. */
|
|
22
|
+
edge: number;
|
|
23
|
+
/** Index into the boxes passed in: the node the edge leaves. */
|
|
24
|
+
from: number;
|
|
25
|
+
/** Index into the boxes passed in: the node the edge enters. */
|
|
26
|
+
to: number;
|
|
27
|
+
}
|
|
28
|
+
/** A point this close outside a box still counts as touching it, for rounded borders and sub-pixel error. */
|
|
29
|
+
export declare const CONTAINMENT_TOLERANCE = 2;
|
|
30
|
+
/**
|
|
31
|
+
* The widest gap, in viewBox units, between an end point and a box that still matches. Pele stops a line short
|
|
32
|
+
* of its node to leave room for the arrowhead: about 7 units for flowchart arrows and about 12 for class
|
|
33
|
+
* relations in the probe, so 16 covers both with a margin.
|
|
34
|
+
*/
|
|
35
|
+
export declare const MATCH_LIMIT = 16;
|
|
36
|
+
/** Distance from a point to a box: zero inside it, otherwise the gap to the nearest edge or corner. */
|
|
37
|
+
export declare function distanceToBox(point: Point, box: Box): number;
|
|
38
|
+
/**
|
|
39
|
+
* The box a point belongs to, as an index, or -1 when none is close enough. A point inside a box (with the
|
|
40
|
+
* tolerance) wins, and of several such boxes the smallest one, so a node nested in a cluster is the node.
|
|
41
|
+
* Otherwise the nearest box within `limit` wins, and between equally near boxes the smaller one.
|
|
42
|
+
*/
|
|
43
|
+
export declare function matchPoint(point: Point, boxes: readonly Box[], { tolerance, limit }?: {
|
|
44
|
+
tolerance?: number;
|
|
45
|
+
limit?: number;
|
|
46
|
+
}): number;
|
|
47
|
+
/**
|
|
48
|
+
* Links each edge from the node its start point belongs to to the node its end point belongs to. An edge whose
|
|
49
|
+
* end matches no box, or whose two ends match the same box (a self-loop), is left out; the rest still link.
|
|
50
|
+
*/
|
|
51
|
+
export declare function linkEdges(edges: readonly EdgeEnds[], boxes: readonly Box[], options?: {
|
|
52
|
+
tolerance?: number;
|
|
53
|
+
limit?: number;
|
|
54
|
+
}): LinkedEdge[];
|
package/dist/geometry.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure matching of edge end points to node boxes, for Pele graphs. Plain numbers in, index out: the DOM layer
|
|
3
|
+
* (adapters.ts) reads the boxes and end points in one coordinate space, and this file decides which node
|
|
4
|
+
* each end belongs to. Edge identifiers are never read, because they do not reliably name their ends.
|
|
5
|
+
*/
|
|
6
|
+
/** A point this close outside a box still counts as touching it, for rounded borders and sub-pixel error. */
|
|
7
|
+
export const CONTAINMENT_TOLERANCE = 2;
|
|
8
|
+
/**
|
|
9
|
+
* The widest gap, in viewBox units, between an end point and a box that still matches. Pele stops a line short
|
|
10
|
+
* of its node to leave room for the arrowhead: about 7 units for flowchart arrows and about 12 for class
|
|
11
|
+
* relations in the probe, so 16 covers both with a margin.
|
|
12
|
+
*/
|
|
13
|
+
export const MATCH_LIMIT = 16;
|
|
14
|
+
const area = (box) => (box.right - box.left) * (box.bottom - box.top);
|
|
15
|
+
/** Distance from a point to a box: zero inside it, otherwise the gap to the nearest edge or corner. */
|
|
16
|
+
export function distanceToBox(point, box) {
|
|
17
|
+
const dx = Math.max(box.left - point.x, 0, point.x - box.right);
|
|
18
|
+
const dy = Math.max(box.top - point.y, 0, point.y - box.bottom);
|
|
19
|
+
return Math.hypot(dx, dy);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The box a point belongs to, as an index, or -1 when none is close enough. A point inside a box (with the
|
|
23
|
+
* tolerance) wins, and of several such boxes the smallest one, so a node nested in a cluster is the node.
|
|
24
|
+
* Otherwise the nearest box within `limit` wins, and between equally near boxes the smaller one.
|
|
25
|
+
*/
|
|
26
|
+
export function matchPoint(point, boxes, { tolerance = CONTAINMENT_TOLERANCE, limit = MATCH_LIMIT } = {}) {
|
|
27
|
+
let best = -1;
|
|
28
|
+
let bestArea = Infinity;
|
|
29
|
+
boxes.forEach((box, index) => {
|
|
30
|
+
const inside = point.x >= box.left - tolerance && point.x <= box.right + tolerance && point.y >= box.top - tolerance && point.y <= box.bottom + tolerance;
|
|
31
|
+
if (inside && area(box) < bestArea) {
|
|
32
|
+
best = index;
|
|
33
|
+
bestArea = area(box);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
if (best !== -1)
|
|
37
|
+
return best;
|
|
38
|
+
let bestDistance = Infinity;
|
|
39
|
+
boxes.forEach((box, index) => {
|
|
40
|
+
const distance = distanceToBox(point, box);
|
|
41
|
+
if (distance > limit)
|
|
42
|
+
return;
|
|
43
|
+
if (distance < bestDistance || (distance === bestDistance && area(box) < bestArea)) {
|
|
44
|
+
best = index;
|
|
45
|
+
bestDistance = distance;
|
|
46
|
+
bestArea = area(box);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
return best;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Links each edge from the node its start point belongs to to the node its end point belongs to. An edge whose
|
|
53
|
+
* end matches no box, or whose two ends match the same box (a self-loop), is left out; the rest still link.
|
|
54
|
+
*/
|
|
55
|
+
export function linkEdges(edges, boxes, options) {
|
|
56
|
+
const linked = [];
|
|
57
|
+
edges.forEach((ends, edge) => {
|
|
58
|
+
const from = matchPoint(ends.start, boxes, options);
|
|
59
|
+
const to = matchPoint(ends.end, boxes, options);
|
|
60
|
+
if (from === -1 || to === -1 || from === to)
|
|
61
|
+
return;
|
|
62
|
+
linked.push({ edge, from, to });
|
|
63
|
+
});
|
|
64
|
+
return linked;
|
|
65
|
+
}
|
package/dist/graph.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ export interface MermaidGraphEdge {
|
|
|
18
18
|
from: number;
|
|
19
19
|
to: number;
|
|
20
20
|
labelEl: SVGGElement | null;
|
|
21
|
+
/** Arrowheads drawn apart from the line (Pele). They reveal and dim with their edge. Empty for Mermaid. */
|
|
22
|
+
markers: SVGElement[];
|
|
21
23
|
}
|
|
22
24
|
export interface MermaidGraph {
|
|
23
25
|
nodes: MermaidGraphNode[];
|
package/dist/graph.js
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* requirement, mindmap, swimlane) emits `g.nodes > g.node` and `g.edgePaths > path`, and Sankey draws
|
|
4
|
-
* its links as `g.links > g.link > path`, so the model is read from the DOM instead of re-parsing the
|
|
5
|
-
* diagram source. Diagram types without that structure (sequence, gantt, ...) yield an empty graph;
|
|
6
|
-
* `adapters.ts` describes their parts instead.
|
|
7
|
-
*/
|
|
1
|
+
import { linkEdges } from './geometry.js';
|
|
2
|
+
import { peleGraphParts } from './adapters.js';
|
|
8
3
|
const NODE_KEY = /-(?:flowchart|state|class|classId|er)-(.+?)-\d+$/;
|
|
9
4
|
/** Flowchart-style edges are `L_<from>_<to>_<n>`; class diagrams use `id_<from>_<to>_<n>`. */
|
|
10
5
|
const EDGE_ID = /^(?:L|id)_(.+)_\d+$/;
|
|
@@ -64,7 +59,40 @@ function nearestNode(boxes, x, y) {
|
|
|
64
59
|
});
|
|
65
60
|
return best;
|
|
66
61
|
}
|
|
62
|
+
/** Pele output carries the `data-mermaid-engine` marker the post-processor sets, or at least its `pele` root class. */
|
|
63
|
+
function isPeleSvg(svg) {
|
|
64
|
+
return svg.getAttribute('data-mermaid-engine') === 'pele' || svg.classList.contains('pele');
|
|
65
|
+
}
|
|
66
|
+
/** Builds the graph of a Pele flowchart, state, class or ER diagram from geometry. Ids are keys, never endpoints. */
|
|
67
|
+
function buildPeleGraph(svg) {
|
|
68
|
+
const parts = peleGraphParts(svg);
|
|
69
|
+
const nodes = (parts?.nodes ?? []).map((part, index) => ({
|
|
70
|
+
index,
|
|
71
|
+
el: part.el,
|
|
72
|
+
key: part.key,
|
|
73
|
+
label: part.label,
|
|
74
|
+
}));
|
|
75
|
+
const edges = [];
|
|
76
|
+
for (const link of linkEdges(parts?.edges.map((edge) => edge.ends) ?? [], parts?.nodes.map((node) => node.box) ?? [])) {
|
|
77
|
+
const edge = parts.edges[link.edge];
|
|
78
|
+
edges.push({ index: edges.length, el: edge.el, from: link.from, to: link.to, labelEl: null, markers: edge.markers });
|
|
79
|
+
}
|
|
80
|
+
return finishGraph(nodes, edges);
|
|
81
|
+
}
|
|
82
|
+
/** Per-node adjacency from the edge list. */
|
|
83
|
+
function finishGraph(nodes, edges) {
|
|
84
|
+
const out = nodes.map(() => []);
|
|
85
|
+
const inc = nodes.map(() => []);
|
|
86
|
+
edges.forEach((edge) => {
|
|
87
|
+
out[edge.from].push(edge.index);
|
|
88
|
+
inc[edge.to].push(edge.index);
|
|
89
|
+
});
|
|
90
|
+
return { nodes, edges, out, inc };
|
|
91
|
+
}
|
|
67
92
|
export function buildMermaidGraph(svg) {
|
|
93
|
+
// The adapter set follows the SVG's origin: Pele's geometry for Pele output, Mermaid's classes otherwise.
|
|
94
|
+
if (isPeleSvg(svg))
|
|
95
|
+
return buildPeleGraph(svg);
|
|
68
96
|
const nodeEls = Array.from(svg.querySelectorAll('g.nodes > g.node'));
|
|
69
97
|
const edgeEls = Array.from(svg.querySelectorAll(EDGE_PATHS));
|
|
70
98
|
const nodes = nodeEls.map((el, index) => ({
|
|
@@ -113,15 +141,10 @@ export function buildMermaidGraph(svg) {
|
|
|
113
141
|
from: ends[0],
|
|
114
142
|
to: ends[1],
|
|
115
143
|
labelEl: labelById.get(dataId)?.closest('g.edgeLabel') ?? null,
|
|
144
|
+
markers: [],
|
|
116
145
|
});
|
|
117
146
|
});
|
|
118
|
-
|
|
119
|
-
const inc = nodes.map(() => []);
|
|
120
|
-
edges.forEach((edge) => {
|
|
121
|
-
out[edge.from].push(edge.index);
|
|
122
|
-
inc[edge.to].push(edge.index);
|
|
123
|
-
});
|
|
124
|
-
return { nodes, edges, out, inc };
|
|
147
|
+
return finishGraph(nodes, edges);
|
|
125
148
|
}
|
|
126
149
|
/**
|
|
127
150
|
* Longest-path layering with back edges ignored, so cycles (retry loops) cannot push a node past
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { renderMermaid } from './render.js';
|
|
2
2
|
export { mountMermaidDiagrams } from './mount.js';
|
|
3
|
-
export
|
|
3
|
+
export { ENGINE_STORAGE_KEY, readStoredEngine, resolveEngine, writeStoredEngine } from './engine.js';
|
|
4
|
+
export type { MermaidEngine, MermaidHandle, MermaidInteractionConfig, MermaidLabelOverrides, MermaidLabels, MermaidMode, MermaidMountHandle, MermaidMountOptions, MermaidPreset, MermaidRenderOptions, MermaidTokenRole, MermaidTokens, MermaidTraceSummary, MermaidUpdate, PeleErrorShape, PeleModule, PeleRenderResult, } from './types.js';
|
package/dist/index.js
CHANGED
package/dist/interaction.js
CHANGED
|
@@ -129,7 +129,8 @@ export function attachMermaidDiagramInteraction(root, config = {}, prebuilt) {
|
|
|
129
129
|
graph.edges.forEach((edge) => {
|
|
130
130
|
const up = trace.upstreamEdges.has(edge.index);
|
|
131
131
|
const down = trace.downstreamEdges.has(edge.index);
|
|
132
|
-
|
|
132
|
+
// Arrowheads drawn apart from the line (Pele) dim and light with their edge.
|
|
133
|
+
const targets = [edge.el, ...edge.markers, ...(edge.labelEl ? [edge.labelEl] : [])];
|
|
133
134
|
targets.forEach((el) => {
|
|
134
135
|
if (!up && !down)
|
|
135
136
|
el.classList.add('mermaid-trace-dim');
|
|
@@ -155,7 +156,7 @@ export function attachMermaidDiagramInteraction(root, config = {}, prebuilt) {
|
|
|
155
156
|
return;
|
|
156
157
|
layer = svgEl('g', 'mermaid-trace-layer');
|
|
157
158
|
layer.setAttribute('aria-hidden', 'true');
|
|
158
|
-
host.insertBefore(layer, svg.querySelector('g.edgePaths')?.nextSibling ?? null);
|
|
159
|
+
host.insertBefore(layer, svg.querySelector('g.edgePaths, g.pele-edges')?.nextSibling ?? null);
|
|
159
160
|
edgesWithHop.forEach(({ index, delay, direction }) => {
|
|
160
161
|
const source = graph.edges[index].el;
|
|
161
162
|
const length = typeof source.getTotalLength === 'function' ? source.getTotalLength() : 0;
|
package/dist/labels.js
CHANGED
|
@@ -7,6 +7,7 @@ export const DEFAULT_LABELS = {
|
|
|
7
7
|
renderingErrorPrefix: 'Rendering error:',
|
|
8
8
|
noValidDiagram: 'No valid diagram detected',
|
|
9
9
|
unknownDiagramType: 'Unknown diagram type',
|
|
10
|
+
peleDegradedNotice: 'This diagram type is simplified by the Pele engine. Switch to Mermaid for the full layout.',
|
|
10
11
|
diagramLabel: 'Diagram',
|
|
11
12
|
replay: 'Replay animation',
|
|
12
13
|
expand: 'Enlarge diagram',
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pele diagram types that render in a clearly degraded way, so the container shows a notice above them. The list
|
|
3
|
+
* is filled from the same-size screenshot comparison of the demo samples (see the change's design notes), and
|
|
4
|
+
* each entry is a Pele type name, as `PeleRenderResult.type` reports it. Falling back to Mermaid is not used,
|
|
5
|
+
* because it would load `mermaid` on a Pele-only site.
|
|
6
|
+
*/
|
|
7
|
+
export declare const PELE_DEGRADED_TYPES: readonly string[];
|
|
8
|
+
/** True when a Pele diagram of this type should show the degraded notice. */
|
|
9
|
+
export declare function isPeleDegraded(type: string | undefined): boolean;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pele diagram types that render in a clearly degraded way, so the container shows a notice above them. The list
|
|
3
|
+
* is filled from the same-size screenshot comparison of the demo samples (see the change's design notes), and
|
|
4
|
+
* each entry is a Pele type name, as `PeleRenderResult.type` reports it. Falling back to Mermaid is not used,
|
|
5
|
+
* because it would load `mermaid` on a Pele-only site.
|
|
6
|
+
*/
|
|
7
|
+
// Found in the 2026-10-31 same-size comparison of examples/demo-web/src/mermaid-samples.ts (36 samples, both engines
|
|
8
|
+
// at 440px): c4 scales to unreadable text, gitGraph branch labels are white on mid-tone fills, and treemap leaf
|
|
9
|
+
// labels are white and too small on their fills.
|
|
10
|
+
export const PELE_DEGRADED_TYPES = Object.freeze(['c4', 'gitGraph', 'treemap']);
|
|
11
|
+
/** True when a Pele diagram of this type should show the degraded notice. */
|
|
12
|
+
export function isPeleDegraded(type) {
|
|
13
|
+
return type !== undefined && PELE_DEGRADED_TYPES.includes(type);
|
|
14
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { MermaidPreset, MermaidThemeMode, MermaidTokens } from './types.js';
|
|
2
|
+
/** Text on a surface, and slice labels on a series color, must hold this ratio (WCAG AA for normal text). */
|
|
3
|
+
export declare const PELE_MIN_CONTRAST = 4.5;
|
|
4
|
+
/** Pele reads `--pele-series-1` to `--pele-series-8` for categorical fills. */
|
|
5
|
+
export declare const PELE_SERIES_COUNT = 8;
|
|
6
|
+
/** Every custom property the mapping writes, in order. Pele reads each of them, with its own fallback. */
|
|
7
|
+
export declare const PELE_VARIABLE_NAMES: readonly string[];
|
|
8
|
+
export interface PeleThemeOptions {
|
|
9
|
+
/** Site token names for the theme roles, the same option Mermaid takes. */
|
|
10
|
+
tokens?: MermaidTokens;
|
|
11
|
+
/** Font stack for the diagram text. Defaults to `sans-serif`; the render path passes the container's font. */
|
|
12
|
+
font?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Moves `color` toward black or white, whichever gives more contrast on `background`, until the pair reaches
|
|
16
|
+
* `minimum`. A color that already passes is returned unchanged. The palette's colors are hex, so the steps
|
|
17
|
+
* are exact; any other form is passed through, as the contrast check cannot read it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function ensureContrast(color: string, background: string, minimum?: number): string;
|
|
20
|
+
/**
|
|
21
|
+
* The custom properties for Pele in one mode and preset. Series colors come from the preset palette, adjusted per
|
|
22
|
+
* background so the slice label (drawn in the background color) holds 4.5:1 on each slice.
|
|
23
|
+
*/
|
|
24
|
+
export declare function peleVariables(mode: MermaidThemeMode, preset?: MermaidPreset, options?: PeleThemeOptions): Record<string, string>;
|
|
25
|
+
/** Writes the variables as inline custom properties on the container. Pele's SVG inherits them; no render call. */
|
|
26
|
+
export declare function applyPeleVariables(element: {
|
|
27
|
+
style: {
|
|
28
|
+
setProperty: (name: string, value: string) => void;
|
|
29
|
+
};
|
|
30
|
+
}, variables: Record<string, string>): void;
|
|
31
|
+
/** The container's computed font stack, so Pele text matches the page. Falls back to `sans-serif` without a DOM. */
|
|
32
|
+
export declare function peleFontFamily(element: unknown): string;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { mixColors } from './palette.js';
|
|
2
|
+
import { getContrastRatio, getMermaidSemanticTheme } from './theme.js';
|
|
3
|
+
/** Text on a surface, and slice labels on a series color, must hold this ratio (WCAG AA for normal text). */
|
|
4
|
+
export const PELE_MIN_CONTRAST = 4.5;
|
|
5
|
+
/** Pele reads `--pele-series-1` to `--pele-series-8` for categorical fills. */
|
|
6
|
+
export const PELE_SERIES_COUNT = 8;
|
|
7
|
+
const DEFAULT_RADIUS = '4px';
|
|
8
|
+
const DEFAULT_FONT = 'sans-serif';
|
|
9
|
+
/** Every custom property the mapping writes, in order. Pele reads each of them, with its own fallback. */
|
|
10
|
+
export const PELE_VARIABLE_NAMES = [
|
|
11
|
+
'--pele-bg',
|
|
12
|
+
'--pele-fg',
|
|
13
|
+
'--pele-muted',
|
|
14
|
+
'--pele-line',
|
|
15
|
+
'--pele-surface',
|
|
16
|
+
'--pele-surface-alt',
|
|
17
|
+
'--pele-border',
|
|
18
|
+
'--pele-accent',
|
|
19
|
+
'--pele-font',
|
|
20
|
+
'--pele-radius',
|
|
21
|
+
...Array.from({ length: PELE_SERIES_COUNT }, (_, index) => `--pele-series-${index + 1}`),
|
|
22
|
+
];
|
|
23
|
+
/**
|
|
24
|
+
* Moves `color` toward black or white, whichever gives more contrast on `background`, until the pair reaches
|
|
25
|
+
* `minimum`. A color that already passes is returned unchanged. The palette's colors are hex, so the steps
|
|
26
|
+
* are exact; any other form is passed through, as the contrast check cannot read it.
|
|
27
|
+
*/
|
|
28
|
+
export function ensureContrast(color, background, minimum = PELE_MIN_CONTRAST) {
|
|
29
|
+
if (getContrastRatio(background, color) >= minimum)
|
|
30
|
+
return color;
|
|
31
|
+
const target = getContrastRatio(background, '#000000') >= getContrastRatio(background, '#ffffff') ? '#000000' : '#ffffff';
|
|
32
|
+
let candidate = color;
|
|
33
|
+
for (let step = 1; step <= 50; step += 1) {
|
|
34
|
+
candidate = mixColors(color, target, step / 50);
|
|
35
|
+
if (getContrastRatio(background, candidate) >= minimum)
|
|
36
|
+
return candidate;
|
|
37
|
+
}
|
|
38
|
+
return candidate;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The custom properties for Pele in one mode and preset. Series colors come from the preset palette, adjusted per
|
|
42
|
+
* background so the slice label (drawn in the background color) holds 4.5:1 on each slice.
|
|
43
|
+
*/
|
|
44
|
+
export function peleVariables(mode, preset = 'default', options = {}) {
|
|
45
|
+
const theme = getMermaidSemanticTheme(mode, preset, { tokens: options.tokens });
|
|
46
|
+
const { background, foreground } = theme;
|
|
47
|
+
// Secondary text: a mix toward the background, kept only when it still reads at 4.5:1.
|
|
48
|
+
const mutedMix = mixColors(foreground, background, 0.35);
|
|
49
|
+
const muted = getContrastRatio(mutedMix, background) >= PELE_MIN_CONTRAST ? mutedMix : foreground;
|
|
50
|
+
const series = Object.fromEntries(theme.series.slice(0, PELE_SERIES_COUNT).map((tone, index) => [`--pele-series-${index + 1}`, ensureContrast(tone.mark, background)]));
|
|
51
|
+
return {
|
|
52
|
+
'--pele-bg': background,
|
|
53
|
+
'--pele-fg': foreground,
|
|
54
|
+
'--pele-muted': muted,
|
|
55
|
+
'--pele-line': theme.edge.line,
|
|
56
|
+
'--pele-surface': theme.node.fill,
|
|
57
|
+
'--pele-surface-alt': theme.cluster.fill,
|
|
58
|
+
'--pele-border': theme.node.border,
|
|
59
|
+
'--pele-accent': theme.accent,
|
|
60
|
+
'--pele-font': options.font ?? DEFAULT_FONT,
|
|
61
|
+
'--pele-radius': DEFAULT_RADIUS,
|
|
62
|
+
...series,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/** Writes the variables as inline custom properties on the container. Pele's SVG inherits them; no render call. */
|
|
66
|
+
export function applyPeleVariables(element, variables) {
|
|
67
|
+
for (const [name, value] of Object.entries(variables))
|
|
68
|
+
element.style.setProperty(name, value);
|
|
69
|
+
}
|
|
70
|
+
/** The container's computed font stack, so Pele text matches the page. Falls back to `sans-serif` without a DOM. */
|
|
71
|
+
export function peleFontFamily(element) {
|
|
72
|
+
if (typeof getComputedStyle !== 'function' || typeof Element === 'undefined' || !(element instanceof Element))
|
|
73
|
+
return DEFAULT_FONT;
|
|
74
|
+
return getComputedStyle(element).fontFamily || DEFAULT_FONT;
|
|
75
|
+
}
|
package/dist/render.js
CHANGED
|
@@ -5,8 +5,11 @@ import { playMermaidEntrance } from './entrance.js';
|
|
|
5
5
|
import { attachMermaidPartFocus } from './focus.js';
|
|
6
6
|
import { buildMermaidGraph } from './graph.js';
|
|
7
7
|
import { attachMermaidDiagramInteraction } from './interaction.js';
|
|
8
|
+
import { resolveEngine } from './engine.js';
|
|
8
9
|
import { resolveLabels } from './labels.js';
|
|
9
10
|
import { resolveMode, watchMode } from './mode.js';
|
|
11
|
+
import { isPeleDegraded } from './pele-degraded.js';
|
|
12
|
+
import { applyPeleVariables, peleFontFamily, peleVariables } from './pele-theme.js';
|
|
10
13
|
import { ensureSvgViewBox, normalizeMermaidSvgViewBox, postProcessMermaidSvg } from './svg.js';
|
|
11
14
|
import { getMermaidThemeConfig, resolvePreset } from './theme.js';
|
|
12
15
|
import { openMermaidViewer } from './viewer.js';
|
|
@@ -50,6 +53,38 @@ async function initializeOnce(mermaid, config) {
|
|
|
50
53
|
function defaultLoader() {
|
|
51
54
|
return import('mermaid');
|
|
52
55
|
}
|
|
56
|
+
function defaultPeleLoader() {
|
|
57
|
+
return import('pele');
|
|
58
|
+
}
|
|
59
|
+
/** Pele is loaded once per loader. A failed import is not kept, so the next render tries again. */
|
|
60
|
+
const PELE_MODULES = new WeakMap();
|
|
61
|
+
function loadPele(loader) {
|
|
62
|
+
let pending = PELE_MODULES.get(loader);
|
|
63
|
+
if (!pending) {
|
|
64
|
+
pending = loader().catch((error) => {
|
|
65
|
+
PELE_MODULES.delete(loader);
|
|
66
|
+
throw error;
|
|
67
|
+
});
|
|
68
|
+
PELE_MODULES.set(loader, pending);
|
|
69
|
+
}
|
|
70
|
+
return pending;
|
|
71
|
+
}
|
|
72
|
+
/** Mermaid backend: the shared Mermaid instance, queued so renders never overlap. */
|
|
73
|
+
function renderWithMermaid(source, options, mode, preset) {
|
|
74
|
+
return enqueue(async () => {
|
|
75
|
+
const { default: mermaid } = await (options.loadMermaid ?? defaultLoader)();
|
|
76
|
+
const config = getMermaidThemeConfig(mode, preset, { tokens: options.tokens });
|
|
77
|
+
await initializeOnce(mermaid, config);
|
|
78
|
+
const id = `mermaid-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
79
|
+
return (await mermaid.render(id, source)).svg;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/** Pele backend: a direct synchronous call, never Pele's own mount. Mermaid is not loaded on this path. */
|
|
83
|
+
async function renderWithPele(source, options) {
|
|
84
|
+
const pele = await loadPele(options.peleLoader ?? defaultPeleLoader);
|
|
85
|
+
const { svg, type } = pele.render(source);
|
|
86
|
+
return { svg, type };
|
|
87
|
+
}
|
|
53
88
|
/** Maps Mermaid's parser messages onto the labels, as the web app does. */
|
|
54
89
|
/** Trace for a graph the model can read, part focus for every other type, nothing when interaction is off. */
|
|
55
90
|
function attachInteraction(host, svg, config, onTraceChange, graph = buildMermaidGraph(svg)) {
|
|
@@ -58,17 +93,42 @@ function attachInteraction(host, svg, config, onTraceChange, graph = buildMermai
|
|
|
58
93
|
return attachMermaidDiagramInteraction(host, wired, graph);
|
|
59
94
|
return attachMermaidPartFocus(host, wired, describeParts(svg));
|
|
60
95
|
}
|
|
96
|
+
/** Pele's `PeleError` is recognized by its shape, because the class is only reachable once `pele` has loaded. */
|
|
97
|
+
function isPeleError(error) {
|
|
98
|
+
if (!(error instanceof Error))
|
|
99
|
+
return false;
|
|
100
|
+
const candidate = error;
|
|
101
|
+
return error.name === 'PeleError' || (typeof candidate.code === 'string' && typeof candidate.line === 'number');
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Keeps the position visible after the Mermaid label replaces "Parse error on line N:": the first line gets
|
|
105
|
+
* "(line N, column M)" unless the message already names a line. Only the first line changes.
|
|
106
|
+
*/
|
|
107
|
+
function withPosition(message, error) {
|
|
108
|
+
if (!error.line || /\bline \d+/i.test(message))
|
|
109
|
+
return message;
|
|
110
|
+
const position = `line ${error.line}${error.column ? `, column ${error.column}` : ''}`;
|
|
111
|
+
const [first = '', ...rest] = message.split('\n');
|
|
112
|
+
const head = first.endsWith(':') ? `${first.slice(0, -1)} (${position}):` : `${first} (${position})`;
|
|
113
|
+
return [head, ...rest].join('\n');
|
|
114
|
+
}
|
|
61
115
|
function describeError(error, labels) {
|
|
62
116
|
let message = labels.unableToRender;
|
|
63
117
|
if (error instanceof Error)
|
|
64
118
|
message = error.message;
|
|
65
119
|
else if (typeof error === 'string')
|
|
66
120
|
message = error;
|
|
67
|
-
|
|
121
|
+
const described = message
|
|
68
122
|
.replace(/^Parse error on line \d+:/, labels.syntaxErrorPrefix)
|
|
69
123
|
.replace(/^Error:/, labels.renderingErrorPrefix)
|
|
70
124
|
.replace(/no diagram type detected/i, labels.noValidDiagram)
|
|
71
125
|
.replace(/unknown diagram type/i, labels.unknownDiagramType);
|
|
126
|
+
return isPeleError(error) ? withPosition(described, error) : described;
|
|
127
|
+
}
|
|
128
|
+
/** The options a Pele restyle may change without a render. Everything else still needs a render. */
|
|
129
|
+
function withoutRestyleKeys(options) {
|
|
130
|
+
const { mode: _mode, preset: _preset, tokens: _tokens, ...rest } = options;
|
|
131
|
+
return rest;
|
|
72
132
|
}
|
|
73
133
|
/** Objects compare by value and functions by identity, so a new loader or callback counts as a change. */
|
|
74
134
|
function sameValue(left, right) {
|
|
@@ -99,6 +159,11 @@ class MermaidDiagram {
|
|
|
99
159
|
entrance = null;
|
|
100
160
|
observer = null;
|
|
101
161
|
unwatchMode = null;
|
|
162
|
+
/** What the shown diagram was rendered from, so a Pele restyle is only taken when nothing else changed. */
|
|
163
|
+
renderedEngine = null;
|
|
164
|
+
renderedSource = '';
|
|
165
|
+
renderedOptions = {};
|
|
166
|
+
renderedPreset = 'default';
|
|
102
167
|
constructor(host, source, options) {
|
|
103
168
|
this.host = host;
|
|
104
169
|
this.source = source;
|
|
@@ -153,6 +218,8 @@ class MermaidDiagram {
|
|
|
153
218
|
}
|
|
154
219
|
async render() {
|
|
155
220
|
const version = ++this.version;
|
|
221
|
+
if (this.restylePele())
|
|
222
|
+
return;
|
|
156
223
|
this.teardownDiagram();
|
|
157
224
|
if (!this.source.trim()) {
|
|
158
225
|
this.host.replaceChildren();
|
|
@@ -162,17 +229,15 @@ class MermaidDiagram {
|
|
|
162
229
|
const { source, options } = this;
|
|
163
230
|
const mode = resolveMode(options.mode ?? 'auto');
|
|
164
231
|
const preset = resolvePreset(options.preset);
|
|
232
|
+
const engine = resolveEngine(options.engine);
|
|
165
233
|
try {
|
|
166
|
-
const
|
|
167
|
-
const { default: mermaid } = await (options.loadMermaid ?? defaultLoader)();
|
|
168
|
-
const config = getMermaidThemeConfig(mode, preset, { tokens: options.tokens });
|
|
169
|
-
await initializeOnce(mermaid, config);
|
|
170
|
-
const id = `mermaid-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
171
|
-
return (await mermaid.render(id, source)).svg;
|
|
172
|
-
});
|
|
234
|
+
const rendered = engine === 'pele' ? await renderWithPele(source, options) : { svg: await renderWithMermaid(source, options, mode, preset), type: '' };
|
|
173
235
|
if (this.isStale(version))
|
|
174
236
|
return;
|
|
175
|
-
this.
|
|
237
|
+
this.renderedSource = source;
|
|
238
|
+
this.renderedOptions = withoutRestyleKeys(options);
|
|
239
|
+
this.renderedEngine = engine;
|
|
240
|
+
this.showDiagram(postProcessMermaidSvg(rendered.svg, {}, engine), mode, preset, rendered.type);
|
|
176
241
|
}
|
|
177
242
|
catch (error) {
|
|
178
243
|
if (this.isStale(version))
|
|
@@ -181,8 +246,9 @@ class MermaidDiagram {
|
|
|
181
246
|
this.host.replaceChildren(this.errorPanel(describeError(error, this.labels)));
|
|
182
247
|
}
|
|
183
248
|
}
|
|
184
|
-
showDiagram(svg, mode, preset) {
|
|
249
|
+
showDiagram(svg, mode, preset, peleType = '') {
|
|
185
250
|
this.renderedMode = mode;
|
|
251
|
+
this.renderedPreset = preset;
|
|
186
252
|
this.markup = svg;
|
|
187
253
|
const container = el('div', [this.baseClasses(), `mermaid-preset-${preset}`].join(' '));
|
|
188
254
|
container.dataset.mermaidPreset = preset;
|
|
@@ -193,6 +259,14 @@ class MermaidDiagram {
|
|
|
193
259
|
container.appendChild(diagramHost);
|
|
194
260
|
this.container = container;
|
|
195
261
|
this.diagramHost = diagramHost;
|
|
262
|
+
// Pele's SVG reads its colors from these variables, so they are written once here and restyled in place.
|
|
263
|
+
if (this.renderedEngine === 'pele')
|
|
264
|
+
this.paintPele(container, mode, preset);
|
|
265
|
+
if (this.renderedEngine === 'pele' && isPeleDegraded(peleType)) {
|
|
266
|
+
const notice = el('p', 'mermaid-engine-notice', this.labels.peleDegradedNotice);
|
|
267
|
+
notice.setAttribute('role', 'note');
|
|
268
|
+
container.insertBefore(notice, diagramHost);
|
|
269
|
+
}
|
|
196
270
|
// Layout-dependent work runs once the SVG is in the document: the viewBox needs getBBox, and the
|
|
197
271
|
// reveal's hidden first frame has to be in place before the browser paints.
|
|
198
272
|
const svgElement = diagramHost.querySelector('svg');
|
|
@@ -218,6 +292,33 @@ class MermaidDiagram {
|
|
|
218
292
|
}
|
|
219
293
|
this.scheduleEntrance(svgElement, graph, diagramHost);
|
|
220
294
|
}
|
|
295
|
+
/** Writes the Pele variables for a mode and preset onto the container. No Pele call is made. */
|
|
296
|
+
paintPele(container, mode, preset) {
|
|
297
|
+
applyPeleVariables(container, peleVariables(mode, preset, { tokens: this.options.tokens, font: peleFontFamily(container) }));
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* A mode, preset or token change on a shown Pele diagram only rewrites its variables and classes. Source,
|
|
301
|
+
* engine and every other option still take the full render path. Returns false when a render is needed.
|
|
302
|
+
*/
|
|
303
|
+
restylePele() {
|
|
304
|
+
if (this.renderedEngine !== 'pele' || !this.container || this.renderedSource !== this.source)
|
|
305
|
+
return false;
|
|
306
|
+
if (resolveEngine(this.options.engine) !== 'pele')
|
|
307
|
+
return false;
|
|
308
|
+
if (!sameOptions(withoutRestyleKeys(this.options), this.renderedOptions))
|
|
309
|
+
return false;
|
|
310
|
+
const mode = resolveMode(this.options.mode ?? 'auto');
|
|
311
|
+
const preset = resolvePreset(this.options.preset);
|
|
312
|
+
const container = this.container;
|
|
313
|
+
container.classList.remove(`mermaid-preset-${this.renderedPreset}`);
|
|
314
|
+
container.classList.add(`mermaid-preset-${preset}`);
|
|
315
|
+
container.dataset.mermaidPreset = preset;
|
|
316
|
+
container.dataset.mermaidMode = mode;
|
|
317
|
+
this.renderedMode = mode;
|
|
318
|
+
this.renderedPreset = preset;
|
|
319
|
+
this.paintPele(container, mode, preset);
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
221
322
|
scheduleEntrance(svg, graph, diagramHost) {
|
|
222
323
|
if (this.options.entrance === false)
|
|
223
324
|
return;
|
package/dist/svg.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import type { MermaidSvgPostProcessorConfig } from './types.js';
|
|
1
|
+
import type { MermaidEngine, MermaidSvgPostProcessorConfig } from './types.js';
|
|
2
2
|
export declare function normalizeMermaidSvgViewBox(svg: SVGSVGElement, padding?: number): void;
|
|
3
3
|
/** Gives an SVG that Mermaid left without a viewBox one from its content, so it can be fitted and scaled. */
|
|
4
4
|
export declare function ensureSvgViewBox(svg: SVGSVGElement, padding?: number): void;
|
|
5
5
|
/**
|
|
6
|
-
* Adds scoped semantic classes to Mermaid output. Non-SVG input is returned
|
|
7
|
-
* unchanged so unrelated application SVGs can never be affected.
|
|
6
|
+
* Adds scoped semantic classes to Mermaid or Pele output. Non-SVG input is returned
|
|
7
|
+
* unchanged so unrelated application SVGs can never be affected. Pele output gets only the
|
|
8
|
+
* engine-neutral steps: Mermaid's node, cluster and edge classes, gradients and shadows are skipped,
|
|
9
|
+
* because they target Mermaid's markup.
|
|
8
10
|
*/
|
|
9
|
-
export declare function postProcessMermaidSvg(svg: string, options?: MermaidSvgPostProcessorConfig): string;
|
|
11
|
+
export declare function postProcessMermaidSvg(svg: string, options?: MermaidSvgPostProcessorConfig, engine?: MermaidEngine): string;
|
package/dist/svg.js
CHANGED
|
@@ -23,10 +23,12 @@ export function ensureSvgViewBox(svg, padding = 8) {
|
|
|
23
23
|
svg.setAttribute('viewBox', `${bounds.x - padding} ${bounds.y - padding} ${bounds.width + padding * 2} ${bounds.height + padding * 2}`);
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
|
-
* Adds scoped semantic classes to Mermaid output. Non-SVG input is returned
|
|
27
|
-
* unchanged so unrelated application SVGs can never be affected.
|
|
26
|
+
* Adds scoped semantic classes to Mermaid or Pele output. Non-SVG input is returned
|
|
27
|
+
* unchanged so unrelated application SVGs can never be affected. Pele output gets only the
|
|
28
|
+
* engine-neutral steps: Mermaid's node, cluster and edge classes, gradients and shadows are skipped,
|
|
29
|
+
* because they target Mermaid's markup.
|
|
28
30
|
*/
|
|
29
|
-
export function postProcessMermaidSvg(svg, options = {}) {
|
|
31
|
+
export function postProcessMermaidSvg(svg, options = {}, engine = 'mermaid') {
|
|
30
32
|
if (typeof DOMParser === 'undefined')
|
|
31
33
|
return svg;
|
|
32
34
|
// C4 diagrams use `xlink:href` on their icons without declaring the prefix, which an XML parser rejects.
|
|
@@ -51,6 +53,26 @@ export function postProcessMermaidSvg(svg, options = {}) {
|
|
|
51
53
|
else
|
|
52
54
|
root.removeAttribute('style');
|
|
53
55
|
}
|
|
56
|
+
if (engine === 'pele') {
|
|
57
|
+
// Pele's root carries a fixed width and height; the viewBox keeps the proportions, so both attributes go.
|
|
58
|
+
// The origin marker lets graph.ts choose Pele's geometry adapters for this SVG.
|
|
59
|
+
root.setAttribute('data-mermaid-engine', 'pele');
|
|
60
|
+
const pele = root.getAttribute('aria-roledescription');
|
|
61
|
+
if (pele)
|
|
62
|
+
root.setAttribute('data-mermaid-type', pele);
|
|
63
|
+
if (!root.getAttribute('viewBox')) {
|
|
64
|
+
root.setAttribute('viewBox', `0 0 ${root.getAttribute('width') ?? 0} ${root.getAttribute('height') ?? 0}`);
|
|
65
|
+
}
|
|
66
|
+
root.removeAttribute('height');
|
|
67
|
+
root.setAttribute('width', '100%');
|
|
68
|
+
// The neutral node class lets hover, pinning and keyboard focus find Pele nodes. Core emphasis stays Mermaid's.
|
|
69
|
+
document.querySelectorAll('g.pele-node').forEach((node) => node.classList.add('mermaid-node'));
|
|
70
|
+
document.querySelectorAll('text, foreignObject').forEach((label) => {
|
|
71
|
+
label.classList.add('mermaid-label');
|
|
72
|
+
label.setAttribute('aria-hidden', 'true');
|
|
73
|
+
});
|
|
74
|
+
return new XMLSerializer().serializeToString(root);
|
|
75
|
+
}
|
|
54
76
|
root.setAttribute('width', '100%');
|
|
55
77
|
document.querySelectorAll('.node').forEach((node, index) => {
|
|
56
78
|
node.classList.add('mermaid-node');
|
package/dist/theme.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { MermaidConfig } from 'mermaid';
|
|
2
|
+
import { ARCHIFY_DARK_SERIES, ARCHIFY_LIGHT_SERIES, DEFAULT_DARK_SERIES, DEFAULT_LIGHT_SERIES } from './palette.js';
|
|
3
|
+
export { ARCHIFY_DARK_SERIES, ARCHIFY_LIGHT_SERIES, DEFAULT_DARK_SERIES, DEFAULT_LIGHT_SERIES };
|
|
2
4
|
import type { MermaidLayoutConfig, MermaidPreset, MermaidSemanticTheme, MermaidThemeMode, MermaidTokenRole, MermaidTokens } from './types.js';
|
|
3
5
|
export interface MermaidThemeOptions {
|
|
4
6
|
/** Custom property names for the theme roles. Unset roles read `--hagilight-mermaid-*`. */
|
|
@@ -10,6 +12,8 @@ export declare const MERMAID_LAYOUT_DEFAULTS: MermaidLayoutConfig;
|
|
|
10
12
|
/** Unknown names select the default preset, so a stale stored value never breaks rendering. */
|
|
11
13
|
export declare function resolvePreset(value: unknown): MermaidPreset;
|
|
12
14
|
export declare function getContrastRatio(foreground: string, background: string): number;
|
|
15
|
+
/** Resolves the site tokens for a mode into the semantic theme. Shared by Mermaid and Pele. */
|
|
16
|
+
export declare function buildTheme(mode: MermaidThemeMode, tokens: MermaidTokens): MermaidSemanticTheme;
|
|
13
17
|
export declare function getMermaidSemanticTheme(mode: MermaidThemeMode, preset?: MermaidPreset, options?: MermaidThemeOptions): MermaidSemanticTheme;
|
|
14
18
|
/**
|
|
15
19
|
* Builds the Mermaid configuration for a mode and preset. Configurations are reused while the
|
package/dist/theme.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { ARCHIFY_DARK_SERIES, ARCHIFY_LIGHT_SERIES, DEFAULT_DARK_SERIES, DEFAULT_LIGHT_SERIES, buildSeriesVariables, } from './palette.js';
|
|
2
|
+
// Shared with the Pele mapping (pele-theme.ts): the preset palettes are read from here, not copied.
|
|
3
|
+
export { ARCHIFY_DARK_SERIES, ARCHIFY_LIGHT_SERIES, DEFAULT_DARK_SERIES, DEFAULT_LIGHT_SERIES };
|
|
2
4
|
/** Custom properties read for each role when a site does not map its own. */
|
|
3
5
|
export const TOKEN_PROPERTIES = {
|
|
4
6
|
background: '--hagilight-mermaid-background',
|
|
@@ -140,7 +142,8 @@ export function getContrastRatio(foreground, background) {
|
|
|
140
142
|
const darker = Math.min(foregroundLuminance, backgroundLuminance);
|
|
141
143
|
return (lighter + 0.05) / (darker + 0.05);
|
|
142
144
|
}
|
|
143
|
-
|
|
145
|
+
/** Resolves the site tokens for a mode into the semantic theme. Shared by Mermaid and Pele. */
|
|
146
|
+
export function buildTheme(mode, tokens) {
|
|
144
147
|
const fallback = mode === 'dark' ? DARK_THEME : LIGHT_THEME;
|
|
145
148
|
const read = (role) => readCssToken(tokens[role] ?? TOKEN_PROPERTIES[role], fallback[role]);
|
|
146
149
|
const background = read('background');
|
package/dist/types.d.ts
CHANGED
|
@@ -5,6 +5,35 @@ export type MermaidMode = 'light' | 'dark' | 'auto';
|
|
|
5
5
|
export type MermaidThemeMode = 'light' | 'dark';
|
|
6
6
|
/** Appearance preset. `archify` uses fixed palettes and its own stylesheet. */
|
|
7
7
|
export type MermaidPreset = 'default' | 'archify';
|
|
8
|
+
/** Diagram renderer. `mermaid` is the default; `pele` is the optional Pele engine (`pele` must be installed). */
|
|
9
|
+
export type MermaidEngine = 'mermaid' | 'pele';
|
|
10
|
+
/**
|
|
11
|
+
* Shape of the `pele` module this package calls. Declared here instead of imported, because `pele` is an
|
|
12
|
+
* optional peer and its types must not be required when a site never selects it.
|
|
13
|
+
*/
|
|
14
|
+
export interface PeleRenderResult {
|
|
15
|
+
svg: string;
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
/** Pele's diagram type, for example `flowchart` or `pie`. */
|
|
19
|
+
type: string;
|
|
20
|
+
mermaidType: string;
|
|
21
|
+
}
|
|
22
|
+
export interface PeleModule {
|
|
23
|
+
render: (source: string) => PeleRenderResult;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Shape of a `PeleError`, recognized by its fields because the class itself is only reachable after `pele`
|
|
27
|
+
* loads. `line` and `column` are 1-based; Pele reports 0 when it could not locate the error.
|
|
28
|
+
*/
|
|
29
|
+
export interface PeleErrorShape extends Error {
|
|
30
|
+
code: string;
|
|
31
|
+
line?: number;
|
|
32
|
+
column?: number;
|
|
33
|
+
type?: string | null;
|
|
34
|
+
mermaidType?: string | null;
|
|
35
|
+
snippet?: string;
|
|
36
|
+
}
|
|
8
37
|
/** Semantic roles a site color can map to. */
|
|
9
38
|
export type MermaidTokenRole = 'background' | 'foreground' | 'primary' | 'accent' | 'primaryForeground';
|
|
10
39
|
/** Maps each role to the custom property a site defines, for example `{ background: '--background' }`. */
|
|
@@ -19,6 +48,8 @@ export interface MermaidLabels {
|
|
|
19
48
|
renderingErrorPrefix: string;
|
|
20
49
|
noValidDiagram: string;
|
|
21
50
|
unknownDiagramType: string;
|
|
51
|
+
/** Shown above a diagram of a type the Pele engine draws in a simplified way. */
|
|
52
|
+
peleDegradedNotice: string;
|
|
22
53
|
diagramLabel: string;
|
|
23
54
|
replay: string;
|
|
24
55
|
/** Opens the enlarged viewer. */
|
|
@@ -154,8 +185,15 @@ export interface MermaidRenderOptions {
|
|
|
154
185
|
* Defaults to `width`.
|
|
155
186
|
*/
|
|
156
187
|
fit?: 'width' | 'contain';
|
|
188
|
+
/**
|
|
189
|
+
* Diagram renderer. Resolved from this option, then the choice stored in this browser by
|
|
190
|
+
* `writeStoredEngine`, then `mermaid`. An unrecognized value is ignored. Explicit `mermaid` outranks storage.
|
|
191
|
+
*/
|
|
192
|
+
engine?: MermaidEngine;
|
|
157
193
|
/** Supplies the `mermaid` module. Defaults to a dynamic import of `mermaid`. */
|
|
158
194
|
loadMermaid?: () => Promise<MermaidModule>;
|
|
195
|
+
/** Supplies the `pele` module when the Pele engine renders. Defaults to a dynamic import of `pele`. */
|
|
196
|
+
peleLoader?: () => Promise<PeleModule>;
|
|
159
197
|
/** Extra class names for the diagram container. */
|
|
160
198
|
className?: string;
|
|
161
199
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hagicode/hagilight-mermaid",
|
|
3
|
-
"version": "0.20.1-dev.
|
|
3
|
+
"version": "0.20.1-dev.196.1.d67f4e4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Framework-agnostic Mermaid rendering with the HagiCode look: themes, presets, entrance reveal and path tracing for Astro, Starlight and Docusaurus sites",
|
|
6
6
|
"repository": {
|
|
@@ -59,7 +59,13 @@
|
|
|
59
59
|
"dist/**/*.d.ts"
|
|
60
60
|
],
|
|
61
61
|
"peerDependencies": {
|
|
62
|
-
"mermaid": "^11"
|
|
62
|
+
"mermaid": "^11",
|
|
63
|
+
"pele": "^1.13.0"
|
|
64
|
+
},
|
|
65
|
+
"peerDependenciesMeta": {
|
|
66
|
+
"pele": {
|
|
67
|
+
"optional": true
|
|
68
|
+
}
|
|
63
69
|
},
|
|
64
70
|
"keywords": [
|
|
65
71
|
"hagicode",
|
package/styles.css
CHANGED
|
@@ -749,6 +749,16 @@
|
|
|
749
749
|
line-height: 1.5;
|
|
750
750
|
}
|
|
751
751
|
|
|
752
|
+
.mermaid-container .mermaid-engine-notice {
|
|
753
|
+
margin: 0 0 8px;
|
|
754
|
+
padding: 8px 12px;
|
|
755
|
+
border: 1px dashed var(--hagilight-mermaid-muted-ink, #64748b);
|
|
756
|
+
border-radius: 6px;
|
|
757
|
+
color: var(--hagilight-mermaid-muted-ink, #64748b);
|
|
758
|
+
font-size: 0.8125rem;
|
|
759
|
+
line-height: 1.5;
|
|
760
|
+
}
|
|
761
|
+
|
|
752
762
|
.mermaid-container .mermaid-error-message {
|
|
753
763
|
margin: 0 0 12px 0;
|
|
754
764
|
font-size: 0.875rem;
|