@tldraw/mermaid 4.6.0-internal.c7df3c92455a

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.
Files changed (55) hide show
  1. package/dist-cjs/blueprint.js +17 -0
  2. package/dist-cjs/blueprint.js.map +7 -0
  3. package/dist-cjs/colors.js +173 -0
  4. package/dist-cjs/colors.js.map +7 -0
  5. package/dist-cjs/createMermaidDiagram.js +144 -0
  6. package/dist-cjs/createMermaidDiagram.js.map +7 -0
  7. package/dist-cjs/flowchartDiagram.js +202 -0
  8. package/dist-cjs/flowchartDiagram.js.map +7 -0
  9. package/dist-cjs/index.d.ts +114 -0
  10. package/dist-cjs/index.js +34 -0
  11. package/dist-cjs/index.js.map +7 -0
  12. package/dist-cjs/renderBlueprint.js +314 -0
  13. package/dist-cjs/renderBlueprint.js.map +7 -0
  14. package/dist-cjs/sequenceDiagram.js +686 -0
  15. package/dist-cjs/sequenceDiagram.js.map +7 -0
  16. package/dist-cjs/stateDiagram.js +373 -0
  17. package/dist-cjs/stateDiagram.js.map +7 -0
  18. package/dist-cjs/svgParsing.js +187 -0
  19. package/dist-cjs/svgParsing.js.map +7 -0
  20. package/dist-cjs/utils.js +75 -0
  21. package/dist-cjs/utils.js.map +7 -0
  22. package/dist-esm/blueprint.mjs +1 -0
  23. package/dist-esm/blueprint.mjs.map +7 -0
  24. package/dist-esm/colors.mjs +153 -0
  25. package/dist-esm/colors.mjs.map +7 -0
  26. package/dist-esm/createMermaidDiagram.mjs +114 -0
  27. package/dist-esm/createMermaidDiagram.mjs.map +7 -0
  28. package/dist-esm/flowchartDiagram.mjs +188 -0
  29. package/dist-esm/flowchartDiagram.mjs.map +7 -0
  30. package/dist-esm/index.d.mts +114 -0
  31. package/dist-esm/index.mjs +14 -0
  32. package/dist-esm/index.mjs.map +7 -0
  33. package/dist-esm/renderBlueprint.mjs +298 -0
  34. package/dist-esm/renderBlueprint.mjs.map +7 -0
  35. package/dist-esm/sequenceDiagram.mjs +666 -0
  36. package/dist-esm/sequenceDiagram.mjs.map +7 -0
  37. package/dist-esm/stateDiagram.mjs +359 -0
  38. package/dist-esm/stateDiagram.mjs.map +7 -0
  39. package/dist-esm/svgParsing.mjs +167 -0
  40. package/dist-esm/svgParsing.mjs.map +7 -0
  41. package/dist-esm/utils.mjs +55 -0
  42. package/dist-esm/utils.mjs.map +7 -0
  43. package/package.json +62 -0
  44. package/src/blueprint.ts +75 -0
  45. package/src/colors.ts +215 -0
  46. package/src/createMermaidDiagram.test.ts +31 -0
  47. package/src/createMermaidDiagram.ts +155 -0
  48. package/src/flowchartDiagram.ts +232 -0
  49. package/src/index.ts +18 -0
  50. package/src/mermaidDiagrams.test.ts +880 -0
  51. package/src/renderBlueprint.ts +373 -0
  52. package/src/sequenceDiagram.ts +851 -0
  53. package/src/stateDiagram.ts +477 -0
  54. package/src/svgParsing.ts +240 -0
  55. package/src/utils.ts +73 -0
