@tldraw/mermaid 5.2.0-next.ee0fa4d6244f → 5.2.1
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 +7 -1
- package/dist-cjs/createMermaidDiagram.js +5 -5
- package/dist-cjs/createMermaidDiagram.js.map +3 -3
- package/dist-cjs/flowchartDiagram.js +7 -3
- package/dist-cjs/flowchartDiagram.js.map +2 -2
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/mindmapDiagram.js +1 -1
- package/dist-cjs/mindmapDiagram.js.map +2 -2
- package/dist-cjs/sequenceDiagram.js +20 -1
- package/dist-cjs/sequenceDiagram.js.map +2 -2
- package/dist-cjs/stateDiagram.js +7 -3
- package/dist-cjs/stateDiagram.js.map +2 -2
- package/dist-cjs/svgParsing.js +2 -2
- package/dist-cjs/svgParsing.js.map +2 -2
- package/dist-esm/createMermaidDiagram.mjs +1 -1
- package/dist-esm/createMermaidDiagram.mjs.map +2 -2
- package/dist-esm/flowchartDiagram.mjs +7 -3
- package/dist-esm/flowchartDiagram.mjs.map +2 -2
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/mindmapDiagram.mjs +1 -1
- package/dist-esm/mindmapDiagram.mjs.map +2 -2
- package/dist-esm/sequenceDiagram.mjs +20 -1
- package/dist-esm/sequenceDiagram.mjs.map +2 -2
- package/dist-esm/stateDiagram.mjs +7 -3
- package/dist-esm/stateDiagram.mjs.map +2 -2
- package/dist-esm/svgParsing.mjs +2 -2
- package/dist-esm/svgParsing.mjs.map +2 -2
- package/package.json +9 -6
- package/src/createMermaidDiagram.ts +7 -1
- package/src/flowchartDiagram.ts +8 -3
- package/src/layoutParsing.test.ts +71 -0
- package/src/mindmapDiagram.ts +1 -1
- package/src/sequenceDiagram.ts +19 -0
- package/src/stateDiagram.ts +10 -3
- package/src/svgParsing.test.ts +58 -0
- package/src/svgParsing.ts +6 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/stateDiagram.ts"],
|
|
4
|
-
"sourcesContent": ["import type { StateStmt, StyleClass } from 'mermaid/dist/diagrams/state/stateDb.d.ts'\nimport type {\n\tDiagramMermaidBlueprint,\n\tMermaidBlueprintEdge,\n\tMermaidBlueprintNode,\n} from './blueprint'\nimport { buildClassDefColorMap, type ParsedNodeColors } from './colors'\nimport {\n\tbuildNodeCentersFromSvg,\n\tparseAllEdgePointsFromSvg,\n\tparseClustersFromSvg,\n\ttype ParsedDiagramLayout,\n\tparseNodesFromSvg,\n\tscaleLayout,\n} from './svgParsing'\nimport { getArrowBend, LAYOUT_SCALE, orderTopDown } from './utils'\n\ninterface DiagramEdge {\n\tid1: string\n\tid2: string\n\trelationTitle?: string\n}\n\ninterface FlatState {\n\tid: string\n\ttype: string\n\tlabel: string\n}\n\nfunction getEffectiveType(state: StateStmt): string {\n\tif (state.type && state.type !== 'default') return state.type\n\t// Mermaid auto-generates start/end pseudo-state ids ending with \"_start\"\n\t// or \"_end\", optionally followed by a disambiguation digit (e.g. \"_start2\").\n\tif (/_start\\d*$/.test(state.id)) return 'start'\n\tif (/_end\\d*$/.test(state.id)) return 'end'\n\treturn state.type || 'default'\n}\n\nconst UNLABELED_TYPES = new Set(['start', 'end'])\n\nfunction getStateLabel(state: StateStmt): string {\n\tif (state.descriptions && state.descriptions.length > 0) {\n\t\treturn state.descriptions.join('\\n')\n\t}\n\tif (state.description) return state.description\n\tif (UNLABELED_TYPES.has(getEffectiveType(state))) return ''\n\treturn state.id\n}\n\ninterface FlattenResult {\n\tleafStates: Map<string, FlatState>\n\tcompoundLabels: Map<string, string>\n\tparentOf: Map<string, string>\n\tallEdges: DiagramEdge[]\n}\n\nfunction flattenStateHierarchy(\n\tstates: Map<string, StateStmt>,\n\trelations: DiagramEdge[],\n\tparentCompound: string | null = null,\n\ttopLevelStates?: Map<string, StateStmt>\n): FlattenResult {\n\tconst leafStates = new Map<string, FlatState>()\n\tconst compoundLabels = new Map<string, string>()\n\tconst parentOf = new Map<string, string>()\n\tconst allEdges: DiagramEdge[] = []\n\tconst root = topLevelStates ?? states\n\n\tfor (const [id, state] of states) {\n\t\tif (parentCompound) parentOf.set(id, parentCompound)\n\n\t\tif (state.doc && state.doc.length > 0) {\n\t\t\tcompoundLabels.set(id, state.description || id)\n\n\t\t\tconst childStatesMap = new Map<string, StateStmt>()\n\t\t\tconst childRelations: DiagramEdge[] = []\n\n\t\t\tfor (const stmt of state.doc) {\n\t\t\t\tif (typeof stmt === 'string') {\n\t\t\t\t\t// Mermaid emits raw strings for stereotyped state declarations\n\t\t\t\t\t// like `state H <<history>>` \u2014 the ID and stereotype are stored\n\t\t\t\t\t// as separate plain string entries. Look up the state in the\n\t\t\t\t\t// top-level map so it gets proper parentage.\n\t\t\t\t\tconst topState = root.get(stmt)\n\t\t\t\t\tif (topState && !childStatesMap.has(stmt)) {\n\t\t\t\t\t\tchildStatesMap.set(stmt, topState)\n\t\t\t\t\t}\n\t\t\t\t} else if (stmt.stmt === 'state' || stmt.stmt === 'default') {\n\t\t\t\t\tconst stateEntry = stmt as StateStmt\n\t\t\t\t\tchildStatesMap.set(stateEntry.id, stateEntry)\n\t\t\t\t} else if (stmt.stmt === 'relation') {\n\t\t\t\t\tconst relation = stmt as unknown as {\n\t\t\t\t\t\tstate1: StateStmt\n\t\t\t\t\t\tstate2: StateStmt\n\t\t\t\t\t\tdescription?: string\n\t\t\t\t\t}\n\t\t\t\t\t// Relation state refs are shallow objects without `doc`. Only add\n\t\t\t\t\t// them when the state hasn't been registered yet so we don't\n\t\t\t\t\t// overwrite a compound-state entry that carries its nested doc.\n\t\t\t\t\tif (!childStatesMap.has(relation.state1.id)) {\n\t\t\t\t\t\tchildStatesMap.set(relation.state1.id, relation.state1)\n\t\t\t\t\t}\n\t\t\t\t\tif (!childStatesMap.has(relation.state2.id)) {\n\t\t\t\t\t\tchildStatesMap.set(relation.state2.id, relation.state2)\n\t\t\t\t\t}\n\t\t\t\t\tchildRelations.push({\n\t\t\t\t\t\tid1: relation.state1.id,\n\t\t\t\t\t\tid2: relation.state2.id,\n\t\t\t\t\t\trelationTitle: relation.description,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst nested = flattenStateHierarchy(childStatesMap, childRelations, id, root)\n\t\t\tfor (const [key, state] of nested.leafStates) leafStates.set(key, state)\n\t\t\tfor (const [key, label] of nested.compoundLabels) compoundLabels.set(key, label)\n\t\t\tfor (const [key, parent] of nested.parentOf) parentOf.set(key, parent)\n\t\t\tallEdges.push(...nested.allEdges)\n\t\t} else {\n\t\t\tleafStates.set(id, {\n\t\t\t\tid,\n\t\t\t\ttype: getEffectiveType(state),\n\t\t\t\tlabel: getStateLabel(state),\n\t\t\t})\n\t\t}\n\t}\n\n\tallEdges.push(...relations)\n\n\treturn { leafStates, compoundLabels, parentOf, allEdges }\n}\n\nconst FIXED_NODE_SIZES: Record<string, [number, number]> = {\n\tstart: [36, 36],\n\tend: [40, 40],\n}\n\nfunction stateToNodes(\n\tstate: FlatState,\n\tx: number,\n\ty: number,\n\tw: number,\n\th: number,\n\tparentId: string | undefined,\n\tcolors: ParsedNodeColors | undefined\n): MermaidBlueprintNode[] {\n\tconst base = { x, y, w, h, parentId, color: 'black' as const }\n\tconst label = state.label || undefined\n\n\tconst node = (\n\t\tid: string,\n\t\tkind: string,\n\t\tpartial: Partial<Pick<MermaidBlueprintNode, 'x' | 'y' | 'w' | 'h' | 'parentId'>> &\n\t\t\tOmit<MermaidBlueprintNode, 'id' | 'kind' | 'x' | 'y' | 'w' | 'h' | 'parentId'>\n\t): MermaidBlueprintNode => ({\n\t\tid,\n\t\tkind,\n\t\t...base,\n\t\t...partial,\n\t})\n\n\tswitch (state.type) {\n\t\tcase 'note':\n\t\t\treturn [\n\t\t\t\tnode(state.id, 'note', {\n\t\t\t\t\tlabel,\n\t\t\t\t\tfill: 'solid',\n\t\t\t\t\tcolor: 'yellow',\n\t\t\t\t\tsize: 's',\n\t\t\t\t\talign: 'middle',\n\t\t\t\t\tverticalAlign: 'middle',\n\t\t\t\t}),\n\t\t\t]\n\t\tcase 'start':\n\t\t\treturn [node(state.id, 'start', { fill: 'solid' })]\n\t\tcase 'end': {\n\t\t\tconst innerSize = w * 0.6\n\t\t\tconst innerId = `${state.id}__inner`\n\t\t\treturn [\n\t\t\t\tnode(state.id, 'end', { fill: 'none' }),\n\t\t\t\tnode(innerId, 'end_inner', {\n\t\t\t\t\tx: x + (w - innerSize) / 2,\n\t\t\t\t\ty: y + (h - innerSize) / 2,\n\t\t\t\t\tw: innerSize,\n\t\t\t\t\th: innerSize,\n\t\t\t\t\tfill: 'solid',\n\t\t\t\t}),\n\t\t\t]\n\t\t}\n\t\tcase 'fork':\n\t\tcase 'join': {\n\t\t\tconst barW = w * 4\n\t\t\tconst barH = Math.max(16, barW / 10)\n\t\t\treturn [\n\t\t\t\tnode(state.id, state.type, {\n\t\t\t\t\tx: x - (barW - w) / 2,\n\t\t\t\t\ty: y + (h - barH) / 2,\n\t\t\t\t\tw: barW,\n\t\t\t\t\th: barH,\n\t\t\t\t\tfill: 'solid',\n\t\t\t\t}),\n\t\t\t]\n\t\t}\n\t\tcase 'choice':\n\t\t\treturn [\n\t\t\t\tnode(state.id, 'choice', {\n\t\t\t\t\tlabel,\n\t\t\t\t\talign: 'middle',\n\t\t\t\t\tverticalAlign: 'middle',\n\t\t\t\t\tsize: 'm',\n\t\t\t\t}),\n\t\t\t]\n\t\tdefault:\n\t\t\treturn [\n\t\t\t\tnode(state.id, state.type, {\n\t\t\t\t\tlabel,\n\t\t\t\t\t...(colors?.fillColor && { fill: 'solid' as const }),\n\t\t\t\t\t...(colors && { color: colors.strokeColor ?? colors.fillColor }),\n\t\t\t\t\talign: 'middle',\n\t\t\t\t\tverticalAlign: 'middle',\n\t\t\t\t\tsize: 'm',\n\t\t\t\t}),\n\t\t\t]\n\t}\n}\n\nconst FRAME_PAD = 24\nconst FRAME_TOP = 54\n\n/** Parse state-diagram SVG layout data for use by {@link stateToBlueprint}. */\nexport function parseStateDiagramLayout(root: Element): ParsedDiagramLayout {\n\tconst nodes = parseNodesFromSvg(\n\t\troot,\n\t\t'.node',\n\t\t(domId) => domId.match(/^state-(.+)-\\d+$/)?.[1] ?? domId\n\t)\n\tconst clusters = parseClustersFromSvg(root, '.statediagram-cluster')\n\tconst edges = parseAllEdgePointsFromSvg(root, (dataId) =>\n\t\t/^edge\\d+$/.test(dataId) ? { start: '', end: '' } : null\n\t)\n\tscaleLayout(nodes, clusters, edges, LAYOUT_SCALE)\n\treturn { nodes, clusters, edges }\n}\n\n/** Convert a parsed Mermaid state diagram into a tldraw blueprint of nodes and edges. */\nexport function stateToBlueprint(\n\tlayout: ParsedDiagramLayout,\n\tstates: Map<string, StateStmt>,\n\trelations: DiagramEdge[],\n\tclassDefs?: Map<string, StyleClass>\n): DiagramMermaidBlueprint {\n\tconst stateColorMap = classDefs ? buildClassDefColorMap(classDefs, states) : new Map()\n\tconst { nodes: svgNodes, clusters: svgClusters, edges: svgEdges } = layout\n\tconst nodeCenters = buildNodeCentersFromSvg(svgNodes, svgClusters)\n\n\tconst { leafStates, compoundLabels, parentOf, allEdges } = flattenStateHierarchy(\n\t\tstates,\n\t\trelations\n\t)\n\n\t// Collect notes attached to states and add them as synthetic leaf nodes + edges.\n\tfor (const [id, state] of states) {\n\t\tconst note = (state as StateStmt & { note?: { position?: string; text: string } }).note\n\t\tif (!note) continue\n\n\t\tconst noteId = `${id}----note`\n\t\tleafStates.set(noteId, { id: noteId, type: 'note', label: note.text.trim() })\n\t\tallEdges.push({ id1: id, id2: noteId, relationTitle: undefined })\n\t}\n\n\tconst nodeLayout = new Map<string, { absX: number; absY: number; w: number; h: number }>()\n\tfor (const [id, state] of leafStates) {\n\t\tconst svgNode = svgNodes.get(id)\n\t\tif (!svgNode) continue\n\n\t\tconst fixed = FIXED_NODE_SIZES[state.type]\n\t\tconst nodeWidth = fixed ? fixed[0] : svgNode.width + 20\n\t\tconst nodeHeight = fixed ? fixed[1] : svgNode.height + 8\n\t\tnodeLayout.set(id, {\n\t\t\tabsX: svgNode.center.x - nodeWidth / 2,\n\t\t\tabsY: svgNode.center.y - nodeHeight / 2,\n\t\t\tw: nodeWidth,\n\t\t\th: nodeHeight,\n\t\t})\n\t}\n\n\tconst compoundIds = [...compoundLabels.keys()]\n\tconst frameBounds = new Map<string, { absX: number; absY: number; w: number; h: number }>()\n\n\t// Use SVG cluster bounds as the authoritative frame size. Mermaid's\n\t// layout already accounts for label width, padding, and special nodes\n\t// like <<history>> that are rendered outside the visual cluster.\n\t// Fall back to child-based computation only when no SVG cluster exists.\n\tconst bottomUp = orderTopDown(\n\t\tcompoundIds,\n\t\t(id) => id,\n\t\t(id) => parentOf.get(id)\n\t).reverse()\n\n\tfor (const compoundId of bottomUp) {\n\t\tconst cluster = svgClusters.get(compoundId)\n\t\tif (cluster) {\n\t\t\tframeBounds.set(compoundId, {\n\t\t\t\tabsX: cluster.topLeft.x,\n\t\t\t\tabsY: cluster.topLeft.y,\n\t\t\t\tw: cluster.width,\n\t\t\t\th: cluster.height,\n\t\t\t})\n\t\t\tcontinue\n\t\t}\n\n\t\tlet frameMinX = Infinity\n\t\tlet frameMinY = Infinity\n\t\tlet frameMaxX = -Infinity\n\t\tlet frameMaxY = -Infinity\n\n\t\tfor (const [id] of leafStates) {\n\t\t\tif (parentOf.get(id) !== compoundId) continue\n\n\t\t\tconst layout = nodeLayout.get(id)\n\t\t\tif (!layout) continue\n\n\t\t\tframeMinX = Math.min(frameMinX, layout.absX)\n\t\t\tframeMinY = Math.min(frameMinY, layout.absY)\n\t\t\tframeMaxX = Math.max(frameMaxX, layout.absX + layout.w)\n\t\t\tframeMaxY = Math.max(frameMaxY, layout.absY + layout.h)\n\t\t}\n\n\t\tfor (const innerId of compoundIds) {\n\t\t\tif (parentOf.get(innerId) !== compoundId) continue\n\n\t\t\tconst innerBounds = frameBounds.get(innerId)\n\t\t\tif (!innerBounds) continue\n\n\t\t\tframeMinX = Math.min(frameMinX, innerBounds.absX)\n\t\t\tframeMinY = Math.min(frameMinY, innerBounds.absY)\n\t\t\tframeMaxX = Math.max(frameMaxX, innerBounds.absX + innerBounds.w)\n\t\t\tframeMaxY = Math.max(frameMaxY, innerBounds.absY + innerBounds.h)\n\t\t}\n\n\t\tif (!isFinite(frameMinX)) continue\n\n\t\tframeBounds.set(compoundId, {\n\t\t\tabsX: frameMinX - FRAME_PAD,\n\t\t\tabsY: frameMinY - FRAME_TOP,\n\t\t\tw: frameMaxX - frameMinX + FRAME_PAD * 2,\n\t\t\th: frameMaxY - frameMinY + FRAME_PAD + FRAME_TOP,\n\t\t})\n\t}\n\n\t// Un-parent any leaf state whose center falls outside its parent frame.\n\t// Mermaid renders some pseudo-states (e.g. <<history>>) outside the\n\t// visual compound cluster even though they're declared inside it.\n\tfor (const [id] of leafStates) {\n\t\tconst pid = parentOf.get(id)\n\t\tif (!pid) continue\n\n\t\tconst frame = frameBounds.get(pid)\n\t\tconst layout = nodeLayout.get(id)\n\t\tif (!frame || !layout) continue\n\n\t\tconst cx = layout.absX + layout.w / 2\n\t\tconst cy = layout.absY + layout.h / 2\n\t\tif (\n\t\t\tcx < frame.absX ||\n\t\t\tcx > frame.absX + frame.w ||\n\t\t\tcy < frame.absY ||\n\t\t\tcy > frame.absY + frame.h\n\t\t) {\n\t\t\tparentOf.delete(id)\n\t\t}\n\t}\n\n\tconst nodes: MermaidBlueprintNode[] = []\n\tconst blueprintEdges: MermaidBlueprintEdge[] = []\n\n\tfor (const compoundId of orderTopDown(\n\t\tcompoundIds,\n\t\t(id) => id,\n\t\t(id) => parentOf.get(id)\n\t)) {\n\t\tconst bounds = frameBounds.get(compoundId)\n\t\tif (!bounds) continue\n\n\t\tconst kind = 'compound'\n\t\tnodes.push({\n\t\t\tid: compoundId,\n\t\t\tkind,\n\t\t\tx: bounds.absX,\n\t\t\ty: bounds.absY,\n\t\t\tw: bounds.w,\n\t\t\th: bounds.h,\n\t\t\tparentId: parentOf.get(compoundId),\n\t\t\tlabel: compoundLabels.get(compoundId) || compoundId,\n\t\t\tfill: 'semi',\n\t\t\tcolor: 'black',\n\t\t\tdash: 'draw',\n\t\t\tsize: 's',\n\t\t\talign: 'middle',\n\t\t\tverticalAlign: 'start',\n\t\t})\n\t}\n\n\tfor (const [id, state] of leafStates) {\n\t\tconst layout = nodeLayout.get(id)\n\t\tif (!layout) continue\n\n\t\tnodes.push(\n\t\t\t...stateToNodes(\n\t\t\t\tstate,\n\t\t\t\tlayout.absX,\n\t\t\t\tlayout.absY,\n\t\t\t\tlayout.w,\n\t\t\t\tlayout.h,\n\t\t\t\tparentOf.get(id),\n\t\t\t\tstateColorMap.get(id)\n\t\t\t)\n\t\t)\n\t}\n\n\tconst claimed = new Set<number>()\n\n\tfor (const edge of allEdges) {\n\t\tconst startCenter = nodeCenters.get(edge.id1)\n\t\tconst endCenter = nodeCenters.get(edge.id2)\n\n\t\tlet bend = 0\n\t\tif (startCenter && endCenter) {\n\t\t\tlet bestIndex = -1\n\t\t\tlet bestDistance = Infinity\n\t\t\tfor (let edgeIndex = 0; edgeIndex < svgEdges.length; edgeIndex++) {\n\t\t\t\tif (claimed.has(edgeIndex) || svgEdges[edgeIndex].points.length < 2) continue\n\n\t\t\t\tconst points = svgEdges[edgeIndex].points\n\t\t\t\tconst distance =\n\t\t\t\t\tMath.hypot(points[0].x - startCenter.x, points[0].y - startCenter.y) +\n\t\t\t\t\tMath.hypot(\n\t\t\t\t\t\tpoints[points.length - 1].x - endCenter.x,\n\t\t\t\t\t\tpoints[points.length - 1].y - endCenter.y\n\t\t\t\t\t)\n\t\t\t\tif (distance < bestDistance) {\n\t\t\t\t\tbestDistance = distance\n\t\t\t\t\tbestIndex = edgeIndex\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (bestIndex >= 0) {\n\t\t\t\tclaimed.add(bestIndex)\n\t\t\t\tbend = getArrowBend(svgEdges[bestIndex])\n\t\t\t}\n\t\t}\n\n\t\tconst isNoteEdge = edge.id2.endsWith('----note') || edge.id1.endsWith('----note')\n\t\tblueprintEdges.push({\n\t\t\tstartNodeId: edge.id1,\n\t\t\tendNodeId: edge.id2,\n\t\t\tlabel: edge.relationTitle,\n\t\t\tbend,\n\t\t\t...(isNoteEdge && { dash: 'dotted' as const, arrowheadEnd: 'none' as const }),\n\t\t})\n\t}\n\n\tconst nodeIds = new Set(nodes.map((n) => n.id))\n\tconst validEdges = blueprintEdges.filter(\n\t\t(e) => nodeIds.has(e.startNodeId) && nodeIds.has(e.endNodeId)\n\t)\n\treturn { diagramKind: 'state', nodes, edges: validEdges }\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import type { StateStmt, StyleClass } from 'mermaid/dist/diagrams/state/stateDb.d.ts'\nimport type {\n\tDiagramMermaidBlueprint,\n\tMermaidBlueprintEdge,\n\tMermaidBlueprintNode,\n} from './blueprint'\nimport { buildClassDefColorMap, type ParsedNodeColors } from './colors'\nimport {\n\tbuildNodeCentersFromSvg,\n\tparseAllEdgePointsFromSvg,\n\tparseClustersFromSvg,\n\ttype ParsedDiagramLayout,\n\tparseNodesFromSvg,\n\tscaleLayout,\n} from './svgParsing'\nimport { getArrowBend, LAYOUT_SCALE, orderTopDown } from './utils'\n\ninterface DiagramEdge {\n\tid1: string\n\tid2: string\n\trelationTitle?: string\n}\n\ninterface FlatState {\n\tid: string\n\ttype: string\n\tlabel: string\n}\n\nfunction getEffectiveType(state: StateStmt): string {\n\tif (state.type && state.type !== 'default') return state.type\n\t// Mermaid auto-generates start/end pseudo-state ids ending with \"_start\"\n\t// or \"_end\", optionally followed by a disambiguation digit (e.g. \"_start2\").\n\tif (/_start\\d*$/.test(state.id)) return 'start'\n\tif (/_end\\d*$/.test(state.id)) return 'end'\n\treturn state.type || 'default'\n}\n\nconst UNLABELED_TYPES = new Set(['start', 'end'])\n\nfunction getStateLabel(state: StateStmt): string {\n\tif (state.descriptions && state.descriptions.length > 0) {\n\t\treturn state.descriptions.join('\\n')\n\t}\n\tif (state.description) return state.description\n\tif (UNLABELED_TYPES.has(getEffectiveType(state))) return ''\n\treturn state.id\n}\n\ninterface FlattenResult {\n\tleafStates: Map<string, FlatState>\n\tcompoundLabels: Map<string, string>\n\tparentOf: Map<string, string>\n\tallEdges: DiagramEdge[]\n}\n\nfunction flattenStateHierarchy(\n\tstates: Map<string, StateStmt>,\n\trelations: DiagramEdge[],\n\tparentCompound: string | null = null,\n\ttopLevelStates?: Map<string, StateStmt>\n): FlattenResult {\n\tconst leafStates = new Map<string, FlatState>()\n\tconst compoundLabels = new Map<string, string>()\n\tconst parentOf = new Map<string, string>()\n\tconst allEdges: DiagramEdge[] = []\n\tconst root = topLevelStates ?? states\n\n\tfor (const [id, state] of states) {\n\t\tif (parentCompound) parentOf.set(id, parentCompound)\n\n\t\tif (state.doc && state.doc.length > 0) {\n\t\t\tcompoundLabels.set(id, state.description || id)\n\n\t\t\tconst childStatesMap = new Map<string, StateStmt>()\n\t\t\tconst childRelations: DiagramEdge[] = []\n\n\t\t\tfor (const stmt of state.doc) {\n\t\t\t\tif (typeof stmt === 'string') {\n\t\t\t\t\t// Mermaid emits raw strings for stereotyped state declarations\n\t\t\t\t\t// like `state H <<history>>` \u2014 the ID and stereotype are stored\n\t\t\t\t\t// as separate plain string entries. Look up the state in the\n\t\t\t\t\t// top-level map so it gets proper parentage.\n\t\t\t\t\tconst topState = root.get(stmt)\n\t\t\t\t\tif (topState && !childStatesMap.has(stmt)) {\n\t\t\t\t\t\tchildStatesMap.set(stmt, topState)\n\t\t\t\t\t}\n\t\t\t\t} else if (stmt.stmt === 'state' || stmt.stmt === 'default') {\n\t\t\t\t\tconst stateEntry = stmt as StateStmt\n\t\t\t\t\tchildStatesMap.set(stateEntry.id, stateEntry)\n\t\t\t\t} else if (stmt.stmt === 'relation') {\n\t\t\t\t\tconst relation = stmt as unknown as {\n\t\t\t\t\t\tstate1: StateStmt\n\t\t\t\t\t\tstate2: StateStmt\n\t\t\t\t\t\tdescription?: string\n\t\t\t\t\t}\n\t\t\t\t\t// Relation state refs are shallow objects without `doc`. Only add\n\t\t\t\t\t// them when the state hasn't been registered yet so we don't\n\t\t\t\t\t// overwrite a compound-state entry that carries its nested doc.\n\t\t\t\t\tif (!childStatesMap.has(relation.state1.id)) {\n\t\t\t\t\t\tchildStatesMap.set(relation.state1.id, relation.state1)\n\t\t\t\t\t}\n\t\t\t\t\tif (!childStatesMap.has(relation.state2.id)) {\n\t\t\t\t\t\tchildStatesMap.set(relation.state2.id, relation.state2)\n\t\t\t\t\t}\n\t\t\t\t\tchildRelations.push({\n\t\t\t\t\t\tid1: relation.state1.id,\n\t\t\t\t\t\tid2: relation.state2.id,\n\t\t\t\t\t\trelationTitle: relation.description,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst nested = flattenStateHierarchy(childStatesMap, childRelations, id, root)\n\t\t\tfor (const [key, state] of nested.leafStates) leafStates.set(key, state)\n\t\t\tfor (const [key, label] of nested.compoundLabels) compoundLabels.set(key, label)\n\t\t\tfor (const [key, parent] of nested.parentOf) parentOf.set(key, parent)\n\t\t\tallEdges.push(...nested.allEdges)\n\t\t} else {\n\t\t\tleafStates.set(id, {\n\t\t\t\tid,\n\t\t\t\ttype: getEffectiveType(state),\n\t\t\t\tlabel: getStateLabel(state),\n\t\t\t})\n\t\t}\n\t}\n\n\tallEdges.push(...relations)\n\n\treturn { leafStates, compoundLabels, parentOf, allEdges }\n}\n\nconst FIXED_NODE_SIZES: Record<string, [number, number]> = {\n\tstart: [36, 36],\n\tend: [40, 40],\n}\n\nfunction stateToNodes(\n\tstate: FlatState,\n\tx: number,\n\ty: number,\n\tw: number,\n\th: number,\n\tparentId: string | undefined,\n\tcolors: ParsedNodeColors | undefined\n): MermaidBlueprintNode[] {\n\tconst base = { x, y, w, h, parentId, color: 'black' as const }\n\tconst label = state.label || undefined\n\n\tconst node = (\n\t\tid: string,\n\t\tkind: string,\n\t\tpartial: Partial<Pick<MermaidBlueprintNode, 'x' | 'y' | 'w' | 'h' | 'parentId'>> &\n\t\t\tOmit<MermaidBlueprintNode, 'id' | 'kind' | 'x' | 'y' | 'w' | 'h' | 'parentId'>\n\t): MermaidBlueprintNode => ({\n\t\tid,\n\t\tkind,\n\t\t...base,\n\t\t...partial,\n\t})\n\n\tswitch (state.type) {\n\t\tcase 'note':\n\t\t\treturn [\n\t\t\t\tnode(state.id, 'note', {\n\t\t\t\t\tlabel,\n\t\t\t\t\tfill: 'solid',\n\t\t\t\t\tcolor: 'yellow',\n\t\t\t\t\tsize: 's',\n\t\t\t\t\talign: 'middle',\n\t\t\t\t\tverticalAlign: 'middle',\n\t\t\t\t}),\n\t\t\t]\n\t\tcase 'start':\n\t\t\treturn [node(state.id, 'start', { fill: 'solid' })]\n\t\tcase 'end': {\n\t\t\tconst innerSize = w * 0.6\n\t\t\tconst innerId = `${state.id}__inner`\n\t\t\treturn [\n\t\t\t\tnode(state.id, 'end', { fill: 'none' }),\n\t\t\t\tnode(innerId, 'end_inner', {\n\t\t\t\t\tx: x + (w - innerSize) / 2,\n\t\t\t\t\ty: y + (h - innerSize) / 2,\n\t\t\t\t\tw: innerSize,\n\t\t\t\t\th: innerSize,\n\t\t\t\t\tfill: 'solid',\n\t\t\t\t}),\n\t\t\t]\n\t\t}\n\t\tcase 'fork':\n\t\tcase 'join': {\n\t\t\tconst barW = w * 4\n\t\t\tconst barH = Math.max(16, barW / 10)\n\t\t\treturn [\n\t\t\t\tnode(state.id, state.type, {\n\t\t\t\t\tx: x - (barW - w) / 2,\n\t\t\t\t\ty: y + (h - barH) / 2,\n\t\t\t\t\tw: barW,\n\t\t\t\t\th: barH,\n\t\t\t\t\tfill: 'solid',\n\t\t\t\t}),\n\t\t\t]\n\t\t}\n\t\tcase 'choice':\n\t\t\treturn [\n\t\t\t\tnode(state.id, 'choice', {\n\t\t\t\t\tlabel,\n\t\t\t\t\talign: 'middle',\n\t\t\t\t\tverticalAlign: 'middle',\n\t\t\t\t\tsize: 'm',\n\t\t\t\t}),\n\t\t\t]\n\t\tdefault:\n\t\t\treturn [\n\t\t\t\tnode(state.id, state.type, {\n\t\t\t\t\tlabel,\n\t\t\t\t\t...(colors?.fillColor && { fill: 'solid' as const }),\n\t\t\t\t\t...(colors && { color: colors.strokeColor ?? colors.fillColor }),\n\t\t\t\t\talign: 'middle',\n\t\t\t\t\tverticalAlign: 'middle',\n\t\t\t\t\tsize: 'm',\n\t\t\t\t}),\n\t\t\t]\n\t}\n}\n\nconst FRAME_PAD = 24\nconst FRAME_TOP = 54\n\n/** Parse state-diagram SVG layout data for use by {@link stateToBlueprint}. */\nexport function parseStateDiagramLayout(root: Element): ParsedDiagramLayout {\n\t// Mermaid 11.15 prefixes node and cluster dom ids with the diagram id (e.g.\n\t// `mermaid-0-state-Foo-1` instead of `state-Foo-1`), so tolerate that\n\t// optional `mermaid-<n>-` prefix when reading the clean id.\n\tconst nodes = parseNodesFromSvg(\n\t\troot,\n\t\t'.node',\n\t\t(domId) => domId.match(/^(?:mermaid-\\d+-)?state-(.+)-\\d+$/)?.[1] ?? domId\n\t)\n\tconst clusters = parseClustersFromSvg(\n\t\troot,\n\t\t'.statediagram-cluster',\n\t\t(domId) => domId.match(/^(?:mermaid-\\d+-)?state-(.+)-\\d+$/)?.[1] ?? domId\n\t)\n\tconst edges = parseAllEdgePointsFromSvg(root, (dataId) =>\n\t\t/(?:^|-)edge\\d+$/.test(dataId) ? { start: '', end: '' } : null\n\t)\n\tscaleLayout(nodes, clusters, edges, LAYOUT_SCALE)\n\treturn { nodes, clusters, edges }\n}\n\n/** Convert a parsed Mermaid state diagram into a tldraw blueprint of nodes and edges. */\nexport function stateToBlueprint(\n\tlayout: ParsedDiagramLayout,\n\tstates: Map<string, StateStmt>,\n\trelations: DiagramEdge[],\n\tclassDefs?: Map<string, StyleClass>\n): DiagramMermaidBlueprint {\n\tconst stateColorMap = classDefs ? buildClassDefColorMap(classDefs, states) : new Map()\n\tconst { nodes: svgNodes, clusters: svgClusters, edges: svgEdges } = layout\n\tconst nodeCenters = buildNodeCentersFromSvg(svgNodes, svgClusters)\n\n\tconst { leafStates, compoundLabels, parentOf, allEdges } = flattenStateHierarchy(\n\t\tstates,\n\t\trelations\n\t)\n\n\t// Collect notes attached to states and add them as synthetic leaf nodes + edges.\n\tfor (const [id, state] of states) {\n\t\tconst note = (state as StateStmt & { note?: { position?: string; text: string } }).note\n\t\tif (!note) continue\n\n\t\tconst noteId = `${id}----note`\n\t\tleafStates.set(noteId, { id: noteId, type: 'note', label: note.text.trim() })\n\t\tallEdges.push({ id1: id, id2: noteId, relationTitle: undefined })\n\t}\n\n\tconst nodeLayout = new Map<string, { absX: number; absY: number; w: number; h: number }>()\n\tfor (const [id, state] of leafStates) {\n\t\tconst svgNode = svgNodes.get(id)\n\t\tif (!svgNode) continue\n\n\t\tconst fixed = FIXED_NODE_SIZES[state.type]\n\t\tconst nodeWidth = fixed ? fixed[0] : svgNode.width + 20\n\t\tconst nodeHeight = fixed ? fixed[1] : svgNode.height + 8\n\t\tnodeLayout.set(id, {\n\t\t\tabsX: svgNode.center.x - nodeWidth / 2,\n\t\t\tabsY: svgNode.center.y - nodeHeight / 2,\n\t\t\tw: nodeWidth,\n\t\t\th: nodeHeight,\n\t\t})\n\t}\n\n\tconst compoundIds = [...compoundLabels.keys()]\n\tconst frameBounds = new Map<string, { absX: number; absY: number; w: number; h: number }>()\n\n\t// Use SVG cluster bounds as the authoritative frame size. Mermaid's\n\t// layout already accounts for label width, padding, and special nodes\n\t// like <<history>> that are rendered outside the visual cluster.\n\t// Fall back to child-based computation only when no SVG cluster exists.\n\tconst bottomUp = orderTopDown(\n\t\tcompoundIds,\n\t\t(id) => id,\n\t\t(id) => parentOf.get(id)\n\t).reverse()\n\n\tfor (const compoundId of bottomUp) {\n\t\tconst cluster = svgClusters.get(compoundId)\n\t\tif (cluster) {\n\t\t\tframeBounds.set(compoundId, {\n\t\t\t\tabsX: cluster.topLeft.x,\n\t\t\t\tabsY: cluster.topLeft.y,\n\t\t\t\tw: cluster.width,\n\t\t\t\th: cluster.height,\n\t\t\t})\n\t\t\tcontinue\n\t\t}\n\n\t\tlet frameMinX = Infinity\n\t\tlet frameMinY = Infinity\n\t\tlet frameMaxX = -Infinity\n\t\tlet frameMaxY = -Infinity\n\n\t\tfor (const [id] of leafStates) {\n\t\t\tif (parentOf.get(id) !== compoundId) continue\n\n\t\t\tconst layout = nodeLayout.get(id)\n\t\t\tif (!layout) continue\n\n\t\t\tframeMinX = Math.min(frameMinX, layout.absX)\n\t\t\tframeMinY = Math.min(frameMinY, layout.absY)\n\t\t\tframeMaxX = Math.max(frameMaxX, layout.absX + layout.w)\n\t\t\tframeMaxY = Math.max(frameMaxY, layout.absY + layout.h)\n\t\t}\n\n\t\tfor (const innerId of compoundIds) {\n\t\t\tif (parentOf.get(innerId) !== compoundId) continue\n\n\t\t\tconst innerBounds = frameBounds.get(innerId)\n\t\t\tif (!innerBounds) continue\n\n\t\t\tframeMinX = Math.min(frameMinX, innerBounds.absX)\n\t\t\tframeMinY = Math.min(frameMinY, innerBounds.absY)\n\t\t\tframeMaxX = Math.max(frameMaxX, innerBounds.absX + innerBounds.w)\n\t\t\tframeMaxY = Math.max(frameMaxY, innerBounds.absY + innerBounds.h)\n\t\t}\n\n\t\tif (!isFinite(frameMinX)) continue\n\n\t\tframeBounds.set(compoundId, {\n\t\t\tabsX: frameMinX - FRAME_PAD,\n\t\t\tabsY: frameMinY - FRAME_TOP,\n\t\t\tw: frameMaxX - frameMinX + FRAME_PAD * 2,\n\t\t\th: frameMaxY - frameMinY + FRAME_PAD + FRAME_TOP,\n\t\t})\n\t}\n\n\t// Un-parent any leaf state whose center falls outside its parent frame.\n\t// Mermaid renders some pseudo-states (e.g. <<history>>) outside the\n\t// visual compound cluster even though they're declared inside it.\n\tfor (const [id] of leafStates) {\n\t\tconst pid = parentOf.get(id)\n\t\tif (!pid) continue\n\n\t\tconst frame = frameBounds.get(pid)\n\t\tconst layout = nodeLayout.get(id)\n\t\tif (!frame || !layout) continue\n\n\t\tconst cx = layout.absX + layout.w / 2\n\t\tconst cy = layout.absY + layout.h / 2\n\t\tif (\n\t\t\tcx < frame.absX ||\n\t\t\tcx > frame.absX + frame.w ||\n\t\t\tcy < frame.absY ||\n\t\t\tcy > frame.absY + frame.h\n\t\t) {\n\t\t\tparentOf.delete(id)\n\t\t}\n\t}\n\n\tconst nodes: MermaidBlueprintNode[] = []\n\tconst blueprintEdges: MermaidBlueprintEdge[] = []\n\n\tfor (const compoundId of orderTopDown(\n\t\tcompoundIds,\n\t\t(id) => id,\n\t\t(id) => parentOf.get(id)\n\t)) {\n\t\tconst bounds = frameBounds.get(compoundId)\n\t\tif (!bounds) continue\n\n\t\tconst kind = 'compound'\n\t\tnodes.push({\n\t\t\tid: compoundId,\n\t\t\tkind,\n\t\t\tx: bounds.absX,\n\t\t\ty: bounds.absY,\n\t\t\tw: bounds.w,\n\t\t\th: bounds.h,\n\t\t\tparentId: parentOf.get(compoundId),\n\t\t\tlabel: compoundLabels.get(compoundId) || compoundId,\n\t\t\tfill: 'semi',\n\t\t\tcolor: 'black',\n\t\t\tdash: 'draw',\n\t\t\tsize: 's',\n\t\t\talign: 'middle',\n\t\t\tverticalAlign: 'start',\n\t\t})\n\t}\n\n\tfor (const [id, state] of leafStates) {\n\t\tconst layout = nodeLayout.get(id)\n\t\tif (!layout) continue\n\n\t\tnodes.push(\n\t\t\t...stateToNodes(\n\t\t\t\tstate,\n\t\t\t\tlayout.absX,\n\t\t\t\tlayout.absY,\n\t\t\t\tlayout.w,\n\t\t\t\tlayout.h,\n\t\t\t\tparentOf.get(id),\n\t\t\t\tstateColorMap.get(id)\n\t\t\t)\n\t\t)\n\t}\n\n\tconst claimed = new Set<number>()\n\n\tfor (const edge of allEdges) {\n\t\tconst startCenter = nodeCenters.get(edge.id1)\n\t\tconst endCenter = nodeCenters.get(edge.id2)\n\n\t\tlet bend = 0\n\t\tif (startCenter && endCenter) {\n\t\t\tlet bestIndex = -1\n\t\t\tlet bestDistance = Infinity\n\t\t\tfor (let edgeIndex = 0; edgeIndex < svgEdges.length; edgeIndex++) {\n\t\t\t\tif (claimed.has(edgeIndex) || svgEdges[edgeIndex].points.length < 2) continue\n\n\t\t\t\tconst points = svgEdges[edgeIndex].points\n\t\t\t\tconst distance =\n\t\t\t\t\tMath.hypot(points[0].x - startCenter.x, points[0].y - startCenter.y) +\n\t\t\t\t\tMath.hypot(\n\t\t\t\t\t\tpoints[points.length - 1].x - endCenter.x,\n\t\t\t\t\t\tpoints[points.length - 1].y - endCenter.y\n\t\t\t\t\t)\n\t\t\t\tif (distance < bestDistance) {\n\t\t\t\t\tbestDistance = distance\n\t\t\t\t\tbestIndex = edgeIndex\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (bestIndex >= 0) {\n\t\t\t\tclaimed.add(bestIndex)\n\t\t\t\tbend = getArrowBend(svgEdges[bestIndex])\n\t\t\t}\n\t\t}\n\n\t\tconst isNoteEdge = edge.id2.endsWith('----note') || edge.id1.endsWith('----note')\n\t\tblueprintEdges.push({\n\t\t\tstartNodeId: edge.id1,\n\t\t\tendNodeId: edge.id2,\n\t\t\tlabel: edge.relationTitle,\n\t\t\tbend,\n\t\t\t...(isNoteEdge && { dash: 'dotted' as const, arrowheadEnd: 'none' as const }),\n\t\t})\n\t}\n\n\tconst nodeIds = new Set(nodes.map((n) => n.id))\n\tconst validEdges = blueprintEdges.filter(\n\t\t(e) => nodeIds.has(e.startNodeId) && nodeIds.has(e.endNodeId)\n\t)\n\treturn { diagramKind: 'state', nodes, edges: validEdges }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["state", "layout"]
|
|
7
7
|
}
|
package/dist-cjs/svgParsing.js
CHANGED
|
@@ -109,10 +109,10 @@ function parseNodesFromSvg(root, selector, idParser) {
|
|
|
109
109
|
}
|
|
110
110
|
return out;
|
|
111
111
|
}
|
|
112
|
-
function parseClustersFromSvg(root, selector) {
|
|
112
|
+
function parseClustersFromSvg(root, selector, idParser = (id) => id) {
|
|
113
113
|
const out = /* @__PURE__ */ new Map();
|
|
114
114
|
for (const groupEl of root.querySelectorAll(selector)) {
|
|
115
|
-
const id = groupEl.getAttribute("id") || "";
|
|
115
|
+
const id = idParser(groupEl.getAttribute("id") || "");
|
|
116
116
|
const rect = groupEl.querySelector("rect");
|
|
117
117
|
if (!rect) continue;
|
|
118
118
|
const rx = parseFloat(rect.getAttribute("x") || "0");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/svgParsing.ts"],
|
|
4
|
-
"sourcesContent": ["export interface Vec2 {\n\tx: number\n\ty: number\n}\n\nexport interface ParsedNode {\n\tid: string\n\tcenter: Vec2\n\twidth: number\n\theight: number\n}\n\nexport interface ParsedCluster {\n\tid: string\n\ttopLeft: Vec2\n\twidth: number\n\theight: number\n}\n\ntype NodeIdParser = (domId: string) => string\ntype EdgeIdParser = (dataId: string) => { start: string; end: string } | null\n\nfunction parseTranslate(attr: string | null): Vec2 {\n\tif (!attr) return { x: 0, y: 0 }\n\t// Matches SVG translate transforms, e.g. transform=\"translate(123.45, 67.8)\".\n\t// Handles scientific notation (1.2e+3). Group 1 = x offset, group 2 = y offset.\n\tconst translateMatch = attr.match(/translate\\(\\s*([\\d.e+-]+)[,\\s]+([\\d.e+-]+)\\s*\\)/)\n\tif (!translateMatch) return { x: 0, y: 0 }\n\treturn { x: parseFloat(translateMatch[1]), y: parseFloat(translateMatch[2]) }\n}\n\nexport function getAccumulatedTranslate(el: Element): Vec2 {\n\tlet x = 0\n\tlet y = 0\n\tlet cur: Element | null = el.parentElement\n\twhile (cur) {\n\t\tconst parentTranslate = parseTranslate(cur.getAttribute('transform'))\n\t\tx += parentTranslate.x\n\t\ty += parentTranslate.y\n\t\tcur = cur.parentElement\n\t}\n\treturn { x, y }\n}\n\n/**\n * Extract element dimensions from a live SVG element using getBBox(),\n * falling back to attribute parsing for non-browser environments (jsdom).\n */\nfunction getNodeDimensions(groupEl: Element): { w: number; h: number } {\n\tconst shapeEl = groupEl.querySelector('.label-container')\n\tif (shapeEl) {\n\t\ttry {\n\t\t\tconst bbox = (shapeEl as SVGGraphicsElement).getBBox()\n\t\t\tif (bbox.width > 0 && bbox.height > 0) return { w: bbox.width, h: bbox.height }\n\t\t} catch {\n\t\t\t/* fall through */\n\t\t}\n\t}\n\n\ttry {\n\t\tconst bbox = (groupEl as SVGGraphicsElement).getBBox()\n\t\tif (bbox.width > 0 && bbox.height > 0) return { w: bbox.width, h: bbox.height }\n\t} catch {\n\t\t/* fall through */\n\t}\n\n\tconst rect = groupEl.querySelector('rect')\n\tif (rect) {\n\t\tconst w = parseFloat(rect.getAttribute('width') || '0')\n\t\tconst h = parseFloat(rect.getAttribute('height') || '0')\n\t\tif (w > 0 && h > 0) return { w, h }\n\t}\n\tconst poly = groupEl.querySelector('polygon')\n\tif (poly) {\n\t\tconst pts = (poly.getAttribute('points') || '')\n\t\t\t.trim()\n\t\t\t.split(/\\s+/)\n\t\t\t.map((pointStr) => pointStr.split(',').map(Number))\n\t\tlet minX = Infinity\n\t\tlet maxX = -Infinity\n\t\tlet minY = Infinity\n\t\tlet maxY = -Infinity\n\t\tfor (const [px, py] of pts) {\n\t\t\tminX = Math.min(minX, px)\n\t\t\tmaxX = Math.max(maxX, px)\n\t\t\tminY = Math.min(minY, py)\n\t\t\tmaxY = Math.max(maxY, py)\n\t\t}\n\t\tif (maxX > minX && maxY > minY) return { w: maxX - minX, h: maxY - minY }\n\t}\n\tconst circle = groupEl.querySelector('circle')\n\tif (circle) {\n\t\tconst r = parseFloat(circle.getAttribute('r') || '0')\n\t\tif (r > 0) return { w: r * 2, h: r * 2 }\n\t}\n\tconst ellipse = groupEl.querySelector('ellipse')\n\tif (ellipse) {\n\t\tconst w = parseFloat(ellipse.getAttribute('rx') || '0') * 2\n\t\tconst h = parseFloat(ellipse.getAttribute('ry') || '0') * 2\n\t\tif (w > 0 && h > 0) return { w, h }\n\t}\n\treturn { w: 0, h: 0 }\n}\n\nexport function parseNodesFromSvg(\n\troot: Element,\n\tselector: string,\n\tidParser: NodeIdParser\n): Map<string, ParsedNode> {\n\tconst out = new Map<string, ParsedNode>()\n\tfor (const groupEl of root.querySelectorAll(selector)) {\n\t\tconst rawId = groupEl.getAttribute('id') || ''\n\t\tconst id = idParser(rawId)\n\t\tconst self = parseTranslate(groupEl.getAttribute('transform'))\n\t\tconst ancestor = getAccumulatedTranslate(groupEl)\n\t\tconst { w, h } = getNodeDimensions(groupEl)\n\t\tout.set(id, {\n\t\t\tid,\n\t\t\tcenter: { x: ancestor.x + self.x, y: ancestor.y + self.y },\n\t\t\twidth: w,\n\t\t\theight: h,\n\t\t})\n\t}\n\treturn out\n}\n\nexport function parseClustersFromSvg(
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsBA,SAAS,eAAe,MAA2B;AAClD,MAAI,CAAC,KAAM,QAAO,EAAE,GAAG,GAAG,GAAG,EAAE;AAG/B,QAAM,iBAAiB,KAAK,MAAM,iDAAiD;AACnF,MAAI,CAAC,eAAgB,QAAO,EAAE,GAAG,GAAG,GAAG,EAAE;AACzC,SAAO,EAAE,GAAG,WAAW,eAAe,CAAC,CAAC,GAAG,GAAG,WAAW,eAAe,CAAC,CAAC,EAAE;AAC7E;AAEO,SAAS,wBAAwB,IAAmB;AAC1D,MAAI,IAAI;AACR,MAAI,IAAI;AACR,MAAI,MAAsB,GAAG;AAC7B,SAAO,KAAK;AACX,UAAM,kBAAkB,eAAe,IAAI,aAAa,WAAW,CAAC;AACpE,SAAK,gBAAgB;AACrB,SAAK,gBAAgB;AACrB,UAAM,IAAI;AAAA,EACX;AACA,SAAO,EAAE,GAAG,EAAE;AACf;AAMA,SAAS,kBAAkB,SAA4C;AACtE,QAAM,UAAU,QAAQ,cAAc,kBAAkB;AACxD,MAAI,SAAS;AACZ,QAAI;AACH,YAAM,OAAQ,QAA+B,QAAQ;AACrD,UAAI,KAAK,QAAQ,KAAK,KAAK,SAAS,EAAG,QAAO,EAAE,GAAG,KAAK,OAAO,GAAG,KAAK,OAAO;AAAA,IAC/E,QAAQ;AAAA,IAER;AAAA,EACD;AAEA,MAAI;AACH,UAAM,OAAQ,QAA+B,QAAQ;AACrD,QAAI,KAAK,QAAQ,KAAK,KAAK,SAAS,EAAG,QAAO,EAAE,GAAG,KAAK,OAAO,GAAG,KAAK,OAAO;AAAA,EAC/E,QAAQ;AAAA,EAER;AAEA,QAAM,OAAO,QAAQ,cAAc,MAAM;AACzC,MAAI,MAAM;AACT,UAAM,IAAI,WAAW,KAAK,aAAa,OAAO,KAAK,GAAG;AACtD,UAAM,IAAI,WAAW,KAAK,aAAa,QAAQ,KAAK,GAAG;AACvD,QAAI,IAAI,KAAK,IAAI,EAAG,QAAO,EAAE,GAAG,EAAE;AAAA,EACnC;AACA,QAAM,OAAO,QAAQ,cAAc,SAAS;AAC5C,MAAI,MAAM;AACT,UAAM,OAAO,KAAK,aAAa,QAAQ,KAAK,IAC1C,KAAK,EACL,MAAM,KAAK,EACX,IAAI,CAAC,aAAa,SAAS,MAAM,GAAG,EAAE,IAAI,MAAM,CAAC;AACnD,QAAI,OAAO;AACX,QAAI,OAAO;AACX,QAAI,OAAO;AACX,QAAI,OAAO;AACX,eAAW,CAAC,IAAI,EAAE,KAAK,KAAK;AAC3B,aAAO,KAAK,IAAI,MAAM,EAAE;AACxB,aAAO,KAAK,IAAI,MAAM,EAAE;AACxB,aAAO,KAAK,IAAI,MAAM,EAAE;AACxB,aAAO,KAAK,IAAI,MAAM,EAAE;AAAA,IACzB;AACA,QAAI,OAAO,QAAQ,OAAO,KAAM,QAAO,EAAE,GAAG,OAAO,MAAM,GAAG,OAAO,KAAK;AAAA,EACzE;AACA,QAAM,SAAS,QAAQ,cAAc,QAAQ;AAC7C,MAAI,QAAQ;AACX,UAAM,IAAI,WAAW,OAAO,aAAa,GAAG,KAAK,GAAG;AACpD,QAAI,IAAI,EAAG,QAAO,EAAE,GAAG,IAAI,GAAG,GAAG,IAAI,EAAE;AAAA,EACxC;AACA,QAAM,UAAU,QAAQ,cAAc,SAAS;AAC/C,MAAI,SAAS;AACZ,UAAM,IAAI,WAAW,QAAQ,aAAa,IAAI,KAAK,GAAG,IAAI;AAC1D,UAAM,IAAI,WAAW,QAAQ,aAAa,IAAI,KAAK,GAAG,IAAI;AAC1D,QAAI,IAAI,KAAK,IAAI,EAAG,QAAO,EAAE,GAAG,EAAE;AAAA,EACnC;AACA,SAAO,EAAE,GAAG,GAAG,GAAG,EAAE;AACrB;AAEO,SAAS,kBACf,MACA,UACA,UAC0B;AAC1B,QAAM,MAAM,oBAAI,IAAwB;AACxC,aAAW,WAAW,KAAK,iBAAiB,QAAQ,GAAG;AACtD,UAAM,QAAQ,QAAQ,aAAa,IAAI,KAAK;AAC5C,UAAM,KAAK,SAAS,KAAK;AACzB,UAAM,OAAO,eAAe,QAAQ,aAAa,WAAW,CAAC;AAC7D,UAAM,WAAW,wBAAwB,OAAO;AAChD,UAAM,EAAE,GAAG,EAAE,IAAI,kBAAkB,OAAO;AAC1C,QAAI,IAAI,IAAI;AAAA,MACX;AAAA,MACA,QAAQ,EAAE,GAAG,SAAS,IAAI,KAAK,GAAG,GAAG,SAAS,IAAI,KAAK,EAAE;AAAA,MACzD,OAAO;AAAA,MACP,QAAQ;AAAA,IACT,CAAC;AAAA,EACF;AACA,SAAO;AACR;AAEO,SAAS,
|
|
4
|
+
"sourcesContent": ["export interface Vec2 {\n\tx: number\n\ty: number\n}\n\nexport interface ParsedNode {\n\tid: string\n\tcenter: Vec2\n\twidth: number\n\theight: number\n}\n\nexport interface ParsedCluster {\n\tid: string\n\ttopLeft: Vec2\n\twidth: number\n\theight: number\n}\n\ntype NodeIdParser = (domId: string) => string\ntype EdgeIdParser = (dataId: string) => { start: string; end: string } | null\n\nfunction parseTranslate(attr: string | null): Vec2 {\n\tif (!attr) return { x: 0, y: 0 }\n\t// Matches SVG translate transforms, e.g. transform=\"translate(123.45, 67.8)\".\n\t// Handles scientific notation (1.2e+3). Group 1 = x offset, group 2 = y offset.\n\tconst translateMatch = attr.match(/translate\\(\\s*([\\d.e+-]+)[,\\s]+([\\d.e+-]+)\\s*\\)/)\n\tif (!translateMatch) return { x: 0, y: 0 }\n\treturn { x: parseFloat(translateMatch[1]), y: parseFloat(translateMatch[2]) }\n}\n\nexport function getAccumulatedTranslate(el: Element): Vec2 {\n\tlet x = 0\n\tlet y = 0\n\tlet cur: Element | null = el.parentElement\n\twhile (cur) {\n\t\tconst parentTranslate = parseTranslate(cur.getAttribute('transform'))\n\t\tx += parentTranslate.x\n\t\ty += parentTranslate.y\n\t\tcur = cur.parentElement\n\t}\n\treturn { x, y }\n}\n\n/**\n * Extract element dimensions from a live SVG element using getBBox(),\n * falling back to attribute parsing for non-browser environments (jsdom).\n */\nfunction getNodeDimensions(groupEl: Element): { w: number; h: number } {\n\tconst shapeEl = groupEl.querySelector('.label-container')\n\tif (shapeEl) {\n\t\ttry {\n\t\t\tconst bbox = (shapeEl as SVGGraphicsElement).getBBox()\n\t\t\tif (bbox.width > 0 && bbox.height > 0) return { w: bbox.width, h: bbox.height }\n\t\t} catch {\n\t\t\t/* fall through */\n\t\t}\n\t}\n\n\ttry {\n\t\tconst bbox = (groupEl as SVGGraphicsElement).getBBox()\n\t\tif (bbox.width > 0 && bbox.height > 0) return { w: bbox.width, h: bbox.height }\n\t} catch {\n\t\t/* fall through */\n\t}\n\n\tconst rect = groupEl.querySelector('rect')\n\tif (rect) {\n\t\tconst w = parseFloat(rect.getAttribute('width') || '0')\n\t\tconst h = parseFloat(rect.getAttribute('height') || '0')\n\t\tif (w > 0 && h > 0) return { w, h }\n\t}\n\tconst poly = groupEl.querySelector('polygon')\n\tif (poly) {\n\t\tconst pts = (poly.getAttribute('points') || '')\n\t\t\t.trim()\n\t\t\t.split(/\\s+/)\n\t\t\t.map((pointStr) => pointStr.split(',').map(Number))\n\t\tlet minX = Infinity\n\t\tlet maxX = -Infinity\n\t\tlet minY = Infinity\n\t\tlet maxY = -Infinity\n\t\tfor (const [px, py] of pts) {\n\t\t\tminX = Math.min(minX, px)\n\t\t\tmaxX = Math.max(maxX, px)\n\t\t\tminY = Math.min(minY, py)\n\t\t\tmaxY = Math.max(maxY, py)\n\t\t}\n\t\tif (maxX > minX && maxY > minY) return { w: maxX - minX, h: maxY - minY }\n\t}\n\tconst circle = groupEl.querySelector('circle')\n\tif (circle) {\n\t\tconst r = parseFloat(circle.getAttribute('r') || '0')\n\t\tif (r > 0) return { w: r * 2, h: r * 2 }\n\t}\n\tconst ellipse = groupEl.querySelector('ellipse')\n\tif (ellipse) {\n\t\tconst w = parseFloat(ellipse.getAttribute('rx') || '0') * 2\n\t\tconst h = parseFloat(ellipse.getAttribute('ry') || '0') * 2\n\t\tif (w > 0 && h > 0) return { w, h }\n\t}\n\treturn { w: 0, h: 0 }\n}\n\nexport function parseNodesFromSvg(\n\troot: Element,\n\tselector: string,\n\tidParser: NodeIdParser\n): Map<string, ParsedNode> {\n\tconst out = new Map<string, ParsedNode>()\n\tfor (const groupEl of root.querySelectorAll(selector)) {\n\t\tconst rawId = groupEl.getAttribute('id') || ''\n\t\tconst id = idParser(rawId)\n\t\tconst self = parseTranslate(groupEl.getAttribute('transform'))\n\t\tconst ancestor = getAccumulatedTranslate(groupEl)\n\t\tconst { w, h } = getNodeDimensions(groupEl)\n\t\tout.set(id, {\n\t\t\tid,\n\t\t\tcenter: { x: ancestor.x + self.x, y: ancestor.y + self.y },\n\t\t\twidth: w,\n\t\t\theight: h,\n\t\t})\n\t}\n\treturn out\n}\n\nexport function parseClustersFromSvg(\n\troot: Element,\n\tselector: string,\n\tidParser: NodeIdParser = (id) => id\n): Map<string, ParsedCluster> {\n\tconst out = new Map<string, ParsedCluster>()\n\tfor (const groupEl of root.querySelectorAll(selector)) {\n\t\tconst id = idParser(groupEl.getAttribute('id') || '')\n\t\tconst rect = groupEl.querySelector('rect')\n\t\tif (!rect) continue\n\t\tconst rx = parseFloat(rect.getAttribute('x') || '0')\n\t\tconst ry = parseFloat(rect.getAttribute('y') || '0')\n\t\tconst w = parseFloat(rect.getAttribute('width') || '0')\n\t\tconst h = parseFloat(rect.getAttribute('height') || '0')\n\t\tconst self = parseTranslate(groupEl.getAttribute('transform'))\n\t\tconst ancestor = getAccumulatedTranslate(groupEl)\n\t\tout.set(id, {\n\t\t\tid,\n\t\t\ttopLeft: { x: ancestor.x + self.x + rx, y: ancestor.y + self.y + ry },\n\t\t\twidth: w,\n\t\t\theight: h,\n\t\t})\n\t}\n\treturn out\n}\n\nexport interface ParsedEdge {\n\tstart: string\n\tend: string\n\tpoints: Vec2[]\n}\n\n/**\n * Pre-parsed SVG layout for flowchart and state diagram converters.\n * Contains already-scaled node, cluster, and edge data.\n */\nexport interface ParsedDiagramLayout {\n\tnodes: Map<string, ParsedNode>\n\tclusters: Map<string, ParsedCluster>\n\tedges: ParsedEdge[]\n}\n\n/**\n * Parse every SVG edge path in DOM order (matching mermaid's edge list order).\n * Unlike the old per-pair map, this preserves all parallel edges individually.\n */\nexport function parseAllEdgePointsFromSvg(root: Element, parser: EdgeIdParser): ParsedEdge[] {\n\tconst out: ParsedEdge[] = []\n\tfor (const path of root.querySelectorAll('path[data-points]')) {\n\t\tconst dataId = path.getAttribute('data-id') || path.getAttribute('id') || ''\n\t\tconst dataPoints = path.getAttribute('data-points')\n\t\tif (!dataPoints) continue\n\t\tconst parsed = parser(dataId)\n\t\tif (!parsed) continue\n\t\ttry {\n\t\t\tconst points = JSON.parse(atob(dataPoints))\n\t\t\tconst ancestor = getAccumulatedTranslate(path as Element)\n\t\t\tfor (const point of points) {\n\t\t\t\tpoint.x += ancestor.x\n\t\t\t\tpoint.y += ancestor.y\n\t\t\t}\n\t\t\tout.push({ start: parsed.start, end: parsed.end, points })\n\t\t} catch {\n\t\t\t/* ignore malformed data */\n\t\t}\n\t}\n\treturn out\n}\n\n/**\n * Build a map of node/cluster id \u2192 center (for flowchart and state diagram edge matching).\n */\nexport function buildNodeCentersFromSvg(\n\tnodes: Map<string, ParsedNode>,\n\tclusters: Map<string, ParsedCluster>\n): Map<string, Vec2> {\n\tconst out = new Map<string, Vec2>()\n\tfor (const [id, node] of nodes) {\n\t\tout.set(id, { x: node.center.x, y: node.center.y })\n\t}\n\tfor (const [id, cluster] of clusters) {\n\t\tout.set(id, {\n\t\t\tx: cluster.topLeft.x + cluster.width / 2,\n\t\t\ty: cluster.topLeft.y + cluster.height / 2,\n\t\t})\n\t}\n\treturn out\n}\n\n// ---------------------------------------------------------------------------\n// Layout scaling and bounds\n// ---------------------------------------------------------------------------\n\nexport function scaleLayout(\n\tnodes: Map<string, ParsedNode>,\n\tclusters: Map<string, ParsedCluster>,\n\tedges: ParsedEdge[],\n\tscale: number\n): void {\n\tfor (const [, node] of nodes) {\n\t\tnode.center.x *= scale\n\t\tnode.center.y *= scale\n\t\tnode.width *= scale\n\t\tnode.height *= scale\n\t}\n\tfor (const [, cluster] of clusters) {\n\t\tcluster.topLeft.x *= scale\n\t\tcluster.topLeft.y *= scale\n\t\tcluster.width *= scale\n\t\tcluster.height *= scale\n\t}\n\tfor (const edge of edges) {\n\t\tfor (const point of edge.points) {\n\t\t\tpoint.x *= scale\n\t\t\tpoint.y *= scale\n\t\t}\n\t}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
let nextMermaidId = 0;
|
|
2
|
-
import mermaid from "mermaid";
|
|
3
2
|
import { flowchartToBlueprint, parseFlowchartLayout } from "./flowchartDiagram.mjs";
|
|
4
3
|
import { mindmapToBlueprint, parseMindmapLayout } from "./mindmapDiagram.mjs";
|
|
5
4
|
import { renderBlueprint } from "./renderBlueprint.mjs";
|
|
@@ -25,6 +24,7 @@ const MERMAID_CONFIG = {
|
|
|
25
24
|
themeVariables: { fontSize: `${18 * FONT_INFLATE}px` }
|
|
26
25
|
};
|
|
27
26
|
async function createMermaidDiagram(editor, text, options = {}) {
|
|
27
|
+
const mermaid = (await import("mermaid")).default;
|
|
28
28
|
mermaid.initialize({
|
|
29
29
|
...MERMAID_CONFIG,
|
|
30
30
|
...(options.mermaidConfig ?? {}),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/createMermaidDiagram.ts"],
|
|
4
|
-
"sourcesContent": ["let nextMermaidId = 0\n\nimport
|
|
5
|
-
"mappings": "AAAA,IAAI,gBAAgB;
|
|
4
|
+
"sourcesContent": ["let nextMermaidId = 0\n\nimport type { FlowDB } from 'mermaid/dist/diagrams/flowchart/flowDb.d.ts'\nimport type { FlowEdge, FlowSubGraph, FlowVertex } from 'mermaid/dist/diagrams/flowchart/types.js'\nimport type { MindmapDB } from 'mermaid/dist/diagrams/mindmap/mindmapDb.d.ts'\nimport type { SequenceDB } from 'mermaid/dist/diagrams/sequence/sequenceDb.d.ts'\nimport type { StateDB } from 'mermaid/dist/diagrams/state/stateDb.d.ts'\nimport { Editor } from 'tldraw'\nimport { flowchartToBlueprint, parseFlowchartLayout } from './flowchartDiagram'\nimport { mindmapToBlueprint, parseMindmapLayout } from './mindmapDiagram'\nimport { BlueprintRenderingOptions, renderBlueprint } from './renderBlueprint'\nimport { countSequenceEvents, parseSequenceLayout, sequenceToBlueprint } from './sequenceDiagram'\nimport { parseStateDiagramLayout, stateToBlueprint } from './stateDiagram'\n\n/** @public */\nexport class MermaidDiagramError extends Error {\n\tconstructor(\n\t\tpublic diagramType: string,\n\t\tpublic type: 'parse' | 'unsupported'\n\t) {\n\t\tsuper(`mermaid diagram error: ${diagramType}`)\n\t\tthis.name = 'MermaidDiagramError'\n\t}\n}\n\n// Inflate the font size so Mermaid's layout engine allocates larger nodes,\n// compensating for tldraw's hand-drawn font being wider than Mermaid's default.\nconst FONT_INFLATE = 1.4\n\nconst MERMAID_CONFIG = {\n\tstartOnLoad: false,\n\tflowchart: { nodeSpacing: 80, rankSpacing: 80, padding: 20 },\n\tstate: { nodeSpacing: 80, rankSpacing: 80, padding: 20 },\n\tmindmap: { padding: 20 },\n\tsequence: { actorMargin: 50, noteMargin: 20 },\n\tthemeVariables: { fontSize: `${18 * FONT_INFLATE}px` },\n}\n\n/** @public */\nexport interface MermaidDiagramOptions {\n\tmermaidConfig?: Record<string, any>\n\tblueprintRender?: BlueprintRenderingOptions\n\tonUnsupportedDiagram?(svg: string): Promise<void>\n}\n\n/**\n * Parse mermaid text and create tldraw shapes for supported diagram types.\n * Returns the SVG string for supported diagrams, or `null` when the diagram type\n * is unsupported (after calling `onUnsupportedDiagram` if provided).\n * Throws {@link MermaidDiagramError} if parsing fails.\n * @public\n */\nexport async function createMermaidDiagram(\n\teditor: Editor,\n\ttext: string,\n\toptions: MermaidDiagramOptions = {}\n): Promise<void> {\n\t// load mermaid lazily: it's a large, ESM-only dependency only needed when a\n\t// diagram is actually created. a dynamic import() works from CommonJS (unlike\n\t// a static import, which compiles to require(<esm>) and throws\n\t// ERR_REQUIRE_ESM on Node <20.19, Jest, and ts-node) and avoids pulling\n\t// mermaid in when @tldraw/mermaid is merely imported.\n\tconst mermaid = (await import('mermaid')).default\n\n\tmermaid.initialize({\n\t\t...MERMAID_CONFIG,\n\t\t...(options.mermaidConfig ?? {}),\n\t\tflowchart: { ...MERMAID_CONFIG.flowchart, ...options.mermaidConfig?.flowchart },\n\t\tstate: { ...MERMAID_CONFIG.state, ...options.mermaidConfig?.state },\n\t\tmindmap: { ...MERMAID_CONFIG.mindmap, ...options.mermaidConfig?.mindmap },\n\t\tsequence: { ...MERMAID_CONFIG.sequence, ...options.mermaidConfig?.sequence },\n\t\tthemeVariables: { ...MERMAID_CONFIG.themeVariables, ...options.mermaidConfig?.themeVariables },\n\t})\n\n\tconst parsedResult = await mermaid.parse(text, { suppressErrors: true })\n\n\tif (!parsedResult) {\n\t\tthrow new MermaidDiagramError('not a mermaid diagram', 'parse')\n\t}\n\n\tconst offscreen = document.createElement('div')\n\toffscreen.style.position = 'absolute'\n\toffscreen.style.left = '-9999px'\n\toffscreen.style.top = '-9999px'\n\toffscreen.style.overflow = 'hidden'\n\tdocument.body.appendChild(offscreen)\n\n\ttry {\n\t\tconst parsedSvg = (await mermaid.render(`mermaid-${nextMermaidId++}`, text, offscreen)).svg\n\n\t\t// Reuse the live SVG that mermaid.render() already mounted into the\n\t\t// offscreen container. This avoids a second DOM mount and ensures\n\t\t// getBBox() works for every diagram type (state diagrams in particular\n\t\t// lack explicit dimension attributes and rely on live layout).\n\t\tlet liveSvg = offscreen.querySelector('svg')\n\n\t\tif (!liveSvg) {\n\t\t\toffscreen.innerHTML = parsedSvg\n\t\t\tliveSvg = offscreen.querySelector('svg')\n\t\t\tif (!liveSvg) {\n\t\t\t\tthrow new MermaidDiagramError(parsedResult.diagramType, 'parse')\n\t\t\t}\n\t\t}\n\n\t\t// eslint-disable-next-line @typescript-eslint/no-deprecated\n\t\tconst diagramResult = await mermaid.mermaidAPI.getDiagramFromText(text)\n\n\t\tlet blueprint\n\t\tswitch (parsedResult.diagramType) {\n\t\t\tcase 'flowchart-v2': {\n\t\t\t\tconst db = diagramResult.db as FlowDB\n\t\t\t\tconst vertices = db.getVertices() as Map<string, FlowVertex>\n\t\t\t\tconst edges = db.getEdges() as FlowEdge[]\n\t\t\t\tconst subGraphs = db.getSubGraphs() as FlowSubGraph[]\n\t\t\t\tconst classes = db.getClasses()\n\t\t\t\tconst layout = parseFlowchartLayout(liveSvg)\n\t\t\t\tblueprint = flowchartToBlueprint(layout, vertices, edges, subGraphs, classes)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'sequence': {\n\t\t\t\tconst db = diagramResult.db as SequenceDB\n\t\t\t\tconst actors = db.getActors()\n\t\t\t\tconst actorKeys = db.getActorKeys()\n\t\t\t\tconst messages = db.getMessages()\n\t\t\t\tconst layout = parseSequenceLayout(liveSvg, actorKeys.length, countSequenceEvents(messages))\n\t\t\t\tblueprint = sequenceToBlueprint(\n\t\t\t\t\tlayout,\n\t\t\t\t\tactors,\n\t\t\t\t\tactorKeys,\n\t\t\t\t\tmessages,\n\t\t\t\t\tdb.getCreatedActors(),\n\t\t\t\t\tdb.getDestroyedActors()\n\t\t\t\t)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'state':\n\t\t\tcase 'stateDiagram': {\n\t\t\t\tconst db = diagramResult.db as StateDB\n\t\t\t\tconst states = db.getStates()\n\t\t\t\tconst relations = db.getRelations()\n\t\t\t\tconst classes = db.getClasses()\n\t\t\t\tconst layout = parseStateDiagramLayout(liveSvg)\n\t\t\t\tblueprint = stateToBlueprint(layout, states, relations, classes)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'mindmap': {\n\t\t\t\tconst db = diagramResult.db as MindmapDB\n\t\t\t\tconst tree = db.getMindmap()\n\t\t\t\tif (tree) {\n\t\t\t\t\tdb.assignSections(tree)\n\t\t\t\t\tconst layout = parseMindmapLayout(liveSvg)\n\t\t\t\t\tblueprint = mindmapToBlueprint(layout, tree, liveSvg)\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tif (options.onUnsupportedDiagram) {\n\t\t\t\t\tawait options.onUnsupportedDiagram(parsedSvg)\n\t\t\t\t} else {\n\t\t\t\t\tthrow new MermaidDiagramError(parsedResult.diagramType, 'unsupported')\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t}\n\n\t\tif (blueprint) {\n\t\t\trenderBlueprint(editor, blueprint, options.blueprintRender)\n\t\t}\n\t} catch (e) {\n\t\tif (e instanceof MermaidDiagramError) throw e\n\t\tconsole.error(e)\n\t\tthrow new MermaidDiagramError(parsedResult.diagramType, 'parse')\n\t} finally {\n\t\toffscreen.remove()\n\t}\n}\n"],
|
|
5
|
+
"mappings": "AAAA,IAAI,gBAAgB;AAQpB,SAAS,sBAAsB,4BAA4B;AAC3D,SAAS,oBAAoB,0BAA0B;AACvD,SAAoC,uBAAuB;AAC3D,SAAS,qBAAqB,qBAAqB,2BAA2B;AAC9E,SAAS,yBAAyB,wBAAwB;AAGnD,MAAM,4BAA4B,MAAM;AAAA,EAC9C,YACQ,aACA,MACN;AACD,UAAM,0BAA0B,WAAW,EAAE;AAHtC;AACA;AAGP,SAAK,OAAO;AAAA,EACb;AAAA,EALQ;AAAA,EACA;AAKT;AAIA,MAAM,eAAe;AAErB,MAAM,iBAAiB;AAAA,EACtB,aAAa;AAAA,EACb,WAAW,EAAE,aAAa,IAAI,aAAa,IAAI,SAAS,GAAG;AAAA,EAC3D,OAAO,EAAE,aAAa,IAAI,aAAa,IAAI,SAAS,GAAG;AAAA,EACvD,SAAS,EAAE,SAAS,GAAG;AAAA,EACvB,UAAU,EAAE,aAAa,IAAI,YAAY,GAAG;AAAA,EAC5C,gBAAgB,EAAE,UAAU,GAAG,KAAK,YAAY,KAAK;AACtD;AAgBA,eAAsB,qBACrB,QACA,MACA,UAAiC,CAAC,GAClB;AAMhB,QAAM,WAAW,MAAM,OAAO,SAAS,GAAG;AAE1C,UAAQ,WAAW;AAAA,IAClB,GAAG;AAAA,IACH,GAAI,QAAQ,iBAAiB,CAAC;AAAA,IAC9B,WAAW,EAAE,GAAG,eAAe,WAAW,GAAG,QAAQ,eAAe,UAAU;AAAA,IAC9E,OAAO,EAAE,GAAG,eAAe,OAAO,GAAG,QAAQ,eAAe,MAAM;AAAA,IAClE,SAAS,EAAE,GAAG,eAAe,SAAS,GAAG,QAAQ,eAAe,QAAQ;AAAA,IACxE,UAAU,EAAE,GAAG,eAAe,UAAU,GAAG,QAAQ,eAAe,SAAS;AAAA,IAC3E,gBAAgB,EAAE,GAAG,eAAe,gBAAgB,GAAG,QAAQ,eAAe,eAAe;AAAA,EAC9F,CAAC;AAED,QAAM,eAAe,MAAM,QAAQ,MAAM,MAAM,EAAE,gBAAgB,KAAK,CAAC;AAEvE,MAAI,CAAC,cAAc;AAClB,UAAM,IAAI,oBAAoB,yBAAyB,OAAO;AAAA,EAC/D;AAEA,QAAM,YAAY,SAAS,cAAc,KAAK;AAC9C,YAAU,MAAM,WAAW;AAC3B,YAAU,MAAM,OAAO;AACvB,YAAU,MAAM,MAAM;AACtB,YAAU,MAAM,WAAW;AAC3B,WAAS,KAAK,YAAY,SAAS;AAEnC,MAAI;AACH,UAAM,aAAa,MAAM,QAAQ,OAAO,WAAW,eAAe,IAAI,MAAM,SAAS,GAAG;AAMxF,QAAI,UAAU,UAAU,cAAc,KAAK;AAE3C,QAAI,CAAC,SAAS;AACb,gBAAU,YAAY;AACtB,gBAAU,UAAU,cAAc,KAAK;AACvC,UAAI,CAAC,SAAS;AACb,cAAM,IAAI,oBAAoB,aAAa,aAAa,OAAO;AAAA,MAChE;AAAA,IACD;AAGA,UAAM,gBAAgB,MAAM,QAAQ,WAAW,mBAAmB,IAAI;AAEtE,QAAI;AACJ,YAAQ,aAAa,aAAa;AAAA,MACjC,KAAK,gBAAgB;AACpB,cAAM,KAAK,cAAc;AACzB,cAAM,WAAW,GAAG,YAAY;AAChC,cAAM,QAAQ,GAAG,SAAS;AAC1B,cAAM,YAAY,GAAG,aAAa;AAClC,cAAM,UAAU,GAAG,WAAW;AAC9B,cAAM,SAAS,qBAAqB,OAAO;AAC3C,oBAAY,qBAAqB,QAAQ,UAAU,OAAO,WAAW,OAAO;AAC5E;AAAA,MACD;AAAA,MACA,KAAK,YAAY;AAChB,cAAM,KAAK,cAAc;AACzB,cAAM,SAAS,GAAG,UAAU;AAC5B,cAAM,YAAY,GAAG,aAAa;AAClC,cAAM,WAAW,GAAG,YAAY;AAChC,cAAM,SAAS,oBAAoB,SAAS,UAAU,QAAQ,oBAAoB,QAAQ,CAAC;AAC3F,oBAAY;AAAA,UACX;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,GAAG,iBAAiB;AAAA,UACpB,GAAG,mBAAmB;AAAA,QACvB;AACA;AAAA,MACD;AAAA,MACA,KAAK;AAAA,MACL,KAAK,gBAAgB;AACpB,cAAM,KAAK,cAAc;AACzB,cAAM,SAAS,GAAG,UAAU;AAC5B,cAAM,YAAY,GAAG,aAAa;AAClC,cAAM,UAAU,GAAG,WAAW;AAC9B,cAAM,SAAS,wBAAwB,OAAO;AAC9C,oBAAY,iBAAiB,QAAQ,QAAQ,WAAW,OAAO;AAC/D;AAAA,MACD;AAAA,MACA,KAAK,WAAW;AACf,cAAM,KAAK,cAAc;AACzB,cAAM,OAAO,GAAG,WAAW;AAC3B,YAAI,MAAM;AACT,aAAG,eAAe,IAAI;AACtB,gBAAM,SAAS,mBAAmB,OAAO;AACzC,sBAAY,mBAAmB,QAAQ,MAAM,OAAO;AAAA,QACrD;AACA;AAAA,MACD;AAAA,MACA;AACC,YAAI,QAAQ,sBAAsB;AACjC,gBAAM,QAAQ,qBAAqB,SAAS;AAAA,QAC7C,OAAO;AACN,gBAAM,IAAI,oBAAoB,aAAa,aAAa,aAAa;AAAA,QACtE;AACA;AAAA,IACF;AAEA,QAAI,WAAW;AACd,sBAAgB,QAAQ,WAAW,QAAQ,eAAe;AAAA,IAC3D;AAAA,EACD,SAAS,GAAG;AACX,QAAI,aAAa,oBAAqB,OAAM;AAC5C,YAAQ,MAAM,CAAC;AACf,UAAM,IAAI,oBAAoB,aAAa,aAAa,OAAO;AAAA,EAChE,UAAE;AACD,cAAU,OAAO;AAAA,EAClB;AACD;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -38,12 +38,16 @@ function buildHierarchy(subGraphs) {
|
|
|
38
38
|
}
|
|
39
39
|
function parseFlowchartLayout(root) {
|
|
40
40
|
const nodes = parseNodesFromSvg(root, ".node", (domId) => {
|
|
41
|
-
const match = domId.match(/^flowchart-(.+)-\d+$/);
|
|
41
|
+
const match = domId.match(/^(?:mermaid-\d+-)?flowchart-(.+)-\d+$/);
|
|
42
42
|
return match ? match[1] : domId;
|
|
43
43
|
});
|
|
44
|
-
const clusters = parseClustersFromSvg(
|
|
44
|
+
const clusters = parseClustersFromSvg(
|
|
45
|
+
root,
|
|
46
|
+
".cluster",
|
|
47
|
+
(domId) => domId.replace(/^mermaid-\d+-/, "")
|
|
48
|
+
);
|
|
45
49
|
const edges = parseAllEdgePointsFromSvg(root, (dataId) => {
|
|
46
|
-
const match = dataId.match(
|
|
50
|
+
const match = dataId.match(/(?:^|-)L_(.+)_([^_]+)_\d+$/);
|
|
47
51
|
return match ? { start: match[1], end: match[2] } : null;
|
|
48
52
|
});
|
|
49
53
|
scaleLayout(nodes, clusters, edges, LAYOUT_SCALE);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/flowchartDiagram.ts"],
|
|
4
|
-
"sourcesContent": ["import type {\n\tFlowClass,\n\tFlowEdge,\n\tFlowSubGraph,\n\tFlowVertex,\n} from 'mermaid/dist/diagrams/flowchart/types.js'\nimport { TLArrowShapeArrowheadStyle, TLDefaultDashStyle } from 'tldraw'\nimport type {\n\tDiagramMermaidBlueprint,\n\tMermaidBlueprintEdge,\n\tMermaidBlueprintNode,\n} from './blueprint'\nimport { buildClassDefColorMap, parseCssStyles, parseNodeInlineColor } from './colors'\nimport {\n\tbuildNodeCentersFromSvg,\n\tparseAllEdgePointsFromSvg,\n\tparseClustersFromSvg,\n\ttype ParsedDiagramLayout,\n\tparseNodesFromSvg,\n\tscaleLayout,\n} from './svgParsing'\nimport { getArrowBend, LAYOUT_SCALE, orderTopDown } from './utils'\n\nfunction mapEdgeTypeToArrowhead(type: string | undefined): TLArrowShapeArrowheadStyle {\n\tif (!type) return 'arrow'\n\n\tif (type.includes('point')) return 'arrow'\n\tif (type.includes('circle')) return 'dot'\n\tif (type.includes('cross')) return 'bar'\n\tif (type.includes('open')) return 'none'\n\n\treturn 'arrow'\n}\n\nfunction mapEdgeStrokeToDash(stroke: string | undefined): TLDefaultDashStyle {\n\tif (!stroke) return 'solid'\n\tif (stroke === 'dotted') return 'dotted'\n\treturn 'solid'\n}\n\nconst FRAME_TOP_PAD = 14\n\nfunction buildHierarchy(subGraphs: FlowSubGraph[]) {\n\tconst subGraphIds = new Set(subGraphs.map((subGraph) => subGraph.id))\n\tconst nodeToSubGraph = new Map<string, string>()\n\tconst subGraphParent = new Map<string, string>()\n\tfor (const subGraph of subGraphs) {\n\t\tfor (const nodeId of subGraph.nodes) {\n\t\t\tif (subGraphIds.has(nodeId)) {\n\t\t\t\tsubGraphParent.set(nodeId, subGraph.id)\n\t\t\t} else if (!nodeToSubGraph.has(nodeId)) {\n\t\t\t\tnodeToSubGraph.set(nodeId, subGraph.id)\n\t\t\t}\n\t\t}\n\t}\n\treturn { nodeToSubGraph, subGraphParent }\n}\n\n/** Parse flowchart-specific SVG layout data for use by {@link flowchartToBlueprint}. */\nexport function parseFlowchartLayout(root: Element): ParsedDiagramLayout {\n\tconst nodes = parseNodesFromSvg(root, '.node', (domId) => {\n\t\tconst match = domId.match(/^flowchart-(.+)-\\d+$/)\n\t\treturn match ? match[1] : domId\n\t})\n\tconst clusters = parseClustersFromSvg(root, '.cluster')\n\tconst edges = parseAllEdgePointsFromSvg(root, (dataId) => {\n\t\tconst match = dataId.match(
|
|
5
|
-
"mappings": "AAYA,SAAS,uBAAuB,gBAAgB,4BAA4B;AAC5E;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,OACM;AACP,SAAS,cAAc,cAAc,oBAAoB;AAEzD,SAAS,uBAAuB,MAAsD;AACrF,MAAI,CAAC,KAAM,QAAO;AAElB,MAAI,KAAK,SAAS,OAAO,EAAG,QAAO;AACnC,MAAI,KAAK,SAAS,QAAQ,EAAG,QAAO;AACpC,MAAI,KAAK,SAAS,OAAO,EAAG,QAAO;AACnC,MAAI,KAAK,SAAS,MAAM,EAAG,QAAO;AAElC,SAAO;AACR;AAEA,SAAS,oBAAoB,QAAgD;AAC5E,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI,WAAW,SAAU,QAAO;AAChC,SAAO;AACR;AAEA,MAAM,gBAAgB;AAEtB,SAAS,eAAe,WAA2B;AAClD,QAAM,cAAc,IAAI,IAAI,UAAU,IAAI,CAAC,aAAa,SAAS,EAAE,CAAC;AACpE,QAAM,iBAAiB,oBAAI,IAAoB;AAC/C,QAAM,iBAAiB,oBAAI,IAAoB;AAC/C,aAAW,YAAY,WAAW;AACjC,eAAW,UAAU,SAAS,OAAO;AACpC,UAAI,YAAY,IAAI,MAAM,GAAG;AAC5B,uBAAe,IAAI,QAAQ,SAAS,EAAE;AAAA,MACvC,WAAW,CAAC,eAAe,IAAI,MAAM,GAAG;AACvC,uBAAe,IAAI,QAAQ,SAAS,EAAE;AAAA,MACvC;AAAA,IACD;AAAA,EACD;AACA,SAAO,EAAE,gBAAgB,eAAe;AACzC;AAGO,SAAS,qBAAqB,MAAoC;
|
|
4
|
+
"sourcesContent": ["import type {\n\tFlowClass,\n\tFlowEdge,\n\tFlowSubGraph,\n\tFlowVertex,\n} from 'mermaid/dist/diagrams/flowchart/types.js'\nimport { TLArrowShapeArrowheadStyle, TLDefaultDashStyle } from 'tldraw'\nimport type {\n\tDiagramMermaidBlueprint,\n\tMermaidBlueprintEdge,\n\tMermaidBlueprintNode,\n} from './blueprint'\nimport { buildClassDefColorMap, parseCssStyles, parseNodeInlineColor } from './colors'\nimport {\n\tbuildNodeCentersFromSvg,\n\tparseAllEdgePointsFromSvg,\n\tparseClustersFromSvg,\n\ttype ParsedDiagramLayout,\n\tparseNodesFromSvg,\n\tscaleLayout,\n} from './svgParsing'\nimport { getArrowBend, LAYOUT_SCALE, orderTopDown } from './utils'\n\nfunction mapEdgeTypeToArrowhead(type: string | undefined): TLArrowShapeArrowheadStyle {\n\tif (!type) return 'arrow'\n\n\tif (type.includes('point')) return 'arrow'\n\tif (type.includes('circle')) return 'dot'\n\tif (type.includes('cross')) return 'bar'\n\tif (type.includes('open')) return 'none'\n\n\treturn 'arrow'\n}\n\nfunction mapEdgeStrokeToDash(stroke: string | undefined): TLDefaultDashStyle {\n\tif (!stroke) return 'solid'\n\tif (stroke === 'dotted') return 'dotted'\n\treturn 'solid'\n}\n\nconst FRAME_TOP_PAD = 14\n\nfunction buildHierarchy(subGraphs: FlowSubGraph[]) {\n\tconst subGraphIds = new Set(subGraphs.map((subGraph) => subGraph.id))\n\tconst nodeToSubGraph = new Map<string, string>()\n\tconst subGraphParent = new Map<string, string>()\n\tfor (const subGraph of subGraphs) {\n\t\tfor (const nodeId of subGraph.nodes) {\n\t\t\tif (subGraphIds.has(nodeId)) {\n\t\t\t\tsubGraphParent.set(nodeId, subGraph.id)\n\t\t\t} else if (!nodeToSubGraph.has(nodeId)) {\n\t\t\t\tnodeToSubGraph.set(nodeId, subGraph.id)\n\t\t\t}\n\t\t}\n\t}\n\treturn { nodeToSubGraph, subGraphParent }\n}\n\n/** Parse flowchart-specific SVG layout data for use by {@link flowchartToBlueprint}. */\nexport function parseFlowchartLayout(root: Element): ParsedDiagramLayout {\n\t// Mermaid 11.15 prefixes node and cluster dom ids with the diagram id (e.g.\n\t// `mermaid-0-flowchart-s1-0` instead of `flowchart-s1-0`), so tolerate that\n\t// optional `mermaid-<n>-` prefix when reading the clean id.\n\tconst nodes = parseNodesFromSvg(root, '.node', (domId) => {\n\t\tconst match = domId.match(/^(?:mermaid-\\d+-)?flowchart-(.+)-\\d+$/)\n\t\treturn match ? match[1] : domId\n\t})\n\tconst clusters = parseClustersFromSvg(root, '.cluster', (domId) =>\n\t\tdomId.replace(/^mermaid-\\d+-/, '')\n\t)\n\tconst edges = parseAllEdgePointsFromSvg(root, (dataId) => {\n\t\tconst match = dataId.match(/(?:^|-)L_(.+)_([^_]+)_\\d+$/)\n\t\treturn match ? { start: match[1], end: match[2] } : null\n\t})\n\tscaleLayout(nodes, clusters, edges, LAYOUT_SCALE)\n\treturn { nodes, clusters, edges }\n}\n\n/** Convert a parsed Mermaid flowchart into a tldraw blueprint of nodes and edges. */\nexport function flowchartToBlueprint(\n\tlayout: ParsedDiagramLayout,\n\tvertices: Map<string, FlowVertex>,\n\tedges: FlowEdge[],\n\tsubGraphs?: FlowSubGraph[],\n\tclassDefs?: Map<string, FlowClass>\n): DiagramMermaidBlueprint {\n\tconst nodeColorMap = classDefs ? buildClassDefColorMap(classDefs, vertices) : new Map()\n\tconst { nodes: svgNodes, clusters: svgClusters, edges: svgEdges } = layout\n\tconst nodeCenters = buildNodeCentersFromSvg(svgNodes, svgClusters)\n\n\tconst allSubGraphs = subGraphs || []\n\tconst { nodeToSubGraph, subGraphParent } = buildHierarchy(allSubGraphs)\n\n\tconst nodes: MermaidBlueprintNode[] = []\n\tconst blueprintEdges: MermaidBlueprintEdge[] = []\n\n\t// Frames for subgraphs\n\tfor (const subGraph of orderTopDown(\n\t\tallSubGraphs,\n\t\t(subGraph) => subGraph.id,\n\t\t(subGraph) => subGraphParent.get(subGraph.id)\n\t)) {\n\t\tconst cluster = svgClusters.get(subGraph.id)\n\t\tif (!cluster) continue\n\n\t\tconst id = subGraph.id\n\t\tconst kind = 'subgraph'\n\t\tnodes.push({\n\t\t\tid,\n\t\t\tkind,\n\t\t\tx: cluster.topLeft.x,\n\t\t\ty: cluster.topLeft.y - FRAME_TOP_PAD,\n\t\t\tw: cluster.width,\n\t\t\th: cluster.height + FRAME_TOP_PAD,\n\t\t\tparentId: subGraphParent.get(subGraph.id),\n\t\t\tlabel: subGraph.title || subGraph.id,\n\t\t\tfill: 'semi',\n\t\t\tcolor: 'black',\n\t\t\tdash: 'draw',\n\t\t\tsize: 's',\n\t\t\talign: 'middle',\n\t\t\tverticalAlign: 'start',\n\t\t})\n\t}\n\n\t// Node shapes\n\tfor (const [id, vertex] of vertices) {\n\t\tconst svgNode = svgNodes.get(id)\n\t\tif (!svgNode) continue\n\n\t\tconst kind = vertex.type ?? 'rect'\n\t\tconst colors = nodeColorMap.get(id) ?? parseNodeInlineColor(vertex.styles)\n\n\t\tlet { width: w, height: h } = svgNode\n\t\tif (vertex.type === 'circle' || vertex.type === 'doublecircle') {\n\t\t\tw = h = Math.max(w, h)\n\t\t}\n\n\t\tnodes.push({\n\t\t\tid,\n\t\t\tkind,\n\t\t\tx: svgNode.center.x - w / 2,\n\t\t\ty: svgNode.center.y - h / 2,\n\t\t\tw,\n\t\t\th,\n\t\t\tparentId: nodeToSubGraph.get(id),\n\t\t\tlabel: vertex.text || undefined,\n\t\t\t...(colors?.fillColor && { fill: 'solid' as const }),\n\t\t\t...(colors && { color: colors.strokeColor ?? colors.fillColor }),\n\t\t\talign: 'middle',\n\t\t\tverticalAlign: 'middle',\n\t\t\tsize: 'm',\n\t\t})\n\t}\n\n\t// Edges: match DB edges to SVG edges by proximity, compute bends\n\tconst claimed = new Set<number>()\n\tfor (const edge of edges) {\n\t\tconst startCenter = nodeCenters.get(edge.start)\n\t\tconst endCenter = nodeCenters.get(edge.end)\n\n\t\tlet bend = 0\n\t\tif (startCenter && endCenter) {\n\t\t\tlet bestIndex = -1\n\t\t\tlet bestDist = Infinity\n\t\t\tfor (let i = 0; i < svgEdges.length; i++) {\n\t\t\t\tif (claimed.has(i) || svgEdges[i].points.length < 2) continue\n\n\t\t\t\tconst points = svgEdges[i].points\n\t\t\t\tconst distance =\n\t\t\t\t\tMath.hypot(points[0].x - startCenter.x, points[0].y - startCenter.y) +\n\t\t\t\t\tMath.hypot(\n\t\t\t\t\t\tpoints[points.length - 1].x - endCenter.x,\n\t\t\t\t\t\tpoints[points.length - 1].y - endCenter.y\n\t\t\t\t\t)\n\t\t\t\tif (distance < bestDist) {\n\t\t\t\t\tbestDist = distance\n\t\t\t\t\tbestIndex = i\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (bestIndex >= 0) {\n\t\t\t\tclaimed.add(bestIndex)\n\t\t\t\tbend = getArrowBend(svgEdges[bestIndex])\n\t\t\t}\n\t\t}\n\n\t\tconst cssOverrides = edge.style ? parseCssStyles(edge.style) : undefined\n\t\tconst arrowheadEnd = mapEdgeTypeToArrowhead(edge.type)\n\t\tconst dash = cssOverrides?.dashOverride ?? mapEdgeStrokeToDash(edge.stroke)\n\t\tconst size = cssOverrides?.sizeOverride ?? (edge.stroke === 'thick' ? 'l' : 's')\n\n\t\tblueprintEdges.push({\n\t\t\tstartNodeId: edge.start,\n\t\t\tendNodeId: edge.end,\n\t\t\tlabel: edge.text,\n\t\t\tbend,\n\t\t\tarrowheadEnd,\n\t\t\tarrowheadStart: edge.type?.includes('double_arrow') ? arrowheadEnd : undefined,\n\t\t\tdash,\n\t\t\tsize,\n\t\t\tcolor: cssOverrides?.color,\n\t\t})\n\t}\n\n\tconst nodeIds = new Set(nodes.map((n) => n.id))\n\tconst validEdges = blueprintEdges.filter(\n\t\t(e) => nodeIds.has(e.startNodeId) && nodeIds.has(e.endNodeId)\n\t)\n\treturn { diagramKind: 'flowchart', nodes, edges: validEdges }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["subGraph"]
|
|
7
7
|
}
|
package/dist-esm/index.mjs
CHANGED
|
@@ -30,7 +30,7 @@ function flattenMindmapTree(node, parentId, out) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
function parseMindmapNodeId(domId) {
|
|
33
|
-
const match = domId.match(/^node_(\d+)$/);
|
|
33
|
+
const match = domId.match(/^(?:mermaid-\d+-)?node_(\d+)$/);
|
|
34
34
|
return match ? match[1] : domId;
|
|
35
35
|
}
|
|
36
36
|
function parseMindmapLayout(root) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/mindmapDiagram.ts"],
|
|
4
|
-
"sourcesContent": ["import type { MindmapNode } from 'mermaid/dist/diagrams/mindmap/mindmapTypes.js'\nimport type { TLDefaultColorStyle, TLDefaultSizeStyle } from 'tldraw'\nimport type {\n\tDiagramMermaidBlueprint,\n\tMermaidBlueprintEdge,\n\tMermaidBlueprintNode,\n} from './blueprint'\nimport { parseRgbToTldrawColor } from './colors'\nimport { parseNodesFromSvg, scaleLayout } from './svgParsing'\nimport type { ParsedNode } from './svgParsing'\nimport { LAYOUT_SCALE } from './utils'\n\n/** Mermaid mindmap node `type` integers (`kind` on blueprint nodes is `String(type)`). @public */\nexport const MERMAID_MINDMAP_NODE_TYPE = {\n\tDEFAULT: 0,\n\tROUNDED_RECT: 1,\n\tRECT: 2,\n\tCIRCLE: 3,\n\tCLOUD: 4,\n\tBANG: 5,\n\tHEXAGON: 6,\n} as const\n\nfunction getEdgeSizeForLevel(parentLevel: number): TLDefaultSizeStyle {\n\tif (parentLevel <= 0) return 'l'\n\tif (parentLevel === 1) return 'm'\n\treturn 's'\n}\n\ninterface FlatNode {\n\tid: string\n\tlabel: string\n\ttype: number\n\tlevel: number\n\tparentId: string | undefined\n\tsection: number | undefined\n\tisRoot: boolean\n}\n\nfunction flattenMindmapTree(\n\tnode: MindmapNode,\n\tparentId: string | undefined,\n\tout: FlatNode[]\n): void {\n\tout.push({\n\t\tid: String(node.id),\n\t\tlabel: node.descr,\n\t\ttype: node.type,\n\t\tlevel: node.level,\n\t\tparentId,\n\t\tsection: node.section,\n\t\tisRoot: !!node.isRoot,\n\t})\n\tfor (const child of node.children) {\n\t\tflattenMindmapTree(child, String(node.id), out)\n\t}\n}\n\n/**\n * Pre-parsed SVG layout for mindmap diagram converters.\n * Contains already-scaled node positions extracted from the SVG.\n */\nexport interface ParsedMindmapLayout {\n\tnodes: Map<string, ParsedNode>\n}\n\nfunction parseMindmapNodeId(domId: string): string {\n\tconst match = domId.match(/^node_(\\d+)$/)\n\treturn match ? match[1] : domId\n}\n\n/** Parse mindmap-specific SVG layout data for use by {@link mindmapToBlueprint}. */\nexport function parseMindmapLayout(root: Element): ParsedMindmapLayout {\n\tconst nodes = parseNodesFromSvg(root, '.node', parseMindmapNodeId)\n\tscaleLayout(nodes, new Map(), [], LAYOUT_SCALE)\n\treturn { nodes }\n}\n\n/** Convert a parsed Mermaid mindmap into a tldraw blueprint of nodes and edges. */\nexport function mindmapToBlueprint(\n\tlayout: ParsedMindmapLayout,\n\tmindmapTree: MindmapNode,\n\tsvgRoot: Element\n): DiagramMermaidBlueprint {\n\tconst flatNodes: FlatNode[] = []\n\tflattenMindmapTree(mindmapTree, undefined, flatNodes)\n\n\tconst { nodes: svgNodes } = layout\n\n\tconst nodeColors = new Map<string, TLDefaultColorStyle>()\n\tfor (const el of svgRoot.querySelectorAll('.node')) {\n\t\tconst rawId = el.getAttribute('id') || ''\n\t\tconst id = parseMindmapNodeId(rawId)\n\t\tconst shape =\n\t\t\tel.querySelector('rect, circle, ellipse, polygon, path') ??\n\t\t\tel.querySelector('.label-container')\n\t\tif (shape) {\n\t\t\tconst parsed = parseRgbToTldrawColor(getComputedStyle(shape as Element).fill)\n\t\t\tif (parsed) nodeColors.set(id, parsed.color)\n\t\t}\n\t}\n\n\tconst nodes: MermaidBlueprintNode[] = []\n\tconst edges: MermaidBlueprintEdge[] = []\n\tconst levelById = new Map(flatNodes.map((n) => [n.id, n.level]))\n\n\tfor (const flatNode of flatNodes) {\n\t\tconst svgNode = svgNodes.get(flatNode.id)\n\t\tif (!svgNode) continue\n\n\t\tconst kind = String(flatNode.type)\n\t\tconst color = nodeColors.get(flatNode.id) ?? 'black'\n\n\t\tlet { width: w, height: h } = svgNode\n\t\tif (flatNode.type === MERMAID_MINDMAP_NODE_TYPE.CIRCLE) {\n\t\t\tw = h = Math.max(w, h)\n\t\t}\n\n\t\tconst id = flatNode.id\n\t\tnodes.push({\n\t\t\tid,\n\t\t\tkind,\n\t\t\tx: svgNode.center.x - w / 2,\n\t\t\ty: svgNode.center.y - h / 2,\n\t\t\tw,\n\t\t\th,\n\t\t\tlabel: flatNode.label || undefined,\n\t\t\tfill: 'solid',\n\t\t\tcolor,\n\t\t\tsize: flatNode.isRoot ? 'l' : 'm',\n\t\t\talign: 'middle',\n\t\t\tverticalAlign: 'middle',\n\t\t})\n\n\t\t// Edge from parent to this node\n\t\tif (flatNode.parentId) {\n\t\t\tconst parentLevel = levelById.get(flatNode.parentId) ?? 0\n\t\t\tedges.push({\n\t\t\t\tstartNodeId: flatNode.parentId,\n\t\t\t\tendNodeId: flatNode.id,\n\t\t\t\tbend: 0,\n\t\t\t\tarrowheadEnd: 'none',\n\t\t\t\tarrowheadStart: 'none',\n\t\t\t\tsize: getEdgeSizeForLevel(parentLevel),\n\t\t\t\tcolor,\n\t\t\t})\n\t\t}\n\t}\n\n\tconst nodeIds = new Set(nodes.map((n) => n.id))\n\tconst validEdges = edges.filter((e) => nodeIds.has(e.startNodeId) && nodeIds.has(e.endNodeId))\n\treturn { diagramKind: 'mindmap', nodes, edges: validEdges }\n}\n"],
|
|
5
|
-
"mappings": "AAOA,SAAS,6BAA6B;AACtC,SAAS,mBAAmB,mBAAmB;AAE/C,SAAS,oBAAoB;AAGtB,MAAM,4BAA4B;AAAA,EACxC,SAAS;AAAA,EACT,cAAc;AAAA,EACd,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,SAAS;AACV;AAEA,SAAS,oBAAoB,aAAyC;AACrE,MAAI,eAAe,EAAG,QAAO;AAC7B,MAAI,gBAAgB,EAAG,QAAO;AAC9B,SAAO;AACR;AAYA,SAAS,mBACR,MACA,UACA,KACO;AACP,MAAI,KAAK;AAAA,IACR,IAAI,OAAO,KAAK,EAAE;AAAA,IAClB,OAAO,KAAK;AAAA,IACZ,MAAM,KAAK;AAAA,IACX,OAAO,KAAK;AAAA,IACZ;AAAA,IACA,SAAS,KAAK;AAAA,IACd,QAAQ,CAAC,CAAC,KAAK;AAAA,EAChB,CAAC;AACD,aAAW,SAAS,KAAK,UAAU;AAClC,uBAAmB,OAAO,OAAO,KAAK,EAAE,GAAG,GAAG;AAAA,EAC/C;AACD;AAUA,SAAS,mBAAmB,OAAuB;AAClD,QAAM,QAAQ,MAAM,MAAM
|
|
4
|
+
"sourcesContent": ["import type { MindmapNode } from 'mermaid/dist/diagrams/mindmap/mindmapTypes.js'\nimport type { TLDefaultColorStyle, TLDefaultSizeStyle } from 'tldraw'\nimport type {\n\tDiagramMermaidBlueprint,\n\tMermaidBlueprintEdge,\n\tMermaidBlueprintNode,\n} from './blueprint'\nimport { parseRgbToTldrawColor } from './colors'\nimport { parseNodesFromSvg, scaleLayout } from './svgParsing'\nimport type { ParsedNode } from './svgParsing'\nimport { LAYOUT_SCALE } from './utils'\n\n/** Mermaid mindmap node `type` integers (`kind` on blueprint nodes is `String(type)`). @public */\nexport const MERMAID_MINDMAP_NODE_TYPE = {\n\tDEFAULT: 0,\n\tROUNDED_RECT: 1,\n\tRECT: 2,\n\tCIRCLE: 3,\n\tCLOUD: 4,\n\tBANG: 5,\n\tHEXAGON: 6,\n} as const\n\nfunction getEdgeSizeForLevel(parentLevel: number): TLDefaultSizeStyle {\n\tif (parentLevel <= 0) return 'l'\n\tif (parentLevel === 1) return 'm'\n\treturn 's'\n}\n\ninterface FlatNode {\n\tid: string\n\tlabel: string\n\ttype: number\n\tlevel: number\n\tparentId: string | undefined\n\tsection: number | undefined\n\tisRoot: boolean\n}\n\nfunction flattenMindmapTree(\n\tnode: MindmapNode,\n\tparentId: string | undefined,\n\tout: FlatNode[]\n): void {\n\tout.push({\n\t\tid: String(node.id),\n\t\tlabel: node.descr,\n\t\ttype: node.type,\n\t\tlevel: node.level,\n\t\tparentId,\n\t\tsection: node.section,\n\t\tisRoot: !!node.isRoot,\n\t})\n\tfor (const child of node.children) {\n\t\tflattenMindmapTree(child, String(node.id), out)\n\t}\n}\n\n/**\n * Pre-parsed SVG layout for mindmap diagram converters.\n * Contains already-scaled node positions extracted from the SVG.\n */\nexport interface ParsedMindmapLayout {\n\tnodes: Map<string, ParsedNode>\n}\n\nfunction parseMindmapNodeId(domId: string): string {\n\tconst match = domId.match(/^(?:mermaid-\\d+-)?node_(\\d+)$/)\n\treturn match ? match[1] : domId\n}\n\n/** Parse mindmap-specific SVG layout data for use by {@link mindmapToBlueprint}. */\nexport function parseMindmapLayout(root: Element): ParsedMindmapLayout {\n\tconst nodes = parseNodesFromSvg(root, '.node', parseMindmapNodeId)\n\tscaleLayout(nodes, new Map(), [], LAYOUT_SCALE)\n\treturn { nodes }\n}\n\n/** Convert a parsed Mermaid mindmap into a tldraw blueprint of nodes and edges. */\nexport function mindmapToBlueprint(\n\tlayout: ParsedMindmapLayout,\n\tmindmapTree: MindmapNode,\n\tsvgRoot: Element\n): DiagramMermaidBlueprint {\n\tconst flatNodes: FlatNode[] = []\n\tflattenMindmapTree(mindmapTree, undefined, flatNodes)\n\n\tconst { nodes: svgNodes } = layout\n\n\tconst nodeColors = new Map<string, TLDefaultColorStyle>()\n\tfor (const el of svgRoot.querySelectorAll('.node')) {\n\t\tconst rawId = el.getAttribute('id') || ''\n\t\tconst id = parseMindmapNodeId(rawId)\n\t\tconst shape =\n\t\t\tel.querySelector('rect, circle, ellipse, polygon, path') ??\n\t\t\tel.querySelector('.label-container')\n\t\tif (shape) {\n\t\t\tconst parsed = parseRgbToTldrawColor(getComputedStyle(shape as Element).fill)\n\t\t\tif (parsed) nodeColors.set(id, parsed.color)\n\t\t}\n\t}\n\n\tconst nodes: MermaidBlueprintNode[] = []\n\tconst edges: MermaidBlueprintEdge[] = []\n\tconst levelById = new Map(flatNodes.map((n) => [n.id, n.level]))\n\n\tfor (const flatNode of flatNodes) {\n\t\tconst svgNode = svgNodes.get(flatNode.id)\n\t\tif (!svgNode) continue\n\n\t\tconst kind = String(flatNode.type)\n\t\tconst color = nodeColors.get(flatNode.id) ?? 'black'\n\n\t\tlet { width: w, height: h } = svgNode\n\t\tif (flatNode.type === MERMAID_MINDMAP_NODE_TYPE.CIRCLE) {\n\t\t\tw = h = Math.max(w, h)\n\t\t}\n\n\t\tconst id = flatNode.id\n\t\tnodes.push({\n\t\t\tid,\n\t\t\tkind,\n\t\t\tx: svgNode.center.x - w / 2,\n\t\t\ty: svgNode.center.y - h / 2,\n\t\t\tw,\n\t\t\th,\n\t\t\tlabel: flatNode.label || undefined,\n\t\t\tfill: 'solid',\n\t\t\tcolor,\n\t\t\tsize: flatNode.isRoot ? 'l' : 'm',\n\t\t\talign: 'middle',\n\t\t\tverticalAlign: 'middle',\n\t\t})\n\n\t\t// Edge from parent to this node\n\t\tif (flatNode.parentId) {\n\t\t\tconst parentLevel = levelById.get(flatNode.parentId) ?? 0\n\t\t\tedges.push({\n\t\t\t\tstartNodeId: flatNode.parentId,\n\t\t\t\tendNodeId: flatNode.id,\n\t\t\t\tbend: 0,\n\t\t\t\tarrowheadEnd: 'none',\n\t\t\t\tarrowheadStart: 'none',\n\t\t\t\tsize: getEdgeSizeForLevel(parentLevel),\n\t\t\t\tcolor,\n\t\t\t})\n\t\t}\n\t}\n\n\tconst nodeIds = new Set(nodes.map((n) => n.id))\n\tconst validEdges = edges.filter((e) => nodeIds.has(e.startNodeId) && nodeIds.has(e.endNodeId))\n\treturn { diagramKind: 'mindmap', nodes, edges: validEdges }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -32,7 +32,26 @@ const LINETYPE = {
|
|
|
32
32
|
BREAK_END: 31,
|
|
33
33
|
PAR_OVER_START: 32,
|
|
34
34
|
BIDIRECTIONAL_SOLID: 33,
|
|
35
|
-
BIDIRECTIONAL_DOTTED: 34
|
|
35
|
+
BIDIRECTIONAL_DOTTED: 34,
|
|
36
|
+
SOLID_TOP: 41,
|
|
37
|
+
SOLID_BOTTOM: 42,
|
|
38
|
+
STICK_TOP: 43,
|
|
39
|
+
STICK_BOTTOM: 44,
|
|
40
|
+
SOLID_ARROW_TOP_REVERSE: 45,
|
|
41
|
+
SOLID_ARROW_BOTTOM_REVERSE: 46,
|
|
42
|
+
STICK_ARROW_TOP_REVERSE: 47,
|
|
43
|
+
STICK_ARROW_BOTTOM_REVERSE: 48,
|
|
44
|
+
SOLID_TOP_DOTTED: 51,
|
|
45
|
+
SOLID_BOTTOM_DOTTED: 52,
|
|
46
|
+
STICK_TOP_DOTTED: 53,
|
|
47
|
+
STICK_BOTTOM_DOTTED: 54,
|
|
48
|
+
SOLID_ARROW_TOP_REVERSE_DOTTED: 55,
|
|
49
|
+
SOLID_ARROW_BOTTOM_REVERSE_DOTTED: 56,
|
|
50
|
+
STICK_ARROW_TOP_REVERSE_DOTTED: 57,
|
|
51
|
+
STICK_ARROW_BOTTOM_REVERSE_DOTTED: 58,
|
|
52
|
+
CENTRAL_CONNECTION: 59,
|
|
53
|
+
CENTRAL_CONNECTION_REVERSE: 60,
|
|
54
|
+
CENTRAL_CONNECTION_DUAL: 61
|
|
36
55
|
};
|
|
37
56
|
const PLACEMENT = {
|
|
38
57
|
LEFTOF: 0,
|