# Edit Mermaid diagrams

Use Mermaid as a human-editable view of a live Grafloria canvas: load Mermaid text into the
instance, change its labels or links, then export Mermaid-compatible text with the document's
positions and styling preserved.

## When to use this

Use the instance's `exportText()` and `loadText()` when the diagram is already mounted. Exported
text contains a Mermaid body followed by Grafloria sidecar comments. Mermaid consumers render the
body and ignore the comments; Grafloria uses the sidecar to retain information Mermaid does not
represent, including positions and styling.

For a one-off JavaScript mount, start with [`render`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-element-core#render), which
returns a [`DiagramInstance`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-renderer-instance-diagraminstance#diagraminstance).
For framework applications, keep the instance supplied by the binding's component ready callback.

## Import, edit, and export

1. Give the canvas host a resolved height. A host with no height produces a blank canvas.
2. Mount a diagram with nodes and edges.
3. Export after initialization and after the canvas has painted, or from a user action.
4. Let the user edit the Mermaid body, then call `loadText(text)` on the same instance.
5. Call `exportText()` again to save the edited diagram.

The first load of untouched exported text uses the sidecar and restores the exact document. If the
body changed, `loadText()` detects the edit and applies the Mermaid text on top of the sidecar
model. Existing nodes and links are reconciled into the live model rather than replacing the
model, so unchanged positions remain available.

### JavaScript

```js
import { render } from '@grafloria/element';

const host = document.createElement('div');
host.style.height = '400px';
host.style.width = '800px';
const editor = document.createElement('textarea');
editor.style.width = '100%';
editor.style.height = '180px';
const apply = document.createElement('button');
apply.textContent = 'Apply Mermaid edit';
document.body.append(host, editor, apply);

const instance = render({
  nodes: [
    { id: 'plan', label: 'Plan', position: { x: 60, y: 90 }, size: { width: 150, height: 66 } },
    { id: 'build', label: 'Build', position: { x: 300, y: 90 }, size: { width: 150, height: 66 } },
    { id: 'ship', label: 'Ship', position: { x: 540, y: 90 }, size: { width: 150, height: 66 } },
  ],
  edges: [
    { id: 'e1', source: 'plan', target: 'build' },
    { id: 'e2', source: 'build', target: 'ship' },
  ],
}, host);

instance.renderNow();
editor.value = instance.exportText();
apply.addEventListener('click', () => {
  editor.value = editor.value.replace('Plan', 'Design');
  instance.loadText(editor.value);
  editor.value = instance.exportText();
});
window.addEventListener('pagehide', () => instance.dispose(), { once: true });
```

The code mounts the flow, changes `Plan` to `Design` in the Mermaid body, and loads that edit back
into the same instance. The canvas shows the new label while the sidecar keeps the existing node
positions.

### React

[`GrafloriaFlow`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-react#grafloriaflow) owns the rendered canvas; capture its
`onInit` instance rather than constructing a second renderer.

```text
import { useRef, useState } from 'react';
import { GrafloriaFlow } from '@grafloria/react';
import type { DiagramInstance } from '@grafloria/react';

const nodes = [
  { id: 'start', position: { x: 80, y: 60 }, size: { width: 140, height: 60 }, data: { label: 'Start' } },
  { id: 'work', position: { x: 320, y: 60 }, size: { width: 140, height: 60 }, data: { label: 'Work' } },
  { id: 'done', position: { x: 560, y: 60 }, size: { width: 140, height: 60 }, data: { label: 'Done' } },
];
const edges = [{ id: 'e1', source: 'start', target: 'work' }, { id: 'e2', source: 'work', target: 'done' }];

export default function MermaidEditor() {
  const instance = useRef<DiagramInstance | null>(null);
  const [text, setText] = useState('');
  return <div style={{ display: 'flex', height: '400px' }}>
    <GrafloriaFlow defaultNodes={nodes} defaultEdges={edges} style={{ flex: 1 }} onInit={(api) => {
      instance.current = api;
      setText(api.exportText());
    }} />
    <div style={{ width: 340 }}>
      <button onClick={() => setText(instance.current?.exportText() ?? '')}>Export</button>
      <button onClick={() => instance.current?.loadText(text)}>Load</button>
      <textarea value={text} onChange={(event) => setText(event.target.value)} style={{ width: '100%', height: '350px' }} />
    </div>
  </div>;
}
```

The left side renders the three nodes; editing the textarea and choosing Load reconciles that text
into the same instance.

### Vue

```text
<script setup lang="ts">
import { ref } from 'vue';
import { GrafloriaFlow } from '@grafloria/vue';
import type { DiagramInstance } from '@grafloria/vue';

const text = ref('');
let instance: DiagramInstance | null = null;
const nodes = [
  { id: 'start', position: { x: 80, y: 60 }, size: { width: 140, height: 60 }, data: { label: 'Start' } },
  { id: 'work', position: { x: 320, y: 60 }, size: { width: 140, height: 60 }, data: { label: 'Work' } },
  { id: 'done', position: { x: 560, y: 60 }, size: { width: 140, height: 60 }, data: { label: 'Done' } },
];
const edges = [{ id: 'e1', source: 'start', target: 'work' }, { id: 'e2', source: 'work', target: 'done' }];
function onInit(api: DiagramInstance) { instance = api; text.value = api.exportText(); }
</script>

<template>
  <div style="display:flex;height:400px">
    <GrafloriaFlow :default-nodes="nodes" :default-edges="edges" @init="onInit" />
    <div style="width:340px">
      <button @click="text = instance?.exportText() ?? ''">Export</button>
      <button @click="instance?.loadText(text)">Load</button>
      <textarea v-model="text" style="width:100%;height:350px" />
    </div>
  </div>
</template>
```

The canvas and editor stay side by side. Load applies the current textarea value; Export replaces it
with the current Mermaid-compatible representation.

### Qwik

For the complete Qwik setup and shared text workflow, see [Text and lossless round trips](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/text-and-lossless-round-trips); this page focuses on applying the edited text to the mounted canvas.

### React

```tsx
// import { GrafloriaFlow } from '@grafloria/react';
// Render <GrafloriaFlow defaultNodes={nodes} defaultEdges={edges} onInit={setInstance} />.
// Set text from instance.exportText(); call instance.loadText(text) from Load.
```

### Vue

```tsx
// import { GrafloriaFlow } from '@grafloria/vue';
// Render <GrafloriaFlow :default-nodes="nodes" :default-edges="edges" @init="onInit" />.
// Set text from instance.exportText(); call instance.loadText(text) from Load.
```

### Angular

```ts
// import { DiagramCanvasComponent } from '@grafloria/angular';
// Mount <grafloria-diagram-canvas [(nodes)]="nodes" [(edges)]="edges" />.
// Call canvas.exportText() for Export and canvas.loadText(text) for Load.
```

## What the text contains

`exportText()` returns a Mermaid-shaped flowchart body and, by default, a
`%%grafloria:document` sidecar. The sidecar carries the exact document, while a body hash lets
`loadText()` distinguish untouched exported text from a hand edit. An untouched round trip returns
the sidecar source; a changed body returns the text source.

For parser-only workflows, [`importDiagramText`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-engine-serialization#importdiagramtext)
returns an import result without mounting a canvas. For model-only export,
[`exportDiagramText`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-engine-serialization#exportdiagramtext) writes the same
Mermaid-compatible representation. Use those lower-level functions when no live canvas exists;
use the instance methods when the user is editing a rendered diagram.

Unsupported Mermaid diagram types report an `unsupported` result. Do not render that result as a
different diagram type; inspect it and leave the current canvas intact.

## Live demos

- [Mermaid text](https://grafloria.com/demos/misc/mermaid-text.html) shows sidecar export, body
  editing, and position preservation.

- [Mermaid viewer](https://grafloria.com/demos/misc/mermaid-viewer.html) lets you paste or select
  Mermaid text and reports unsupported diagram types rather than displaying a blank canvas.
- [Mermaid architecture and block diagrams](https://grafloria.com/demos/diagrams/mermaid-architecture-block.html)
  demonstrates editable architecture-beta and block-beta text, including groups, spans, and
  styles.

## Related

- [Text and lossless round trips](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/text-and-lossless-round-trips)
- [The DiagramInstance](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-renderer-instance-diagraminstance)
- [Export diagrams](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/export-diagrams)
- [JavaScript quick start](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/javascript-quick-start)
