Getting Started
Open MindMap v0.9.0 is a modular React and TypeScript runtime. Choose the smallest public surface that matches the product: headless Core, Static SVG, an interactive Viewer, or the Editor Feature host.
Installation
# npm
npm install @xiangfa/mindmap
# pnpm
pnpm add @xiangfa/mindmap
# yarn
yarn add @xiangfa/mindmapFor LaTeX formula rendering, install the optional KaTeX peer as well:
pnpm add katexQuick Start
import { MindMapEditor } from '@xiangfa/mindmap/editor'
import '@xiangfa/mindmap/styles/editor.css'
const markdown = `Project roadmap
- Research
- Build
- Launch`
export function Roadmap() {
return <MindMapEditor markdown={markdown} />
}Markdown Input
Pass a Markdown string directly. Controlled input is useful for streamed output, persistence, or a source editor; defaultMarkdown is the uncontrolled initial value.
const markdown = `Machine Learning
- Supervised Learning
- Classification
- Regression
- Unsupervised Learning
Application Areas
- Natural Language Processing
- Computer Vision`
<MindMapEditor
markdown={markdown}
onMarkdownChange={setMarkdown}
/>Separate root trees with a blank line. For structured input, pass a MindMapDocument through document, or legacy-compatible node data through data.
Theme and Layout Direction
<MindMapEditor markdown={markdown} theme="auto" />
<MindMapEditor markdown={markdown} theme="dark" />
<MindMapEditor markdown={markdown} theme="light" />
<MindMapEditor markdown={markdown} defaultDirection="both" />
<MindMapEditor markdown={markdown} defaultDirection="right" />
<MindMapEditor markdown={markdown} defaultDirection="left" />theme and direction are controlled props. Use themeTokens for literal rendering colors and defaultDirection when the surface should own direction changes.
Read-only Editor
<MindMapEditor markdown={markdown} readOnly />Read-only Editor mode keeps pan, zoom, selection, and folding available while disabling document mutations and edit commands.
Lightweight Viewer
Use the dedicated Viewer entry for dashboards, documentation, and embeds that need interaction but not mutation. It excludes Editor commands and opt-in Features without making an unsupported bundle-size claim.
import { MindMapViewer } from '@xiangfa/mindmap/viewer'
import '@xiangfa/mindmap/styles/viewer.css'
<MindMapViewer
markdown={markdown}
extensions={extensions}
selectable
/>For a non-interactive React SVG, use StaticMindMap from @xiangfa/mindmap/static with styles/static.css.
Markdown Editor Feature
The source editor is an opt-in Editor Feature. Its draft stays synchronized through the same controller transaction used by visual edits.
import { markdownEditorFeature } from '@xiangfa/mindmap/features/markdown-editor'
import '@xiangfa/mindmap/styles/features/markdown-editor.css'
const features = [markdownEditorFeature({ title: 'Markdown source' })]
<MindMapEditor markdown={markdown} features={features} />Pick a public surface
| Entry | Purpose | Stylesheet |
|---|---|---|
@xiangfa/mindmap/core | Parser, serializer, layout, patches, controller, streaming, and portable SVG | None |
@xiangfa/mindmap/static | Non-interactive React SVG | styles/static.css |
@xiangfa/mindmap/viewer | Read-only pan, zoom, focus, selection, and folding | styles/viewer.css |
@xiangfa/mindmap/editor | Editing, commands, toolbar, context menu, and Feature slots | styles/editor.css |
Compose Features and Extensions
import { MindMapEditor } from '@xiangfa/mindmap/editor'
import { basicMindMapExtensions } from '@xiangfa/mindmap/extensions'
import { historyFeature } from '@xiangfa/mindmap/features/history'
import { searchFeature } from '@xiangfa/mindmap/features/search'
import '@xiangfa/mindmap/styles/editor.css'
import '@xiangfa/mindmap/styles/features/history.css'
import '@xiangfa/mindmap/styles/features/search.css'
const extensions = basicMindMapExtensions()
const features = [historyFeature(), searchFeature()]
<MindMapEditor
markdown={markdown}
extensions={extensions}
features={features}
onMarkdownChange={setMarkdown}
/>All Seven Extensions
import {
basicMindMapExtensions,
crossLinkExtension,
frontmatterExtension,
latexExtension,
} from '@xiangfa/mindmap/extensions'
const extensions = [
...basicMindMapExtensions(), // tags, folding, multiline, dotted lines
frontmatterExtension(),
crossLinkExtension(),
latexExtension(),
]Extensions are opt-in in v0.9. Pass an empty array to keep the core grammar only.
Ref API
import { useRef } from 'react'
import { MindMapEditor, type MindMapEditorRef } from '@xiangfa/mindmap/editor'
const ref = useRef<MindMapEditorRef>(null)
async function focusResearch() {
ref.current?.fitView(true)
ref.current?.focusNode('research')
}
<MindMapEditor ref={ref} markdown={markdown} />The ref exposes the current Document, Markdown and controller; data import; SVG and outline export; undo/redo; commands; viewport focus; selection; direction; and node editing helpers.
Listening for Changes
<MindMapEditor
defaultMarkdown="Roadmap
- Research"
onMarkdownChange={(nextMarkdown) => saveSource(nextMarkdown)}
onDocumentChange={(nextDocument) => saveDocument(nextDocument)}
onEvent={(event) => {
if (event.phase === 'commit') persist(event.current.document)
}}
/>preview events are transient. Persist on commit, and reconcile to current.document after rollback.
i18n / Localization
The runtime detects the browser locale and includes English and Simplified Chinese messages. Supply locale or override individual strings through messages.
<MindMapEditor markdown={markdown} locale="en-US" />
<MindMapEditor
markdown={markdown}
locale="zh-CN"
messages={{ newNode: 'New', zoomIn: 'Zoom in' }}
/>MindMapDocument plus namespaced node attributes. See MIGRATION-v0.9.md.Basic Syntax
The first non-empty line creates a root. Indented list items create descendants. Multiple unindented lines create independent roots on the same surface.
Machine Learning
- Supervised Learning
- Classification
- Logistic Regression
- Decision Trees
- Regression
- Linear Regression
- Unsupervised Learning
- Clustering
- Dimensionality Reduction
Computer Vision
- Image Classification
- Object DetectionUse two spaces per nesting level. Empty lines are ignored, and node IDs remain stable when the parser can reconcile an updated Markdown structure with the current Document.
Text Formatting
Node labels support inline Markdown. Formatting is parsed as display tokens; it is never executed as HTML.
Machine Learning
- **Bold Topic**
- *Italic Topic*
- ~~Deprecated Topic~~
- `code or identifier`
- ==Highlighted Topic==| Syntax | Effect | Use case |
|---|---|---|
**bold** | Strong emphasis | Important nodes |
*italic* | Emphasis | Supplementary descriptions |
~~text~~ | Strikethrough | Deprecated or completed items |
`code` | Inline code | Technical terms and identifiers |
==text== | Highlight | Key concepts |
Links & Images
Use standard Markdown links and image tokens inside node labels.
Resources
- [Open MindMap](https://github.com/u14app/mindmap)
- 
- [Documentation](/docs/)[text](url)creates a clickable hyperlink inside the node label.creates an image token and preserves its alt text when loading is not authorized.
remoteImagePolicy; PNG export additionally requires a host imageResolver that embeds safe raster data URLs.Remarks
A remark begins with > under a node and stores supporting prose separately from its visible title.
Machine Learning
- Supervised Learning
> Learn a mapping from labelled examples.
> Output may be categorical or continuous.
- Classification
- RegressionRemarks do not become child nodes. They live in node.attributes.remark.text and appear through the Viewer or Editor's accessible description and tooltip behavior.
Comments
Lines beginning with %% remain in the Markdown source but do not render as nodes.
%% Internal planning note
Machine Learning
- Supervised Learning
%% Revisit examples before publishing
- Classification
- ClusteringA line is a comment only when %% begins the line after optional whitespace. Inline text such as test%%demo remains ordinary node content.
Task Status
Task markers become structured status attributes and can be changed by Editor commands.
Learning Plan Q1
- [x] Linear Algebra
- [-] Probability
- [ ] Neural Networks
- [x] Backpropagation
- [ ] Image Segmentation| Marker | Status | Meaning |
|---|---|---|
[ ] | todo | To do |
[-] | doing | In progress |
[x] | done | Completed |
Extended Syntax
Extended syntax is provided by opt-in Extensions. The basic set contains tags, folding, multiline content, and dotted lines; frontmatter, cross-links, and LaTeX are explicit additions. Together they preserve the seven capabilities documented by v0.7.1.
import {
basicMindMapExtensions,
crossLinkExtension,
frontmatterExtension,
latexExtension,
} from '@xiangfa/mindmap/extensions'
const extensions = [
...basicMindMapExtensions(),
frontmatterExtension(),
crossLinkExtension(),
latexExtension(),
]Dotted Lines
Use -. instead of - to render a dotted edge for a weak, optional, or tentative relationship.
Machine Learning
- Supervised Learning
- Classification
-. Feature Engineering| Syntax | Line style | Meaning |
|---|---|---|
- | Solid | Standard parent-child relationship |
-. | Dotted | Weak, optional, or tentative relationship |
Multi-line Node Content
Lines beginning with | attach visible detail lines to the preceding node. Unlike remarks, these lines render inside the node.
Machine Learning
- Supervised Learning
- Classification
| **Definition**: Maps inputs to categories.
| **Input**: Feature vector X
| **Output**: Class label Y
- Regression
| Produces continuous values.| Syntax | Display | Purpose |
|---|---|---|
> text | Remark / tooltip | Supplementary information outside the visible title |
| text | In-node detail | Visible multi-line node content |
Tags
Use #tag tokens to classify nodes. The tags Extension stores them in node.attributes.tags.values; Viewer and Editor surfaces can filter them with activeTags.
Tech Stack
- React #frontend #javascript
- Astro #framework
- Zustand #state-management
- TypeScript #language
- PostgreSQL #database #backendCross-node Connections
Use {#id} to name an anchor and -> {#id} to connect a node outside the parent-child tree.
System Architecture
- Frontend {#frontend}
- Backend
- API Gateway {#api-gateway}
- REST
- GraphQL
- Response -> {#frontend} "HTTP"
- Data Layer
- Cache -.> {#api-gateway}{#id}defines an anchor.-> {#id}creates a solid cross-link.-> {#id} "label"adds a label.-.> {#id}creates a dotted cross-link.
Folding Markers
Use + instead of - to store a branch as collapsed in node.attributes.folding.collapsed.
Project Structure
- src/
- components/
- Button.tsx
- Modal.tsx
+ utilities/
- format.ts
- validate.ts
- README.md-starts expanded.+starts collapsed and can be expanded by the Viewer or Editor.
Formula Support (LaTeX)
The LaTeX Extension recognizes inline $...$ and block $$...$$ formulas. Interactive React surfaces lazy-load the optional KaTeX peer; StaticMindMap accepts a trusted synchronous renderMath function for SSR, and portable export accepts that same hook or loads KaTeX when available.
Loss Functions
- MSE
| $L = \frac{1}{n}\sum_{i=1}^{n}(y_i - \hat{y}_i)^2$
- Cross Entropy
| $$L = -\sum_i y_i \log(\hat{y}_i)$$Global Configuration (Frontmatter)
The frontmatter Extension reads document-level direction and theme values from a YAML-style header.
---
direction: right
theme: auto
---
Machine Learning
- Supervised Learning
- Unsupervised Learning| Field | Values | Purpose |
|---|---|---|
direction | left, right, both | Document layout direction |
theme | auto, light, dark | Document theme preference |
AI Generation
AI generation is an optional Editor Feature. When configured, it adds a prompt bar to the Editor and accepts either a host-supplied generator or the built-in OpenAI-compatible adapter. The runtime owns Markdown parsing, incremental previews, one completed history entry, cancellation, and rollback.
Basic Usage
import { MindMapEditor } from '@xiangfa/mindmap/editor'
import {
aiFeature,
createOpenAICompatibleGenerator,
} from '@xiangfa/mindmap/features/ai'
import '@xiangfa/mindmap/styles/editor.css'
import '@xiangfa/mindmap/styles/features/ai.css'
const generator = createOpenAICompatibleGenerator({
apiUrl: '/api/chat/completions', // server-side proxy
model: 'gpt-5',
})
const features = [aiFeature({ generator })]
export function Brainstorm() {
return <MindMapEditor defaultMarkdown="Product" features={features} />
}apiUrl should normally be your own same-origin proxy.Custom Generator
A generator receives the prompt, current Markdown, frozen Document, an AbortSignal, and optional prepared attachments. It returns a complete Markdown string or an async iterable of delta strings.
import { aiFeature, type MindMapAIGenerator } from '@xiangfa/mindmap/features/ai'
const generate: MindMapAIGenerator = async function* ({ prompt, markdown, signal }) {
const response = await fetch('/api/mindmap', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt, markdown }),
signal,
})
if (!response.ok || !response.body) throw new Error('Generation failed')
const reader = response.body.getReader()
const decoder = new TextDecoder()
const cancel = () => { void reader.cancel() }
signal.addEventListener('abort', cancel, { once: true })
try {
while (!signal.aborted) {
const { value, done } = await reader.read()
if (done) break
const delta = decoder.decode(value, { stream: true })
if (delta) yield delta
}
const tail = decoder.decode()
if (!signal.aborted && tail) yield tail
} finally {
signal.removeEventListener('abort', cancel)
if (signal.aborted) await reader.cancel().catch(() => undefined)
reader.releaseLock()
}
}
const features = [aiFeature({ generate })]interface MindMapAIGeneratorInput {
prompt: string
markdown: string
document: MindMapDocument
signal: AbortSignal
attachments?: readonly MindMapAIAttachment[]
}
type MindMapAIGeneratorResult =
| string
| AsyncIterable<string>
| Promise<string | AsyncIterable<string>>Async iterables must yield delta chunks. Adapt cumulative snapshot streams explicitly. Empty output, cancellation, parse failure, a replaced controller transaction, or a network error cancels the stream and restores the baseline Document.
OpenAI-compatible Configuration
interface MindMapAIConfig {
apiUrl: string
model: string
apiKey?: string
systemPrompt?: string
attachments?: readonly ('text' | 'image' | 'pdf')[]
maxAttachmentSize?: number
maxAttachments?: number
maxTotalAttachmentSize?: number
attachmentReadConcurrency?: number
headers?: Record<string, string>
request?: (payload: MindMapAIRequestPayload) => Promise<Response>
}| Field | Required | Purpose |
|---|---|---|
apiUrl | Yes | Chat-completions endpoint or host proxy |
model | Yes | Provider model identifier |
apiKey | No | Optional browser Bearer token; prefer a server proxy |
systemPrompt | No | Override the Markdown-only generation instruction |
headers | No | Additional request headers |
request | No | Host adapter for proxy or provider-specific transport |
File Attachments
Enable attachments on aiFeature or its provider config. The Feature validates the allow-list and size limits before invoking the generator.
const features = [aiFeature({
generator,
attachments: ['text', 'image', 'pdf'],
maxAttachmentSize: 5 * 1024 * 1024,
maxAttachments: 10,
maxTotalAttachmentSize: 20 * 1024 * 1024,
attachmentReadConcurrency: 3,
})]| Type | Accepted input | Generator value |
|---|---|---|
text | text/* and common source/data extensions | Decoded text |
image | PNG, JPEG, GIF, or WebP | Base64 data URL |
pdf | application/pdf | Base64 data URL for a provider-specific file part |
Defaults are 5 MiB per file, 10 files, 20 MiB total source bytes, and three concurrent readers. Abort signals stop both file reading and generation.
Custom System Prompt
const generator = createOpenAICompatibleGenerator({
apiUrl: '/api/chat/completions',
model: 'gpt-5',
systemPrompt: [
'Return only a Markdown mind map.',
'Use one root title and indented list items.',
'Do not wrap the result in a code fence.',
].join(' '),
})Website Demo Adapter
The public website keeps the v0.7.1 GET endpoint for its shared Playground, but adapts the endpoint's cumulative text/plain body through MindMapEditorRef.getController().createMarkdownStream(). It strips reasoning blocks and fences, replaces previews, commits once, and rolls back on stop, unmount, empty output, parse errors, or network failures.
https://open-mindmap-ai.u14.app/api/mindmap. It does not send attachments. This disclosure appears next to the prompt input on both the homepage and Live editor.Custom Styling
Import the stylesheet matching each runtime surface and Feature. v0.9 separates six runtime CSS variables from the typed MindMapThemeTokens projection: CSS controls the host surface and interaction UI, while theme tokens produce deterministic SVG geometry and literal export colors.
Surface and Feature Styles
@import '@xiangfa/mindmap/styles/editor.css';
@import '@xiangfa/mindmap/styles/features/history.css';
@import '@xiangfa/mindmap/styles/features/search.css';
.product-map .mm-surface {
--mm-background: #ffffff;
--mm-text: #253044;
--mm-muted: #748096;
--mm-root: #334155;
--mm-selection: #007aff;
--mm-font: system-ui, sans-serif;
}| CSS variable | Controls |
|---|---|
--mm-background | Canvas and child-node background |
--mm-text | Primary node text |
--mm-muted | Secondary labels and details |
--mm-root | Root-node fill |
--mm-selection | Selection treatment |
--mm-font | Runtime font family |
Theme Tokens
<MindMapEditor
markdown={markdown}
theme="light"
themeTokens={{
background: '#ffffff',
text: '#253044',
rootFill: '#334155',
rootText: '#ffffff',
selection: '#007aff',
branches: ['#ff646b', '#43c6c3', '#6ca9ff'],
}}
/>| Group | Theme-token fields |
|---|---|
| Color | background, text, mutedText, rootFill, rootText, selection |
| Branches | branches — ordered literal branch colors |
| Typography | fontFamily, rootFontSize, levelOneFontSize, nodeFontSize |
| Layout | horizontalGap, verticalGap |
| Padding | rootPaddingX, rootPaddingY, nodePaddingX, nodePaddingY |
CSS Class Selectors
Runtime SVG and controls expose stable mm- classes. Rules can target the full surface or a named node without depending on generated layout coordinates.
/* Child node shape and underline accent */
.product-map .mm-node--child .mm-node-shape {
stroke-width: 2;
}
.product-map .mm-node-accent {
stroke-width: 3;
}
/* Connection lines */
.product-map .mm-edge {
stroke-linecap: square;
}
/* One stable document node ID */
.product-map .mm-node[data-mm-node="release"] .mm-node-label {
font-weight: 800;
}| Class | Target |
|---|---|
.mm-static | Static React surface |
.mm-surface | Interactive Viewer or Editor surface |
.mm-node--root / .mm-node--child | Root and child SVG groups |
.mm-node-shape | Node background shape |
.mm-node-label / .mm-node-detail | Primary and multiline text |
.mm-node-accent | Child-node accent line |
.mm-edge | Parent-child and cross-link paths |
.mm-fold-control | Fold and expand control |
.mm-viewport-controls | Pan, zoom, fit, and fullscreen controls |
.mm-editor-toolbar / .mm-context-menu | Editor commands and context menu |
Branch Colors
Set branch colors with themeTokens.branches. The headless layout cycles this ordered palette and writes literal colors into each projected node and edge.
<MindMapEditor
markdown={markdown}
themeTokens={{
branches: ['#e74c3c', '#2ecc71', '#3498db', '#f59e0b'],
}}
/>SVG and PNG Export
Portable SVG output contains resolved colors, geometry, text, classes, and allowed embedded image data. It does not depend on the website's CSS variables. Prepare the SVG before rasterization so authorized remote images and trusted math output can be embedded.
import {
prepareMindMapSvg,
renderSvgToPng,
} from '@xiangfa/mindmap/features/export'
const svg = await prepareMindMapSvg(document, {
extensions,
theme: literalThemeTokens,
imageResolver,
})
const png = await renderSvgToPng(svg)Use literal tokens when exported SVG or PNG must remain self-contained. Host-page CSS is intentionally unavailable after a file is downloaded.
API Reference
v0.9 separates the headless document runtime from React surfaces and optional Features. The root package retains compatibility aliases, but new integrations should prefer explicit entries.
Public entrypoints
| Entry | Primary exports |
|---|---|
@xiangfa/mindmap/core | parseMindMap, serializeMindMap, layoutMindMap, createMindMapController, createMarkdownStream, renderMindMapToSvg |
@xiangfa/mindmap/static | StaticMindMap |
@xiangfa/mindmap/viewer | MindMapViewer, MindMapViewerRef |
@xiangfa/mindmap/editor | MindMapEditor, MindMapEditorRef, command types |
@xiangfa/mindmap/features/* | history, search, import, export, markdown-editor, and AI Feature factories |
@xiangfa/mindmap/extensions | Seven built-in Extension factories and basicMindMapExtensions |
Props
| Prop | Type | Purpose |
|---|---|---|
markdown / defaultMarkdown | string | Controlled or initial Markdown input |
document / data | MindMapDocument | MindMapNode[] | Structured input |
documentRevision | string | number | Explicit revision for authoritative controlled replacements |
controller | MindMapController | Share a headless controller |
extensions | readonly MindMapExtension[] | Parsing and layout hooks |
features | readonly MindMapEditorFeature[] | Optional Editor capabilities |
theme / themeTokens | MindMapThemeMode / token overrides | Runtime appearance |
direction / defaultDirection | 'left' | 'right' | 'both' | Controlled or initial layout direction |
toolbar | boolean | MindMapToolbarConfig | Show or configure the Editor toolbar |
readOnly | boolean | Disable Document mutations while keeping navigation |
locale / messages | string / overrides | Localize runtime controls |
searchQuery / activeTags | controlled filters | Drive search and tag filtering from the host |
selectedNodeId | string | null | Controlled selection |
autoFit | 'initial' | 'always' | 'never' | Viewport fitting policy |
culling | MindMapCullingOptions | Bound large-map DOM rendering |
remoteImagePolicy | 'deny' | 'allow' | predicate | Authorize sanitized remote image URLs |
onMarkdownChange | (markdown) => void | Receive serializable Document changes |
onDocumentChange / onEvent | callbacks | Receive frozen Documents or phased controller events |
onInteractionEvent / onViewportChange | callbacks | Observe view-only interaction without treating it as a Document commit |
ToolbarConfig
Pass false to hide the toolbar, or an object with zoom, history, search, tags, editing, direction, textMode, and fullscreen flags. Feature controls appear only when the matching Feature is installed.
<MindMapEditor
markdown={markdown}
features={[historyFeature(), searchFeature()]}
toolbar={{
zoom: true,
history: true,
search: true,
tags: false,
editing: true,
direction: true,
textMode: false,
fullscreen: true,
}}
/>Ref Methods
const ref = useRef<MindMapEditorRef>(null)
ref.current?.fitView(true)
ref.current?.setMarkdown('Roadmap
- Research')
const controller = ref.current?.getController()
const document = ref.current?.getDocument()| Method group | Methods |
|---|---|
| Read | getDocument, getData, getMarkdown, getController, getCommands |
| Replace / import | setData, setMarkdown, importData, importMarkdown |
| Export | exportToSVG, exportToOutline |
| History and commands | undo, redo, canUndo, canRedo, executeCommand |
| Viewport | fitView, focusNode, selectNode, setDirection |
| Editing | startEditing, addChild, addRoot, addSibling, removeNode |
| Folding | expandNode, collapseNode |
Data Structure
interface MindMapNode {
id: string
text: string
children?: MindMapNode[]
attributes?: MindMapNodeAttributes
}
interface MindMapDocument {
roots: MindMapNode[]
direction?: 'left' | 'right' | 'both'
theme?: 'light' | 'dark' | 'auto'
metadata?: Record<string, string>
comments?: Array<{ text: string; afterNodeId: string | null }>
}Node attributes use namespaces for tasks, remarks, tags, folding, multiline content, dotted connections, and custom Extension data. Controller snapshots and every reachable Document or layout value are frozen.
MindMapViewer
The Viewer accepts the same Document, Markdown, controller, Extension, theme, layout, filter, selection, locale, image-policy, culling, and viewport props that apply to reading. It intentionally omits Editor Features and mutations.
import { MindMapViewer, type MindMapViewerRef } from '@xiangfa/mindmap/viewer'
import '@xiangfa/mindmap/styles/viewer.css'
const viewerRef = useRef<MindMapViewerRef>(null)
<MindMapViewer
ref={viewerRef}
markdown={markdown}
extensions={extensions}
activeTags={['docs']}
onSelectedNodeChange={setSelectedNodeId}
/>MindMapViewerRef Methods
getDocument, getData, getController, fitView, focusNode, selectNode, setDirection, and getViewport.
Input and image boundaries
Every public Document boundary validates content before traversal, layout, rendering, patch application, or resolver callbacks. Shared MAX_MINDMAP_* constants cover document length, node count, nesting, attributes, images, and patch batches. Remote images remain denied unless the host authorizes them.
Controller events
Controller events use preview, commit, rollback, and change phases. Persist completed edits on commit; treat previews as transient and reconcile rollback to current.document.
const controller = createMindMapController(markdown, { extensions })
const unsubscribe = controller.subscribeEvents((event) => {
if (event.phase === 'commit') save(event.current.document)
if (event.phase === 'rollback') restore(event.current.document)
})
const stream = controller.createMarkdownStream()
stream.replace('Roadmap
- Research')
await stream.commit()
unsubscribe()
controller.dispose()Keyboard Shortcuts
Editor commands apply while the surface has focus. Native inputs retain their normal editing behavior.
| Shortcut or gesture | Action |
|---|---|
| Arrow keys | Move selection through the tree |
Tab | Add a child to the selected node |
Shift + Enter | Add a sibling |
Enter / F2 | Edit node text |
Delete / Backspace | Remove the selected node |
| Double-click a node | Edit node text |
Space | Toggle folding |
Escape | Cancel editing, clear selection, or close an active Editor dialog |
Cmd/Ctrl + Z | Undo |
Cmd/Ctrl + Shift + Z / Cmd/Ctrl + Y | Redo |
Cmd/Ctrl + C/X/V | Copy, cut, or paste a subtree |
Alt + Arrow | Move or reparent a node |
Shift + 0 | Fit the complete map |
Shift + L/R/M | Set left, right, or both-side layout |
| Scroll wheel | Zoom around the pointer |
| Drag empty canvas | Pan |
| Drag a node | Place before, after, or under another node |
| Right-click a node | Open the command menu |
Utility Functions
Headless utilities are available from the Core entry; browser export helpers live with the Export Feature. These replace the v0.7 utility aliases with Document-first, explicitly named operations.
import {
applyMindMapPatches,
applyMindMapPatchesWithInverse,
authorizeMindMapImageUrl,
createMarkdownStream,
createMindMapParser,
createMindMapController,
diffMindMapDocuments,
findNode,
layoutMindMap,
normalizeDocument,
parseMindMap,
reconcileMindMapIds,
renderMindMapToSvg,
sanitizeMindMapUrl,
serializeMindMap,
tokenizeMindMapInline,
validateMindMapDocument,
walkNodes,
} from '@xiangfa/mindmap/core'
import {
exportMindMapOutline,
prepareMindMapSvg,
renderSvgToPng,
} from '@xiangfa/mindmap/features/export'| Area | Functions | Use |
|---|---|---|
| Parse and serialize | parseMindMap, createMindMapParser, serializeMindMap | Convert between Markdown and MindMapDocument |
| Projection | layoutMindMap, renderMindMapToSvg | Compute deterministic geometry or portable SVG without React |
| Patches | diffMindMapDocuments, applyMindMapPatches, applyMindMapPatchesWithInverse | Create, apply, and invert immutable edits |
| Runtime | createMindMapController, createMarkdownStream | Own snapshots, history, transactions, and streaming previews |
| Document utilities | normalizeDocument, validateMindMapDocument, reconcileMindMapIds, walkNodes, findNode | Normalize, validate, reconcile, traverse, and query |
| Inline and URLs | tokenizeMindMapInline, sanitizeMindMapUrl, authorizeMindMapImageUrl | Inspect formatted labels and enforce URL policy |
| Browser export | prepareMindMapSvg, renderSvgToPng, exportMindMapOutline | Embed authorized assets and download-friendly formats |
const document = parseMindMap(markdown, { extensions })
const layout = layoutMindMap(document, { extensions })
const svg = renderMindMapToSvg(document, { extensions })
const prepared = await prepareMindMapSvg(document, { extensions })
const png = await renderSvgToPng(prepared)renderMindMapToSvg exports the complete Document without mounting React. prepareMindMapSvg can embed host-authorized images and optional math output before renderSvgToPng validates source and raster dimensions. Remote images must be embedded through an authorized resolver before PNG conversion.