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

bash
# npm
npm install @xiangfa/mindmap

# pnpm
pnpm add @xiangfa/mindmap

# yarn
yarn add @xiangfa/mindmap

For LaTeX formula rendering, install the optional KaTeX peer as well:

bash
pnpm add katex

Quick Start

tsx
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} />
}
The Editor fills the available surface. Give its parent an explicit height, or use a constrained grid or flex child as the website demo does.

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.

tsx
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

tsx
<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

tsx
<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.

tsx
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.

tsx
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

EntryPurposeStylesheet
@xiangfa/mindmap/coreParser, serializer, layout, patches, controller, streaming, and portable SVGNone
@xiangfa/mindmap/staticNon-interactive React SVGstyles/static.css
@xiangfa/mindmap/viewerRead-only pan, zoom, focus, selection, and foldingstyles/viewer.css
@xiangfa/mindmap/editorEditing, commands, toolbar, context menu, and Feature slotsstyles/editor.css

Compose Features and Extensions

tsx
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

tsx
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

tsx
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

tsx
<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.

tsx
<MindMapEditor markdown={markdown} locale="en-US" />

<MindMapEditor
  markdown={markdown}
  locale="zh-CN"
  messages={{ newNode: 'New', zoomIn: 'Zoom in' }}
/>
Migrating from v0.7.1? Prefer explicit package entries, use v0.9 Extension factories, and adopt 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.

mindmap
Machine Learning
- Supervised Learning
  - Classification
    - Logistic Regression
    - Decision Trees
  - Regression
    - Linear Regression
- Unsupervised Learning
  - Clustering
  - Dimensionality Reduction

Computer Vision
- Image Classification
- Object Detection

Use 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.

mindmap
Machine Learning
- **Bold Topic**
- *Italic Topic*
- ~~Deprecated Topic~~
- `code or identifier`
- ==Highlighted Topic==
SyntaxEffectUse case
**bold**Strong emphasisImportant nodes
*italic*EmphasisSupplementary descriptions
~~text~~StrikethroughDeprecated or completed items
`code`Inline codeTechnical terms and identifiers
==text==HighlightKey concepts

Use standard Markdown links and image tokens inside node labels.

mindmap
Resources
- [Open MindMap](https://github.com/u14app/mindmap)
- ![Architecture diagram](data:image/png;base64,...)
- [Documentation](/docs/)
  • [text](url) creates a clickable hyperlink inside the node label.
  • ![alt](url) creates an image token and preserves its alt text when loading is not authorized.
URL schemes are sanitized. Remote HTTP(S) images are denied by default and remain readable as alt text. Authorize intended origins with 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.

mindmap
Machine Learning
- Supervised Learning
  > Learn a mapping from labelled examples.
  > Output may be categorical or continuous.
  - Classification
  - Regression

Remarks 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.

mindmap
%% Internal planning note
Machine Learning
- Supervised Learning
  %% Revisit examples before publishing
  - Classification
- Clustering

A 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.

mindmap
Learning Plan Q1
- [x] Linear Algebra
- [-] Probability
- [ ] Neural Networks
  - [x] Backpropagation
  - [ ] Image Segmentation
MarkerStatusMeaning
[ ]todoTo do
[-]doingIn progress
[x]doneCompleted

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.

tsx
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.

mindmap
Machine Learning
- Supervised Learning
  - Classification
  -. Feature Engineering
SyntaxLine styleMeaning
-SolidStandard parent-child relationship
-.DottedWeak, 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.

mindmap
Machine Learning
- Supervised Learning
  - Classification
    | **Definition**: Maps inputs to categories.
    | **Input**: Feature vector X
    | **Output**: Class label Y
  - Regression
    | Produces continuous values.
SyntaxDisplayPurpose
> textRemark / tooltipSupplementary information outside the visible title
| textIn-node detailVisible 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.

mindmap
Tech Stack
- React #frontend #javascript
  - Astro #framework
  - Zustand #state-management
- TypeScript #language
- PostgreSQL #database #backend

Cross-node Connections

Use {#id} to name an anchor and -> {#id} to connect a node outside the parent-child tree.

mindmap
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.

mindmap
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.

mindmap
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.

mindmap
---
direction: right
theme: auto
---

Machine Learning
- Supervised Learning
- Unsupervised Learning
FieldValuesPurpose
directionleft, right, bothDocument layout direction
themeauto, light, darkDocument 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

tsx
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} />
}
Keep long-lived provider credentials on a server. The browser-facing 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.

tsx
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 })]
typescript
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

typescript
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>
}
FieldRequiredPurpose
apiUrlYesChat-completions endpoint or host proxy
modelYesProvider model identifier
apiKeyNoOptional browser Bearer token; prefer a server proxy
systemPromptNoOverride the Markdown-only generation instruction
headersNoAdditional request headers
requestNoHost 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.

