merslim 0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 svgdiagram contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,266 @@
1
+ # merslim
2
+
3
+ A **slimmer mermaid** — SVG-first diagram renderer for mermaid-style syntax. 14 native diagram types, zero mermaid runtime dependency.
4
+
5
+ | | |
6
+ |---|---|
7
+ | Bundle | ~160 KB minified ESM (vs. mermaid's ~3 MB lazy-loaded / ~7 MB full) |
8
+ | Runtime deps | `dagre` only |
9
+ | Peer deps | `react ^18 \|\| ^19`, `react-dom ^18 \|\| ^19` |
10
+ | Mermaid | **none** — parsers and renderers are native |
11
+ | Output | Standalone SVG (computed styles inlined for fidelity) |
12
+ | License | MIT |
13
+
14
+ ## Why
15
+
16
+ Mermaid is great, but it's heavy, opaque, and not easy to extend. `merslim` re-implements the popular subset of mermaid syntax with:
17
+
18
+ - A small, typed intermediate representation (IR) you can build programmatically — no need to round-trip through text if you have structured data.
19
+ - A pluggable renderer registry. Diagrams are React components; lazy-loaded by type so you only pay for what you use.
20
+ - A serializer that walks the live DOM, inlines `getComputedStyle()` values onto a clone, and emits a self-contained SVG that opens identically in browsers, Inkscape, and Office.
21
+ - No vendor lock-in to a single visual style — every renderer is ~150–400 lines of plain React/SVG, easy to fork.
22
+
23
+ ## Supported diagram types
24
+
25
+ `flowchart` · `sequenceDiagram` · `erDiagram` · `classDiagram` · `stateDiagram-v2` · `gantt` · `timeline` · `pie` · `quadrantChart` · `journey` · `mindmap` · `architecture-beta` · `C4Context` (Container / Component / Deployment) · `gitGraph`
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ npm install merslim
31
+ ```
32
+
33
+ ## Quick start
34
+
35
+ ```tsx
36
+ import { DiagramRenderer, bootstrapDiagramRenderers } from 'merslim';
37
+
38
+ bootstrapDiagramRenderers(); // call once at app startup
39
+
40
+ const source = `
41
+ flowchart LR
42
+ A[Edit] --> B{Render}
43
+ B --> C[Export]
44
+ `;
45
+
46
+ export function App() {
47
+ return <DiagramRenderer source={source} />;
48
+ }
49
+ ```
50
+
51
+ ## With the export toolbar
52
+
53
+ ```tsx
54
+ import { useRef } from 'react';
55
+ import {
56
+ DiagramRenderer,
57
+ DiagramExportToolbar,
58
+ type RendererHandle,
59
+ } from 'merslim';
60
+
61
+ export function MyDiagram({ source }: { source: string }) {
62
+ const handleRef = useRef<RendererHandle | null>(null);
63
+
64
+ return (
65
+ <div className="group relative">
66
+ <DiagramRenderer source={source} handleRef={handleRef} />
67
+ <DiagramExportToolbar
68
+ source={() => handleRef.current?.getSvgElement() ?? null}
69
+ className="absolute top-2 right-2 opacity-0 group-hover:opacity-100"
70
+ />
71
+ </div>
72
+ );
73
+ }
74
+ ```
75
+
76
+ The toolbar gives you four buttons — copy SVG, copy PNG, download SVG, download PNG — all routed through the same standalone-SVG serializer.
77
+
78
+ ## Headless / SSR
79
+
80
+ If you only need an SVG string (e.g. to generate diagrams at build time for an MDX blog), skip the React component entirely:
81
+
82
+ ```ts
83
+ import { parseToIR, flowchartToSvg, type FlowchartIR } from 'merslim';
84
+
85
+ const result = await parseToIR(`
86
+ flowchart LR
87
+ A --> B
88
+ `);
89
+
90
+ if (result.ok && result.type === 'flowchart') {
91
+ const svg = flowchartToSvg(result.ir as FlowchartIR, { dark: false });
92
+ // write to disk, embed, ship to a CDN...
93
+ }
94
+ ```
95
+
96
+ > **Two builder flavors.** For each graph-shaped diagram there's a one-call
97
+ > convenience builder (`flowchartToSvg`, `classToSvg`, `erToSvg`) that runs
98
+ > layout internally, and a position-taking power-user builder
99
+ > (`buildFlowchartSvg(ir, positions, opts)`) for callers who want custom
100
+ > layout. Chart-shaped diagrams (pie, quadrant, journey, gantt, timeline,
101
+ > c4, architecture, gitgraph) are one-call already.
102
+ > See [`examples/headless/generate.ts`](./examples/headless/generate.ts) for
103
+ > the full pattern.
104
+
105
+ ## Build your own IR
106
+
107
+ The parser is one way to produce an IR; you can produce one any way you like. If you have structured data (a list of orders, a service topology, a customer journey) you can skip mermaid syntax entirely:
108
+
109
+ ```ts
110
+ import { flowchartToSvg, type FlowchartIR } from 'merslim';
111
+
112
+ const ir: FlowchartIR = {
113
+ type: 'flowchart',
114
+ direction: 'LR',
115
+ nodes: [
116
+ { id: 'a', label: 'Order received', kind: 'start' },
117
+ { id: 'b', label: 'Validate payment', kind: 'decision' },
118
+ { id: 'c', label: 'Ship', kind: 'end' },
119
+ ],
120
+ edges: [
121
+ { source: 'a', target: 'b' },
122
+ { source: 'b', target: 'c', label: 'paid' },
123
+ ],
124
+ };
125
+
126
+ const svg = flowchartToSvg(ir, { dark: false });
127
+ ```
128
+
129
+ ## Selective renderer registration
130
+
131
+ `bootstrapDiagramRenderers()` is a convenience that registers all 14 native
132
+ renderers (lazy-loaded, so unused ones stay out of your initial bundle).
133
+ If you only need a subset and want to skip even the lazy chunks, call
134
+ `register()` directly:
135
+
136
+ ```ts
137
+ import { register, DiagramRenderer } from 'merslim';
138
+
139
+ register({
140
+ type: 'flowchart',
141
+ loader: () =>
142
+ import('merslim').then((m) => ({ default: m.FlowchartRenderer })),
143
+ });
144
+ // Now <DiagramRenderer/> only knows about flowcharts. Any other diagram type
145
+ // surfaces a "no renderer registered" error.
146
+ ```
147
+
148
+ If you already have an IR and want to skip the source-string parser entirely,
149
+ the 14 renderers are also exported directly and can be mounted as standalone
150
+ components — `<FlowchartRenderer ir={ir} dark={dark}/>`, `<PieRenderer/>`,
151
+ `<SequenceRenderer/>`, etc.
152
+
153
+ ## Dark mode
154
+
155
+ Pass a `dark` prop to `<DiagramRenderer/>`, or use the helper to track a `.dark` class on `<html>`:
156
+
157
+ ```ts
158
+ import { isDarkMode, watchDarkMode } from 'merslim';
159
+
160
+ const [dark, setDark] = useState(isDarkMode);
161
+ useEffect(() => watchDarkMode(setDark), []);
162
+ ```
163
+
164
+ ## API surface
165
+
166
+ ### Components
167
+
168
+ | Export | Purpose |
169
+ |---|---|
170
+ | `<DiagramRenderer source dark handleRef onError/>` | Parses a source string, dispatches to the matching renderer, exposes a `RendererHandle` ref. |
171
+ | `<DiagramExportToolbar source filenameBase pngScale .../>` | 4-button copy/download toolbar. Accepts any `SvgSource`. |
172
+ | `<FlowchartRenderer/>` `<SequenceRenderer/>` `<ERRenderer/>` `<ClassRenderer/>` `<StateRenderer/>` `<GanttRenderer/>` `<TimelineRenderer/>` `<PieRenderer/>` `<QuadrantRenderer/>` `<JourneyRenderer/>` `<MindmapRenderer/>` `<ArchitectureRenderer/>` `<C4Renderer/>` `<GitGraphRenderer/>` | Direct-mount renderer per diagram type. Same `RendererProps<T>` signature: `{ ir, dark, handleRef }`. Use these when you already have an IR. |
173
+
174
+ ### Parser / IR
175
+
176
+ | Export | Type | Purpose |
177
+ |---|---|---|
178
+ | `parseToIR(source)` | `(string) => Promise<ParseResult>` | Mermaid syntax → typed IR. On success, narrows to `{ ok: true, type: DiagramType, ir: DiagramIR }`. |
179
+ | `detectDiagramType(source)` | `(string) => Promise<RecognizedDiagramType \| null>` | Lightweight first-line check. Returns `null` for empty input, `'unsupported'` for unrecognized headers. |
180
+
181
+ ### Builders (headless)
182
+
183
+ | Convenience (auto-layout) | Power-user (explicit positions) |
184
+ |---|---|
185
+ | `flowchartToSvg(ir, opts)` | `buildFlowchartSvg(ir, positions, opts)` |
186
+ | `classToSvg(ir, opts)` | `buildClassSvg(ir, positions, opts)` |
187
+ | `erToSvg(ir, opts)` | `buildErSvg(ir, positions, opts)` |
188
+ | — | `buildStateSvg(ir, { topLevel, children }, opts)` |
189
+ | — | `buildMindmapSvg(ir, positions, opts)` |
190
+
191
+ Plus the chart-shaped diagrams which never need positions:
192
+ `buildPieSvg`, `buildQuadrantSvg`, `buildJourneySvg`, `buildGanttSvg`,
193
+ `buildTimelineSvg`, `buildArchitectureSvg`, `buildC4Svg`, `buildGitGraphSvg`.
194
+
195
+ All builders take a final `{ dark, padding }` options object and return a
196
+ self-contained SVG string with `role="img"` and an `aria-label`.
197
+
198
+ ### Export pipeline
199
+
200
+ | Export | Purpose |
201
+ |---|---|
202
+ | `toSvgString(source, opts)` | Serialize any `SvgSource` to a standalone SVG string. |
203
+ | `svgToPngBlob(svg, opts)` | Rasterize an SVG string to a PNG `Blob`. |
204
+ | `downloadSvg / downloadPng` | Trigger a file download. |
205
+ | `copySvgToClipboard / copyPngToClipboard` | Write SVG (text) / PNG (image) to the clipboard. |
206
+ | `getSvgDimensions(svg)` | Best-effort intrinsic size from `viewBox` / attrs. |
207
+
208
+ ### Registry
209
+
210
+ | Export | Purpose |
211
+ |---|---|
212
+ | `register({ type, loader })` | Register a renderer for a diagram type. |
213
+ | `bootstrapDiagramRenderers()` | One-shot registration of all built-in renderers. |
214
+ | `getRenderer(type) / hasRenderer(type)` | Introspect the registry. |
215
+
216
+ ## Examples
217
+
218
+ - [`examples/playground/`](./examples/playground/) — Vite + React playground
219
+ with all 14 diagram types, a live source editor, dark-mode toggle, and
220
+ the export toolbar. `npm install && npm run dev` (or `npm run build` for a
221
+ static `dist/` that opens directly from `file://`).
222
+ - [`examples/headless/`](./examples/headless/) — Node script that generates
223
+ one self-contained SVG per diagram type. `npm install && npm start`.
224
+ - [`examples/react/App.tsx`](./examples/react/App.tsx) — Minimal React
225
+ snippet showing the same component wiring without the playground chrome.
226
+
227
+ ## Development
228
+
229
+ ```bash
230
+ npm install
231
+ npm run type-check # tsc --noEmit
232
+ npm test # vitest run
233
+ npm run build # tsup → dist/ (ESM + CJS + .d.ts)
234
+ ```
235
+
236
+ ## Notes & limitations
237
+
238
+ - The built-in renderers use a handful of Tailwind utility classes for surrounding chrome (loading / error states). The diagrams themselves are pure SVG and render correctly without Tailwind; only the wrapper container styling looks bare. PRs to make this opt-out are welcome.
239
+ - `parseToIR` is asynchronous because some builders (gantt, timeline) defer parsing work. Today the body is synchronous but the signature is stable.
240
+
241
+ ## Mermaid compatibility
242
+
243
+ merslim parses a curated subset of mermaid syntax. The full contract is enforced by [`test/compatCorpus.ts`](./test/compatCorpus.ts) — every entry there is a parse-time test that runs in CI.
244
+
245
+ **Known gaps** (parse but produce a partial IR, or fail outright):
246
+
247
+ | Diagram | Gap |
248
+ |---|---|
249
+ | flowchart | Multi-target shorthand `A & B --> C & D` |
250
+ | flowchart | Trapezoid shape `[/Foo\]` (falls back to rect) |
251
+ | flowchart | `<br/>` in labels treated as literal text, not a line break |
252
+ | sequence | `loop`/`alt`/`opt`/`par` blocks parse but render flat (no visual nesting) |
253
+ | sequence | `autonumber` keyword accepted but not honored |
254
+ | sequence | Bidirectional `<<->>` arrow |
255
+ | class | Generics `class Container~T~` |
256
+ | class | Cardinality labels `"1" --> "*"` |
257
+ | class | `namespace { ... }` blocks |
258
+ | state | Parallel/concurrent regions (`--` separator) |
259
+ | state | `<<choice>>`/`<<fork>>`/`<<join>>` pseudo-states |
260
+ | gantt | `excludes`/`todayMarker`/`tickInterval` accepted but not modeled |
261
+
262
+ If you hit a case not listed here, [add it to the corpus as a `known-gap` entry](./test/compatCorpus.ts) — that converts an issue into an executable spec.
263
+
264
+ ## License
265
+
266
+ MIT. See [LICENSE](./LICENSE).