Commit 0bcbb3e
feat(mermaid): add @tldraw/mermaid package for diagram-to-shape conversion (tldraw#8194)
In order to let users paste Mermaid diagram syntax and have it converted
into native tldraw shapes, this PR introduces the `@tldraw/mermaid`
package and integrates it into the dotcom app.
Closes tldraw#4353
### How it works
The system has three layers: **detection**, **parsing + layout
extraction**, and **blueprint rendering**.
**Detection (dotcom integration).** When text is pasted on dotcom,
`SneakyMermaidHandler` registers an external text content handler on the
editor. Before pulling in the heavy mermaid library, it runs a
lightweight regex-based check (`simpleMermaidStringTest`) that strips
YAML frontmatter, `%%{...}%%` directives, and `%%` comments, then tests
whether the cleaned text starts with a known diagram keyword. This keeps
the mermaid library out of the main bundle — it's only `import()`-ed
when a paste actually looks like a diagram. If the text doesn't match,
it falls through to `defaultHandleExternalTextContent`. If the diagram
type is unsupported (e.g. pie charts), the handler falls back to SVG
import via `putExternalContent({ type: 'svg-text' })` and shows a toast.
**Parsing + layout extraction (`createMermaidDiagram`).** This is the
main entry point. It initializes mermaid with inflated font sizes to
compensate for tldraw's hand-drawn font being wider, parses and
validates the syntax, renders to an offscreen DOM element for layout
extraction via `getBBox()`, then extracts the diagram's semantic data
(vertices, edges, actors, states, etc.) and dispatches to the
appropriate diagram-specific converter.
**Diagram-specific converters.** Each converter combines the semantic
data (from mermaid's DB) with layout data (from the rendered SVG) to
produce a `DiagramMermaidBlueprint`:
- *Flowcharts* — Node positions/dimensions from SVG `.node` elements,
cluster bounds from `.cluster` elements, edge waypoints from
`path[data-points]` attributes. Maps mermaid shape types to tldraw geo
types. Builds subgraph hierarchy as frames with `parentId`. Computes
arrow bend from waypoint geometry. Parses `classDef` CSS colors and maps
to nearest tldraw palette color via Euclidean RGB distance.
- *Sequence diagrams* — Actor layouts from SVG rect elements with fixed
spacing. Creates top/bottom actor shapes, vertical lifelines, signal
arrows with precise bindings, note shapes, and fragment frames for
loop/alt/opt/par/critical blocks.
- *State diagrams* — Recursively flattens the state hierarchy. Creates
frames for compound states, handles special types (start, end,
fork/join, choice). Reuses the same SVG node/edge parsing strategy as
flowcharts.
**Blueprint rendering (`renderBlueprint`).** The blueprint is a
diagram-type-agnostic intermediate representation with `nodes`, `edges`,
`lines`, and `groups`. The renderer centers the diagram at the paste
point, creates shapes in parent-first order with proper coordinate
transforms, creates arrows with bindings (precise anchored, self-loop,
and standard center-to-center), groups shapes, and applies a final
position correction pass.
### Change type
- [x] `feature`
- [x] `api`
### Test plan
1. Paste a flowchart mermaid diagram (e.g. `graph TD; A-->B; B-->C;`)
into tldraw.com — shapes should appear
2. Paste a sequence diagram — shapes with lifelines and arrows should
appear
3. Paste a state diagram — shapes with transitions should appear
4. Paste an unsupported diagram type (e.g. pie chart) — should fall back
to SVG with a warning toast
5. Paste normal text — should behave as before (no mermaid handling)
- [x] Unit tests
### API changes
- Added `createMermaidDiagram(editor, text, options?)` — main entry
point to parse and render a Mermaid diagram
- Added `renderBlueprint(editor, blueprint, opts?)` — renders a
`DiagramMermaidBlueprint` into tldraw shapes
- Added `MermaidDiagramError` — error class for parse/unsupported
diagram errors
- Added types: `DiagramMermaidBlueprint`, `MermaidBlueprintGeoNode`,
`MermaidBlueprintEdge`, `MermaidBlueprintLineNode`,
`BlueprintRenderingOptions`, `MermaidDiagramOptions`
### Release notes
- Add Mermaid diagram support: paste Mermaid syntax to create native
tldraw shapes (flowcharts, sequence diagrams, state diagrams)
### Code changes
| Section | LOC change |
| --------------- | ------------- |
| Core code | +2661 / -0 |
| Tests | +964 / -0 |
| Automated files | +176 / -0 |
| Documentation | +1246 / -0 |
| Apps | +99 / -0 |
| Config/tooling | +1252 / -43 |
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Adds a new diagram parsing/rendering subsystem and hooks it into
editor paste handling, which could impact editor stability/perf and
external-content security surface (SVG/text parsing). Scope is large but
mostly additive behind lightweight detection and fallback behavior.
>
> **Overview**
> Adds Mermaid-to-tldraw conversion support via a new `@tldraw/mermaid`
package that parses Mermaid text, extracts layout from rendered SVG, and
renders a diagram-agnostic “blueprint” into native tldraw shapes
(nodes/lines/arrows/groups) for supported diagram types.
>
> Integrates this into dotcom editors (`LocalEditor` and `TlaEditor`)
with a `SneakyMermaidHandler` that detects Mermaid-ish pasted text
without bundling Mermaid eagerly, lazily `import()`s the converter on
demand, and falls back to importing SVG + showing a warning toast for
unsupported diagram types.
>
> Updates app deps/tsconfig references, adds i18n strings for the new
warning toast, and introduces an examples use-case showcasing
large-scale Mermaid rendering.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
6d3b07f. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Guillaume <guillaume@tldraw.com>1 parent a115bf3 commit 0bcbb3e
32 files changed
Lines changed: 6530 additions & 120 deletions
File tree
- apps
- dotcom/client
- public/tla
- locales-compiled
- locales
- src
- components
- SneakyMermaidHandler
- tla/components/TlaEditor
- examples
- src/examples/use-cases/hundred-mermaids
- packages/mermaid
- src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
36 | 36 | | |
37 | 37 | | |
38 | 38 | | |
| 39 | + | |
39 | 40 | | |
40 | 41 | | |
41 | 42 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
213 | 213 | | |
214 | 214 | | |
215 | 215 | | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
216 | 222 | | |
217 | 223 | | |
218 | 224 | | |
| |||
297 | 303 | | |
298 | 304 | | |
299 | 305 | | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
300 | 312 | | |
301 | 313 | | |
302 | 314 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
104 | 104 | | |
105 | 105 | | |
106 | 106 | | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
107 | 110 | | |
108 | 111 | | |
109 | 112 | | |
| |||
146 | 149 | | |
147 | 150 | | |
148 | 151 | | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
149 | 155 | | |
150 | 156 | | |
151 | 157 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| 11 | + | |
11 | 12 | | |
12 | 13 | | |
13 | 14 | | |
| |||
51 | 52 | | |
52 | 53 | | |
53 | 54 | | |
| 55 | + | |
54 | 56 | | |
55 | 57 | | |
56 | 58 | | |
| |||
Lines changed: 61 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
Lines changed: 109 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
Lines changed: 32 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
Lines changed: 2 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
| 26 | + | |
26 | 27 | | |
27 | 28 | | |
28 | 29 | | |
| |||
261 | 262 | | |
262 | 263 | | |
263 | 264 | | |
| 265 | + | |
264 | 266 | | |
265 | 267 | | |
266 | 268 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
| 27 | + | |
27 | 28 | | |
28 | 29 | | |
29 | 30 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
49 | 49 | | |
50 | 50 | | |
51 | 51 | | |
| 52 | + | |
52 | 53 | | |
53 | 54 | | |
54 | 55 | | |
| |||
58 | 59 | | |
59 | 60 | | |
60 | 61 | | |
| 62 | + | |
61 | 63 | | |
62 | 64 | | |
63 | 65 | | |
| |||
0 commit comments