tsx
const features = [aiFeature({
  generator,
  attachments: ['text', 'image', 'pdf'],
  maxAttachmentSize: 5 * 1024 * 1024,
  maxAttachments: 10,
  maxTotalAttachmentSize: 20 * 1024 * 1024,
  attachmentReadConcurrency: 3,
})]
TypeAccepted inputGenerator value
texttext/* and common source/data extensionsDecoded text
imagePNG, JPEG, GIF, or WebPBase64 data URL
pdfapplication/pdfBase64 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

tsx
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.

The demo sends prompt text to 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

css
@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 variableControls
--mm-backgroundCanvas and child-node background
--mm-textPrimary node text
--mm-mutedSecondary labels and details
--mm-rootRoot-node fill
--mm-selectionSelection treatment
--mm-fontRuntime font family

Theme Tokens

tsx
<MindMapEditor
  markdown={markdown}
  theme="light"
  themeTokens={{
    background: '#ffffff',
    text: '#253044',
    rootFill: '#334155',
    rootText: '#ffffff',
    selection: '#007aff',
    branches: ['#ff646b', '#43c6c3', '#6ca9ff'],
  }}
/>
GroupTheme-token fields
Colorbackground, text, mutedText, rootFill, rootText, selection
Branchesbranches — ordered literal branch colors
TypographyfontFamily, rootFontSize, levelOneFontSize, nodeFontSize
LayouthorizontalGap, verticalGap
PaddingrootPaddingX, 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.

css
/* 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;
}
ClassTarget
.mm-staticStatic React surface
.mm-surfaceInteractive Viewer or Editor surface
.mm-node--root / .mm-node--childRoot and child SVG groups
.mm-node-shapeNode background shape
.mm-node-label / .mm-node-detailPrimary and multiline text
.mm-node-accentChild-node accent line
.mm-edgeParent-child and cross-link paths
.mm-fold-controlFold and expand control
.mm-viewport-controlsPan, zoom, fit, and fullscreen controls
.mm-editor-toolbar / .mm-context-menuEditor 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.

tsx
<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.

typescript
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

EntryPrimary exports
@xiangfa/mindmap/coreparseMindMap, serializeMindMap, layoutMindMap, createMindMapController, createMarkdownStream, renderMindMapToSvg
@xiangfa/mindmap/staticStaticMindMap
@xiangfa/mindmap/viewerMindMapViewer, MindMapViewerRef
@xiangfa/mindmap/editorMindMapEditor, MindMapEditorRef, command types
@xiangfa/mindmap/features/*history, search, import, export, markdown-editor, and AI Feature factories
@xiangfa/mindmap/extensionsSeven built-in Extension factories and basicMindMapExtensions

Props

PropTypePurpose
markdown / defaultMarkdownstringControlled or initial Markdown input
document / dataMindMapDocument | MindMapNode[]Structured input
documentRevisionstring | numberExplicit revision for authoritative controlled replacements
controllerMindMapControllerShare a headless controller
extensionsreadonly MindMapExtension[]Parsing and layout hooks
featuresreadonly MindMapEditorFeature[]Optional Editor capabilities
theme / themeTokensMindMapThemeMode / token overridesRuntime appearance
direction / defaultDirection'left' | 'right' | 'both'Controlled or initial layout direction
toolbarboolean | MindMapToolbarConfigShow or configure the Editor toolbar
readOnlybooleanDisable Document mutations while keeping navigation
locale / messagesstring / overridesLocalize runtime controls
searchQuery / activeTagscontrolled filtersDrive search and tag filtering from the host
selectedNodeIdstring | nullControlled selection
autoFit'initial' | 'always' | 'never'Viewport fitting policy
cullingMindMapCullingOptionsBound large-map DOM rendering
remoteImagePolicy'deny' | 'allow' | predicateAuthorize sanitized remote image URLs
onMarkdownChange(markdown) => voidReceive serializable Document changes
onDocumentChange / onEventcallbacksReceive frozen Documents or phased controller events
onInteractionEvent / onViewportChangecallbacksObserve 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.

tsx
<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

tsx
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 groupMethods
ReadgetDocument, getData, getMarkdown, getController, getCommands
Replace / importsetData, setMarkdown, importData, importMarkdown
ExportexportToSVG, exportToOutline
History and commandsundo, redo, canUndo, canRedo, executeCommand
ViewportfitView, focusNode, selectNode, setDirection
EditingstartEditing, addChild, addRoot, addSibling, removeNode
FoldingexpandNode, collapseNode

Data Structure

typescript
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.

tsx
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.

typescript
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 gestureAction
Arrow keysMove selection through the tree
TabAdd a child to the selected node
Shift + EnterAdd a sibling
Enter / F2Edit node text
Delete / BackspaceRemove the selected node
Double-click a nodeEdit node text
SpaceToggle folding
EscapeCancel editing, clear selection, or close an active Editor dialog
Cmd/Ctrl + ZUndo
Cmd/Ctrl + Shift + Z / Cmd/Ctrl + YRedo
Cmd/Ctrl + C/X/VCopy, cut, or paste a subtree
Alt + ArrowMove or reparent a node
Shift + 0Fit the complete map
Shift + L/R/MSet left, right, or both-side layout
Scroll wheelZoom around the pointer
Drag empty canvasPan
Drag a nodePlace before, after, or under another node
Right-click a nodeOpen 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.

typescript
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'
AreaFunctionsUse
Parse and serializeparseMindMap, createMindMapParser, serializeMindMapConvert between Markdown and MindMapDocument
ProjectionlayoutMindMap, renderMindMapToSvgCompute deterministic geometry or portable SVG without React
PatchesdiffMindMapDocuments, applyMindMapPatches, applyMindMapPatchesWithInverseCreate, apply, and invert immutable edits
RuntimecreateMindMapController, createMarkdownStreamOwn snapshots, history, transactions, and streaming previews
Document utilitiesnormalizeDocument, validateMindMapDocument, reconcileMindMapIds, walkNodes, findNodeNormalize, validate, reconcile, traverse, and query
Inline and URLstokenizeMindMapInline, sanitizeMindMapUrl, authorizeMindMapImageUrlInspect formatted labels and enforce URL policy
Browser exportprepareMindMapSvg, renderSvgToPng, exportMindMapOutlineEmbed authorized assets and download-friendly formats
typescript
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.