@@ -0,0 +1,373 @@
1
+ import {
2
+ createShapeId,
3
+ Editor,
4
+ IndexKey,
5
+ TLGeoShape,
6
+ TLLineShape,
7
+ TLShapeId,
8
+ toRichText,
9
+ Vec,
10
+ } from 'tldraw'
11
+ import type {
12
+ DiagramMermaidBlueprint,
13
+ MermaidBlueprintEdge,
14
+ MermaidBlueprintGeoNode,
15
+ MermaidBlueprintLineNode,
16
+ } from './blueprint'
17
+ import { orderTopDown, sanitizeDiagramText } from './utils'
18
+
19
+ /** @public */
20
+ export interface BlueprintRenderingOptions {
21
+ centerOnPosition?: boolean
22
+ position?: { x: number; y: number }
23
+ }
24
+
25
+ const defaultBlueprintRenderingOptions = {
26
+ centerOnPosition: true,
27
+ }
28
+
29
+ /** @public */
30
+ export function renderBlueprint(
31
+ editor: Editor,
32
+ blueprint: DiagramMermaidBlueprint,
33
+ opts?: BlueprintRenderingOptions
34
+ ) {
35
+ const options = { ...defaultBlueprintRenderingOptions, ...(opts || {}) }
36
+ const { nodes, edges, lines } = blueprint
37
+
38
+ const bounds = computeBlueprintBounds(nodes, lines)
39
+ const center = options.position
40
+ ? options.position
41
+ : editor.user.getIsPasteAtCursorMode()
42
+ ? editor.inputs.getCurrentPagePoint()
43
+ : editor.getViewportPageBounds().center
44
+ const offsetX = options.centerOnPosition
45
+ ? center.x - (bounds.maxX + bounds.minX) / 2
46
+ : center.x - bounds.minX
47
+ const offsetY = options.centerOnPosition
48
+ ? center.y - (bounds.maxY + bounds.minY) / 2
49
+ : center.y - bounds.minY
50
+
51
+ const ordered = orderTopDown(
52
+ nodes,
53
+ (n) => n.id,
54
+ (n) => n.parentId
55
+ )
56
+ const nodeById = new Map(nodes.map((node) => [node.id, node]))
57
+
58
+ const shapeIds = new Map<string, TLShapeId>()
59
+
60
+ // Lines first so nodes render on top (z-order = creation order in tldraw)
61
+ if (lines) {
62
+ for (const line of lines) {
63
+ const lineId = createShapeId()
64
+ shapeIds.set(line.id, lineId)
65
+ editor.createShape<TLLineShape>({
66
+ id: lineId,
67
+ type: 'line',
68
+ x: offsetX + line.x,
69
+ y: offsetY + line.y,
70
+ props: {
71
+ dash: line.dash ?? 'solid',
72
+ size: line.size ?? 's',
73
+ color: line.color ?? 'black',
74
+ spline: 'line',
75
+ points: {
76
+ a1: { id: 'a1', index: 'a1' as IndexKey, x: 0, y: 0 },
77
+ a2: { id: 'a2', index: 'a2' as IndexKey, x: line.endX ?? 0, y: line.endY },
78
+ },
79
+ },
80
+ })
81
+ }
82
+ }
83
+
84
+ for (const node of ordered) {
85
+ const shapeId = createShapeId()
86
+ shapeIds.set(node.id, shapeId)
87
+
88
+ const parent = node.parentId ? nodeById.get(node.parentId) : undefined
89
+ const parentShapeId = node.parentId ? shapeIds.get(node.parentId) : undefined
90
+
91
+ const absoluteX = offsetX + node.x
92
+ const absoluteY = offsetY + node.y
93
+ const x = parent ? absoluteX - (offsetX + parent.x) : absoluteX
94
+ const y = parent ? absoluteY - (offsetY + parent.y) : absoluteY
95
+
96
+ editor.createShape<TLGeoShape>({
97
+ id: shapeId,
98
+ type: 'geo',
99
+ x,
100
+ y,
101
+ parentId: parentShapeId,
102
+ props: {
103
+ geo: node.geo,
104
+ w: node.w,
105
+ h: node.h,
106
+ fill: node.fill ?? 'none',
107
+ color: node.color ?? 'black',
108
+ dash: node.dash ?? 'draw',
109
+ size: node.size ?? 'm',
110
+ ...(node.label && { richText: toRichText(sanitizeDiagramText(node.label)) }),
111
+ ...(node.align && { align: node.align }),
112
+ ...(node.verticalAlign && { verticalAlign: node.verticalAlign }),
113
+ },
114
+ })
115
+ }
116
+
117
+ const arrowIds: TLShapeId[] = []
118
+ for (const edge of edges) {
119
+ const arrowId = createArrowFromEdge(editor, edge, shapeIds)
120
+ if (arrowId) arrowIds.push(arrowId)
121
+ }
122
+
123
+ // Create sub-groups and track which shape IDs are consumed by a group
124
+ const groupedIds = new Set<TLShapeId>()
125
+ const subGroupIds: TLShapeId[] = []
126
+ if (blueprint.groups) {
127
+ for (const group of blueprint.groups) {
128
+ const members: TLShapeId[] = []
129
+ for (const blueprintId of group) {
130
+ const memberShapeId = shapeIds.get(blueprintId)
131
+ if (memberShapeId) {
132
+ members.push(memberShapeId)
133
+ groupedIds.add(memberShapeId)
134
+ }
135
+ }
136
+ if (members.length > 1) {
137
+ editor.groupShapes(members)
138
+ const first = editor.getShape(members[0])
139
+ if (first && first.parentId !== editor.getCurrentPageId()) {
140
+ subGroupIds.push(first.parentId as TLShapeId)
141
+ } else {
142
+ subGroupIds.push(members[0])
143
+ }
144
+ } else if (members.length === 1) {
145
+ subGroupIds.push(members[0])
146
+ }
147
+ }
148
+ }
149
+
150
+ // Collect ungrouped top-level IDs
151
+ const topLevelIds: TLShapeId[] = [...subGroupIds]
152
+ for (const node of nodes) {
153
+ if (!node.parentId) {
154
+ const nodeShapeId = shapeIds.get(node.id)
155
+ if (nodeShapeId && !groupedIds.has(nodeShapeId)) topLevelIds.push(nodeShapeId)
156
+ }
157
+ }
158
+ if (lines) {
159
+ for (const line of lines) {
160
+ const lineShapeId = shapeIds.get(line.id)
161
+ if (lineShapeId && !groupedIds.has(lineShapeId)) topLevelIds.push(lineShapeId)
162
+ }
163
+ }
164
+ topLevelIds.push(...arrowIds)
165
+
166
+ let rootShapeId: TLShapeId | undefined
167
+ if (topLevelIds.length > 1) {
168
+ editor.groupShapes(topLevelIds)
169
+ const first = editor.getShape(topLevelIds[0])
170
+ if (first && first.parentId !== editor.getCurrentPageId()) {
171
+ rootShapeId = first.parentId as TLShapeId
172
+ }
173
+ } else if (topLevelIds.length === 1) {
174
+ rootShapeId = topLevelIds[0]
175
+ }
176
+
177
+ if (rootShapeId) {
178
+ const actualBounds = editor.getShapePageBounds(rootShapeId)
179
+ if (actualBounds) {
180
+ const desiredX = options.centerOnPosition ? center.x - actualBounds.w / 2 : center.x
181
+ const desiredY = options.centerOnPosition ? center.y - actualBounds.h / 2 : center.y
182
+ const dx = desiredX - actualBounds.x
183
+ const dy = desiredY - actualBounds.y
184
+ if (Math.abs(dx) > 0.5 || Math.abs(dy) > 0.5) {
185
+ const shape = editor.getShape(rootShapeId)!
186
+ editor.updateShape({
187
+ id: rootShapeId,
188
+ type: shape.type,
189
+ x: shape.x + dx,
190
+ y: shape.y + dy,
191
+ })
192
+ }
193
+ }
194
+ }
195
+ }
196
+
197
+ function makeArrowBinding(
198
+ arrowId: TLShapeId,
199
+ targetId: TLShapeId,
200
+ terminal: 'start' | 'end',
201
+ anchor: { x: number; y: number },
202
+ isExact: boolean,
203
+ isPrecise: boolean
204
+ ) {
205
+ return {
206
+ fromId: arrowId,
207
+ toId: targetId,
208
+ type: 'arrow' as const,
209
+ props: { terminal, normalizedAnchor: anchor, isExact, isPrecise },
210
+ }
211
+ }
212
+
213
+ function createArrowFromEdge(
214
+ editor: Editor,
215
+ edge: MermaidBlueprintEdge,
216
+ shapeIds: Map<string, TLShapeId>
217
+ ): TLShapeId | undefined {
218
+ const startShapeId = shapeIds.get(edge.startNodeId)
219
+ const endShapeId = shapeIds.get(edge.endNodeId)
220
+ if (!startShapeId || !endShapeId) return undefined
221
+
222
+ const startBounds = editor.getShapePageBounds(startShapeId)
223
+ const endBounds = editor.getShapePageBounds(endShapeId)
224
+ if (!startBounds || !endBounds) return undefined
225
+
226
+ const arrowId = createShapeId()
227
+ const isSelfLoop = startShapeId === endShapeId
228
+ const hasPreciseAnchors = edge.anchorStartY !== undefined || edge.anchorEndY !== undefined
229
+
230
+ let labelText = edge.label
231
+ if (edge.decoration?.type === 'autonumber') {
232
+ const num = edge.decoration.value
233
+ labelText = labelText ? `${num} ${labelText}` : num
234
+ }
235
+
236
+ const baseProps = {
237
+ dash: edge.dash ?? ('solid' as const),
238
+ size: edge.size ?? ('s' as const),
239
+ arrowheadEnd: edge.arrowheadEnd ?? ('arrow' as const),
240
+ ...(edge.arrowheadStart && { arrowheadStart: edge.arrowheadStart }),
241
+ color: edge.color ?? ('black' as const),
242
+ ...(labelText && { richText: toRichText(sanitizeDiagramText(labelText)) }),
243
+ }
244
+
245
+ if (hasPreciseAnchors) {
246
+ const startAnchorY = edge.anchorStartY ?? 0.5
247
+ const endAnchorY = edge.anchorEndY ?? 0.5
248
+ const exactStart = edge.isExact ?? true
249
+ const preciseStart = edge.isPrecise ?? true
250
+ const exactEnd = edge.isExactEnd ?? exactStart
251
+ const preciseEnd = edge.isPreciseEnd ?? preciseStart
252
+
253
+ const startPoint = {
254
+ x: startBounds.x + startBounds.w * 0.5,
255
+ y: startBounds.y + startBounds.h * startAnchorY,
256
+ }
257
+ const endPoint = {
258
+ x: endBounds.x + endBounds.w * 0.5,
259
+ y: endBounds.y + endBounds.h * endAnchorY,
260
+ }
261
+ const origin = {
262
+ x: Math.min(startPoint.x, endPoint.x),
263
+ y: Math.min(startPoint.y, endPoint.y),
264
+ }
265
+
266
+ editor.run(() => {
267
+ editor.createShape({
268
+ id: arrowId,
269
+ type: 'arrow',
270
+ x: origin.x,
271
+ y: origin.y,
272
+ props: {
273
+ ...baseProps,
274
+ start: { x: startPoint.x - origin.x, y: startPoint.y - origin.y },
275
+ end: { x: endPoint.x - origin.x, y: endPoint.y - origin.y },
276
+ bend: edge.bend,
277
+ },
278
+ })
279
+ editor.createBindings([
280
+ makeArrowBinding(
281
+ arrowId,
282
+ startShapeId,
283
+ 'start',
284
+ { x: 0.5, y: startAnchorY },
285
+ exactStart,
286
+ preciseStart
287
+ ),
288
+ makeArrowBinding(
289
+ arrowId,
290
+ endShapeId,
291
+ 'end',
292
+ { x: 0.5, y: endAnchorY },
293
+ exactEnd,
294
+ preciseEnd
295
+ ),
296
+ ])
297
+ })
298
+ return arrowId
299
+ }
300
+
301
+ if (isSelfLoop) {
302
+ editor.run(() => {
303
+ editor.createShape({
304
+ id: arrowId,
305
+ type: 'arrow',
306
+ x: startBounds.x,
307
+ y: startBounds.y,
308
+ props: {
309
+ ...baseProps,
310
+ start: { x: startBounds.w / 2, y: 0 },
311
+ end: { x: startBounds.w, y: startBounds.h / 2 },
312
+ bend: -80,
313
+ },
314
+ })
315
+ editor.createBindings([
316
+ makeArrowBinding(arrowId, startShapeId, 'start', { x: 0.9, y: 0.5 }, false, false),
317
+ makeArrowBinding(arrowId, endShapeId, 'end', { x: 0.85, y: 0.8 }, false, false),
318
+ ])
319
+ })
320
+ return arrowId
321
+ }
322
+
323
+ const startCenter = startBounds.center
324
+ const endCenter = endBounds.center
325
+ const arrowOrigin = Vec.Min(startCenter, endCenter)
326
+
327
+ editor.run(() => {
328
+ editor.createShape({
329
+ id: arrowId,
330
+ type: 'arrow',
331
+ x: arrowOrigin.x,
332
+ y: arrowOrigin.y,
333
+ props: {
334
+ ...baseProps,
335
+ start: { x: startCenter.x - arrowOrigin.x, y: startCenter.y - arrowOrigin.y },
336
+ end: { x: endCenter.x - arrowOrigin.x, y: endCenter.y - arrowOrigin.y },
337
+ bend: edge.bend,
338
+ },
339
+ })
340
+ editor.createBindings([
341
+ makeArrowBinding(arrowId, startShapeId, 'start', { x: 0.5, y: 0.5 }, false, false),
342
+ makeArrowBinding(arrowId, endShapeId, 'end', { x: 0.5, y: 0.5 }, false, false),
343
+ ])
344
+ })
345
+
346
+ return arrowId
347
+ }
348
+
349
+ function computeBlueprintBounds(
350
+ nodes: MermaidBlueprintGeoNode[],
351
+ lines?: MermaidBlueprintLineNode[]
352
+ ): { minX: number; minY: number; maxX: number; maxY: number } {
353
+ let minX = Infinity,
354
+ minY = Infinity,
355
+ maxX = -Infinity,
356
+ maxY = -Infinity
357
+ for (const node of nodes) {
358
+ if (node.parentId) continue
359
+ minX = Math.min(minX, node.x)
360
+ minY = Math.min(minY, node.y)
361
+ maxX = Math.max(maxX, node.x + node.w)
362
+ maxY = Math.max(maxY, node.y + node.h)
363
+ }
364
+ if (lines) {
365
+ for (const line of lines) {
366
+ minX = Math.min(minX, line.x)
367
+ minY = Math.min(minY, line.y)
368
+ maxX = Math.max(maxX, line.x)
369
+ maxY = Math.max(maxY, line.y + line.endY)
370
+ }
371
+ }
372
+ return { minX, minY, maxX, maxY }
373
+ }