@zombie-mermaid/core 2.2.6 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +8 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -7
- package/dist/index.d.ts +22 -7
- package/dist/index.js +94 -74
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/render-utils.test.ts +59 -0
- package/src/diagram-type.ts +15 -1
- package/src/index.ts +1 -0
- package/src/multiline-utils.ts +6 -5
- package/src/render-utils.ts +54 -0
- package/src/theme.ts +3 -2
- package/src/types.ts +1 -6
package/package.json
CHANGED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { f } from '../render-utils.ts'
|
|
3
|
+
|
|
4
|
+
describe('f tagged template literal', () => {
|
|
5
|
+
it('rounds floats to 2 decimal places', () => {
|
|
6
|
+
expect(f`${3.14159}`).toBe('3.14')
|
|
7
|
+
expect(f`${0.1 + 0.2}`).toBe('0.3')
|
|
8
|
+
expect(f`${1.005}`).toBe('1')
|
|
9
|
+
expect(f`${1.1}`).toBe('1.1')
|
|
10
|
+
expect(f`${2.0}`).toBe('2')
|
|
11
|
+
expect(f`M ${'A'} ${3.14159} L ${10.5} ${-20.999}`).toBe(
|
|
12
|
+
'M A 3.14 L 10.5 -21',
|
|
13
|
+
)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('keeps integers unchanged', () => {
|
|
17
|
+
expect(f`${42}`).toBe('42')
|
|
18
|
+
expect(f`${0}`).toBe('0')
|
|
19
|
+
expect(f`${-7}`).toBe('-7')
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('passes non-number values through as-is', () => {
|
|
23
|
+
expect(f`hello ${'world'}`).toBe('hello world')
|
|
24
|
+
expect(f`${'foo'}bar`).toBe('foobar')
|
|
25
|
+
expect(f`just text`).toBe('just text')
|
|
26
|
+
expect(f`${true}`).toBe('true')
|
|
27
|
+
expect(f`${null}`).toBe('null')
|
|
28
|
+
expect(f`${undefined}`).toBe('undefined')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('normalizes -0 to 0', () => {
|
|
32
|
+
expect(f`${-0}`).toBe('0')
|
|
33
|
+
expect(f`${-0.001}`).toBe('0')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('rounds very small magnitudes down to 0 rather than emitting scientific notation', () => {
|
|
37
|
+
expect(f`${1e-10}`).toBe('0')
|
|
38
|
+
expect(f`${-1e-10}`).toBe('0')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('normalizes non-finite values to 0 instead of emitting invalid SVG tokens', () => {
|
|
42
|
+
// Reachable from degenerate zero-size geometry (e.g. a zero-length
|
|
43
|
+
// vector normalized by dividing by its own length). "NaN"/"Infinity"
|
|
44
|
+
// are not valid SVG numeric attribute values, unlike upstream's
|
|
45
|
+
// behavior of stringifying them literally.
|
|
46
|
+
expect(f`${NaN}`).toBe('0')
|
|
47
|
+
expect(f`${Infinity}`).toBe('0')
|
|
48
|
+
expect(f`${-Infinity}`).toBe('0')
|
|
49
|
+
expect(f`<rect x="${NaN}" width="${10.5}" />`).toBe(
|
|
50
|
+
'<rect x="0" width="10.5" />',
|
|
51
|
+
)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('handles multiple interpolations mixing rounded and passthrough values', () => {
|
|
55
|
+
expect(
|
|
56
|
+
f`<rect x="${1.005}" y="${-20.999}" fill="${'red'}" data-n="${5}" />`,
|
|
57
|
+
).toBe('<rect x="1" y="-21" fill="red" data-n="5" />')
|
|
58
|
+
})
|
|
59
|
+
})
|
package/src/diagram-type.ts
CHANGED
|
@@ -10,8 +10,22 @@
|
|
|
10
10
|
|
|
11
11
|
import { splitStatements } from './statements.ts'
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Every diagram type this library can detect and route to a renderer — the
|
|
15
|
+
* runtime source of truth `DiagramType` is derived from, so consumers that
|
|
16
|
+
* need to enumerate the types (e.g. the MCP `list_diagram_types` tool) never
|
|
17
|
+
* keep a duplicate list that could drift.
|
|
18
|
+
*/
|
|
19
|
+
export const DIAGRAM_TYPES = [
|
|
20
|
+
'flowchart',
|
|
21
|
+
'sequence',
|
|
22
|
+
'class',
|
|
23
|
+
'er',
|
|
24
|
+
'xychart',
|
|
25
|
+
] as const
|
|
26
|
+
|
|
13
27
|
/** The diagram types this library can detect and route to a renderer. */
|
|
14
|
-
export type DiagramType =
|
|
28
|
+
export type DiagramType = (typeof DIAGRAM_TYPES)[number]
|
|
15
29
|
|
|
16
30
|
/**
|
|
17
31
|
* Detect the diagram type from the mermaid source text.
|
package/src/index.ts
CHANGED
|
@@ -27,6 +27,7 @@ export * from './direction.ts'
|
|
|
27
27
|
export * from './direction-override.ts'
|
|
28
28
|
export * from './init-directive.ts'
|
|
29
29
|
export * from './multiline-utils.ts'
|
|
30
|
+
export * from './render-utils.ts'
|
|
30
31
|
export * from './statements.ts'
|
|
31
32
|
export * from './style-directives.ts'
|
|
32
33
|
export * from './text-metrics.ts'
|
package/src/multiline-utils.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// ============================================================================
|
|
8
8
|
|
|
9
9
|
import { LINE_HEIGHT_RATIO } from './text-metrics.ts'
|
|
10
|
+
import { f } from './render-utils.ts'
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Normalize label text: strip surrounding quotes, convert <br> tags and
|
|
@@ -216,7 +217,7 @@ export function renderMultilineText(
|
|
|
216
217
|
// Single line — simple text element
|
|
217
218
|
if (lines.length === 1) {
|
|
218
219
|
const dy = fontSize * baselineShift
|
|
219
|
-
return `<text x="${cx}" y="${cy}" ${attrs} dy="${dy}">${renderLineContent(text)}</text>`
|
|
220
|
+
return f`<text x="${cx}" y="${cy}" ${attrs} dy="${dy}">${renderLineContent(text)}</text>`
|
|
220
221
|
}
|
|
221
222
|
|
|
222
223
|
// Multi-line — use tspan elements with vertical centering
|
|
@@ -228,11 +229,11 @@ export function renderMultilineText(
|
|
|
228
229
|
const tspans = lines
|
|
229
230
|
.map((line, i) => {
|
|
230
231
|
const dy = i === 0 ? firstDy : lineHeight
|
|
231
|
-
return `<tspan x="${cx}" dy="${dy}">${renderLineContent(line)}</tspan>`
|
|
232
|
+
return f`<tspan x="${cx}" dy="${dy}">${renderLineContent(line)}</tspan>`
|
|
232
233
|
})
|
|
233
234
|
.join('')
|
|
234
235
|
|
|
235
|
-
return `<text x="${cx}" y="${cy}" ${attrs}>${tspans}</text>`
|
|
236
|
+
return f`<text x="${cx}" y="${cy}" ${attrs}>${tspans}</text>`
|
|
236
237
|
}
|
|
237
238
|
|
|
238
239
|
/**
|
|
@@ -266,8 +267,8 @@ export function renderMultilineTextWithBackground(
|
|
|
266
267
|
const bgHeight = textHeight + padding * 2
|
|
267
268
|
|
|
268
269
|
const rect =
|
|
269
|
-
`<rect x="${cx - bgWidth / 2}" y="${cy - bgHeight / 2}" ` +
|
|
270
|
-
`width="${bgWidth}" height="${bgHeight}" ${bgAttrs} />`
|
|
270
|
+
f`<rect x="${cx - bgWidth / 2}" y="${cy - bgHeight / 2}" ` +
|
|
271
|
+
f`width="${bgWidth}" height="${bgHeight}" ${bgAttrs} />`
|
|
271
272
|
|
|
272
273
|
const textEl = renderMultilineText(text, cx, cy, fontSize, textAttrs)
|
|
273
274
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// SVG numeric formatting — round interpolated coordinates/dimensions to 2dp.
|
|
3
|
+
//
|
|
4
|
+
// Ported from lukilabs/beautiful-mermaid#77 ("Round all floats to 2 decimal
|
|
5
|
+
// places in SVG output", credit: GauBen). ELK layout and the curve/bezier
|
|
6
|
+
// math in packages/svg-renderer/src/edge-curves.ts produce full
|
|
7
|
+
// floating-point precision (e.g. `142.38427299999998`), which upstream's PR
|
|
8
|
+
// pointed out bloats generated SVG for no visual benefit — nothing renders a
|
|
9
|
+
// sub-hundredth-of-a-pixel difference. `f` is a tagged template literal used
|
|
10
|
+
// in place of a plain template literal (`` f`<rect x="${x}" />` `` instead of
|
|
11
|
+
// `` `<rect x="${x}" />` ``) everywhere flowchart/ER/class/sequence markup is
|
|
12
|
+
// built, so every interpolated number gets rounded right where it is
|
|
13
|
+
// stringified, with no change to non-numeric interpolations (ids, colors,
|
|
14
|
+
// already-formatted strings).
|
|
15
|
+
//
|
|
16
|
+
// packages/svg-renderer/src/xychart/{renderer,layout}.ts already has its own
|
|
17
|
+
// rounding (`r()`, 1dp) for unrelated axis-label-formatting reasons — this
|
|
18
|
+
// helper is deliberately not applied there.
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Round a number to at most 2 decimal places for SVG output.
|
|
23
|
+
*
|
|
24
|
+
* Non-finite input (`NaN`/`Infinity`, reachable from degenerate zero-size
|
|
25
|
+
* geometry — e.g. a normalized zero-length vector) is normalized to `0`
|
|
26
|
+
* rather than stringified literally. This is a deliberate improvement over
|
|
27
|
+
* upstream: `"NaN"`/`"Infinity"` are not valid SVG numeric attribute values,
|
|
28
|
+
* so passing them through would hand the browser malformed markup instead of
|
|
29
|
+
* just an odd-looking number. Every other case matches upstream exactly,
|
|
30
|
+
* including collapsing `-0` to `0`.
|
|
31
|
+
*/
|
|
32
|
+
function roundCoord(n: number): number {
|
|
33
|
+
if (!Number.isFinite(n)) return 0
|
|
34
|
+
return Number(n.toFixed(2))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Tagged template literal: stringifies interpolated values, rounding any
|
|
39
|
+
* `number` to at most 2 decimal places first. Non-number values (strings,
|
|
40
|
+
* booleans, etc.) pass through `String()` unchanged, same as an untagged
|
|
41
|
+
* template literal would.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* f`<rect x="${3.14159}" y="${-0.001}" />` // '<rect x="3.14" y="0" />'
|
|
45
|
+
*/
|
|
46
|
+
export function f(strings: TemplateStringsArray, ...values: unknown[]): string {
|
|
47
|
+
let out = strings[0] ?? ''
|
|
48
|
+
for (let i = 0; i < values.length; i++) {
|
|
49
|
+
const v = values[i]
|
|
50
|
+
out += typeof v === 'number' ? String(roundCoord(v)) : String(v)
|
|
51
|
+
out += strings[i + 1] ?? ''
|
|
52
|
+
}
|
|
53
|
+
return out
|
|
54
|
+
}
|
package/src/theme.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import { escapeXml, escapeAttr } from './multiline-utils.ts'
|
|
15
15
|
import { parseHexColor } from './color-utils.ts'
|
|
16
|
+
import { f } from './render-utils.ts'
|
|
16
17
|
import {
|
|
17
18
|
MONO_FONT_FAMILY as SVG_MONO_FONT_FAMILY,
|
|
18
19
|
MONO_FONT_FACE_CSS as SVG_MONO_FONT_FACE_CSS,
|
|
@@ -705,8 +706,8 @@ export function svgOpenTag(
|
|
|
705
706
|
}
|
|
706
707
|
|
|
707
708
|
return (
|
|
708
|
-
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" ` +
|
|
709
|
-
`width="${width}" height="${height}"${a11yAttrs}${styleAttr}>` +
|
|
709
|
+
f`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" ` +
|
|
710
|
+
f`width="${width}" height="${height}"${a11yAttrs}${styleAttr}>` +
|
|
710
711
|
titleEl
|
|
711
712
|
)
|
|
712
713
|
}
|
package/src/types.ts
CHANGED
|
@@ -292,8 +292,6 @@ export interface RenderOptions {
|
|
|
292
292
|
nodeSpacing?: number
|
|
293
293
|
/** Vertical spacing between layers. Default: 48. Flowchart/state diagrams only — class/ER diagrams use fixed internal spacing. */
|
|
294
294
|
layerSpacing?: number
|
|
295
|
-
/** Currently unused — accepted for forward compatibility but not read anywhere. */
|
|
296
|
-
componentSpacing?: number
|
|
297
295
|
/** Whether to bundle overlapping fan-out/fan-in edge paths into shared trunks to reduce visual clutter. Default: true */
|
|
298
296
|
mergeEdges?: boolean
|
|
299
297
|
/**
|
|
@@ -614,9 +612,7 @@ export type CommonRenderOptions = Pick<
|
|
|
614
612
|
* Options applicable to flowchart (`graph` / `flowchart`) and state
|
|
615
613
|
* (`stateDiagram-v2`) diagrams — both share `DiagramType: 'flowchart'` and
|
|
616
614
|
* the same layout/render pipeline (src/layout-engine.ts, src/renderer.ts),
|
|
617
|
-
* so they share one option shape.
|
|
618
|
-
* everywhere (accepted for forward compatibility only) but grouped here
|
|
619
|
-
* since it's spacing-shaped like `padding`/`nodeSpacing`/`layerSpacing`.
|
|
615
|
+
* so they share one option shape.
|
|
620
616
|
*/
|
|
621
617
|
export type FlowchartRenderOptions = CommonRenderOptions &
|
|
622
618
|
Pick<
|
|
@@ -624,7 +620,6 @@ export type FlowchartRenderOptions = CommonRenderOptions &
|
|
|
624
620
|
| 'padding'
|
|
625
621
|
| 'nodeSpacing'
|
|
626
622
|
| 'layerSpacing'
|
|
627
|
-
| 'componentSpacing'
|
|
628
623
|
| 'mergeEdges'
|
|
629
624
|
| 'direction'
|
|
630
625
|
| 'curve'
|