← All @molecule/* packages · App templates
@molecule/app-mind-map-canvas-reactFeature · mind-map-canvas · App (browser) · v1.0.1 · Apache-2.0
React mind-map canvas — tree-structured nodes with parent/child links, automatic radial/horizontal/vertical tree layout, fold/unfold subtrees, inline edit-on-double-click; thin domain wrapper over @molecule/app-feature-canvas-react.
npm install @molecule/app-mind-map-canvas-react@molecule/app-mind-map-canvas-react is a ready-made mind-map-canvas feature for the app (browser) side. It composes the core interfaces it needs, so it works with whichever providers your app has bonded.
import { useState } from 'react'
import { MindMapCanvas, type MindMapNode } from '@molecule/app-mind-map-canvas-react'
function Demo() {
const [root, setRoot] = useState<MindMapNode>({
id: 'r',
text: 'Project',
children: [
{ id: 'a', text: 'Plan', children: [] },
{ id: 'b', text: 'Build', children: [] },
],
})
return <MindMapCanvas root={root} onChange={setRoot} layout="radial" />
}Works with: @molecule/app-feature-canvas-react, @molecule/app-react, @molecule/app-ui
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
React mind-map canvas — thin domain wrapper over the shared canvas base. Tree-structured nodes (parent/child links), automatic radial / horizontal / vertical tree layout, fold/unfold subtrees, inline edit-on-double-click, +/- toggles to add and collapse children.
Pan / zoom is delegated to <CanvasSurface> from
@molecule/app-feature-canvas-react. This package owns only the
mind-map domain semantics — adding mechanics that live above the
pan/zoom-and-paint base.
Exports:
<MindMapCanvas> — top-level widget; controlled or uncontrolled.MindMapNode, MindMapLayout, MindMapLayoutResult types.computeRadialPositions,
computeHorizontalTreePositions, layout knobs + defaults.findNode, updateNode, setNodeText,
toggleCollapsed, addChild, removeNode.import { useState } from 'react'
import { MindMapCanvas, type MindMapNode } from '@molecule/app-mind-map-canvas-react'
function Demo() {
const [root, setRoot] = useState<MindMapNode>({
id: 'r',
text: 'Project',
children: [
{ id: 'a', text: 'Plan', children: [] },
{ id: 'b', text: 'Build', children: [] },
],
})
return <MindMapCanvas root={root} onChange={setRoot} layout="radial" />
}
feature
npm install @molecule/app-mind-map-canvas-react @molecule/app-feature-canvas-react @molecule/app-react @molecule/app-ui react
npm install -D @types/react
LayoutOptionsPer-layout knobs (all optional).
interface LayoutOptions {
/** Node width in canvas units. */
nodeWidth?: number
/** Node height in canvas units. */
nodeHeight?: number
/** Distance between concentric rings (radial layout). */
ringSpacing?: number
/** Horizontal step between generations (horizontal/vertical layout). */
stepX?: number
/** Vertical step between sibling subtrees (horizontal layout). */
stepY?: number
/** Origin in canvas-space for the root node's center. Defaults to `(0, 0)`. */
origin?: Point
}
MindMapCanvasProps<MindMapCanvas> props.
interface MindMapCanvasProps {
/**
* Root of the mind-map tree. The canvas is fully-controlled when
* `onChange` is supplied; otherwise the component manages its own
* mirror via `useState`.
*/
root: MindMapNode
/**
* Optional change callback. When provided, the canvas runs in
* controlled mode — every fold/edit/add-child mutation calls
* `onChange(nextRoot)` and the parent decides whether to commit.
*/
onChange?: (next: MindMapNode) => void
/** Layout strategy. Defaults to `'radial'`. */
layout?: MindMapLayout
/** Width of the surface in CSS pixels. Defaults to `800`. */
width?: number
/** Height of the surface in CSS pixels. Defaults to `600`. */
height?: number
/** Node width in canvas units. Defaults to {@link DEFAULT_NODE_WIDTH}. */
nodeWidth?: number
/** Node height in canvas units. Defaults to {@link DEFAULT_NODE_HEIGHT}. */
nodeHeight?: number
/** Fired when a node is clicked (single click, no drag). */
onNodeClick?: (node: MindMapNode) => void
/**
* Fired when a node's text is committed via inline edit. Receives the
* node id and the new text. Useful for analytics / autosave.
*/
onNodeEdit?: (id: string, text: string) => void
/** Extra classes merged onto the outer wrapper. */
className?: string
}
MindMapLayoutResultResult of a layout computation: a flat map of nodeId → canvas-space
top-left position, plus the parent/child pairs that should be drawn
as edges (in tree order, parents first). Collapsed subtrees are
omitted.
interface MindMapLayoutResult {
/** Map of `nodeId` → canvas-space top-left position. */
positions: Map<string, Point>
/** Parent/child id pairs to draw as edges. */
edges: Array<{ parentId: string; childId: string }>
/** Flat list of nodes that should be rendered (i.e. not hidden by a collapsed ancestor). */
visibleNodes: MindMapNode[]
}
MindMapNodeOne mind-map node. Every node is the root of its own subtree (the
top-level node passed as root is just the outermost example).
interface MindMapNode {
/** Stable identifier — must be unique across the entire tree. */
id: string
/** Display text shown in the node's body. */
text: string
/** Direct child subtrees, rendered in array order. */
children: MindMapNode[]
/**
* If `true`, the node renders without its descendants — the subtree
* is effectively hidden until expanded. Defaults to `false`.
*/
collapsed?: boolean
/**
* Optional accent color (CSS color string). Used as the inline
* border-left accent on the node body; ClassMap drives the rest of
* the chrome.
*/
color?: string
}
MindMapLayoutLayout strategies <MindMapCanvas> knows how to compute.
type MindMapLayout = 'radial' | 'horizontal' | 'vertical'
addChild(root, parentId, child)Append a child to the node with the given id. The new child is pushed at the end of the children array. If the parent is currently collapsed, it is automatically expanded so the new child is visible.
function addChild(root: MindMapNode, parentId: string, child: MindMapNode): MindMapNode
root — Tree root.parentId — Id of the parent that will receive the new child.child — The child node to append.Returns: A new tree with the child appended (and the parent expanded).
computeHorizontalTreePositions(root, options, axis)Horizontal tidy-tree layout: root on the left, children fan out to
the right, each subtree's children stacked vertically with enough
room for every leaf descendant. The axis option swaps the role
of the X and Y axes for a vertical (top-down) tree.
function computeHorizontalTreePositions(
root: MindMapNode,
options?: LayoutOptions,
axis?: 'horizontal' | 'vertical',
): MindMapLayoutResult
root — Tree root.options — Optional layout knobs.axis — 'horizontal' (default) places generations along +X; 'vertical' places generations along +Y.Returns: Layout result with positions, edges, and the visible-node list.
computeRadialPositions(root, options)Radial layout: root at origin, children fan out around it at
uniform angles, each generation living on its own concentric ring.
For deeper generations the angular slice of each child is its
parent's slice divided by the parent's child count.
function computeRadialPositions(root: MindMapNode, options?: LayoutOptions): MindMapLayoutResult
root — Tree root.options — Optional layout knobs.Returns: Layout result with positions, edges, and the visible-node list.
findNode(root, id)Walk the tree and find the node with the given id, returning a pointer-style reference (not a clone). Used internally by the other helpers — exported because it's also handy at call sites.
function findNode(root: MindMapNode, id: string): MindMapNode | null
root — Tree root.id — Id of the node to locate.Returns: The node, or null if no node has that id.
MindMapCanvas(props)Mind-map canvas — a thin domain wrapper over
@molecule/app-feature-canvas-react. Composes <CanvasSurface> for
pan/zoom, <CanvasNode> for each tree node, and <CanvasEdge kind="bezier"> for parent → child links. Domain-specific behavior
lives here, not in the base:
./layout.js.collapsed, hiding (or revealing) the entire subtree.<input>; commit on Enter / blur, cancel on Escape.+ button on every node appends a new child
("New idea") and auto-expands the parent.Pan / zoom is owned by <CanvasSurface>; this wrapper never
re-implements those mechanics.
Style is driven by getClassMap(). Inline styles are reserved for
canvas-space geometry and the optional accent border.
function MindMapCanvas(props: MindMapCanvasProps): JSX.Element
props — Component props.Returns: The mind-map canvas element.
removeNode(root, id)Remove the node with the given id (and its entire subtree) from the tree. The root itself cannot be removed — passing the root id is a no-op that returns the original tree.
function removeNode(root: MindMapNode, id: string): MindMapNode
root — Tree root.id — Id of the node to remove.Returns: A new tree with the node removed, or the original tree if id matches the root.
setNodeText(root, id, text)Set a node's text.
function setNodeText(root: MindMapNode, id: string, text: string): MindMapNode
root — Tree root.id — Id of the node to edit.text — New text content.Returns: A new tree with the node's text updated.
toggleCollapsed(root, id)Toggle a node's collapsed flag.
function toggleCollapsed(root: MindMapNode, id: string): MindMapNode
root — Tree root.id — Id of the node to toggle.Returns: A new tree with the node's collapsed flag flipped.
updateNode(root, id, updater)Apply a transformation to the node with the given id, returning a new tree where every ancestor of the touched node has been re-cloned so React-style reference equality remains correct on unchanged subtrees.
function updateNode(
root: MindMapNode,
id: string,
updater: (node: MindMapNode) => MindMapNode,
): MindMapNode
root — Tree root.id — Id of the node to update.updater — Function that returns the replacement node.Returns: A new tree, or the original if id was not found.
walkVisible(root, visit)Walk the tree and call visit(node, parent | null, depth) for every
node not hidden by a collapsed ancestor. Visits in pre-order.
function walkVisible(
root: MindMapNode,
visit: (node: MindMapNode, parent: MindMapNode | null, depth: number) => void,
): void
root — Tree root.visit — Callback invoked once per visible node.DEFAULT_HORIZONTAL_STEP_XDefault horizontal tree per-generation step in canvas units.
const DEFAULT_HORIZONTAL_STEP_X: 220
DEFAULT_HORIZONTAL_STEP_YDefault horizontal tree per-leaf step in canvas units.
const DEFAULT_HORIZONTAL_STEP_Y: 72
DEFAULT_NODE_HEIGHTDefault canvas-space height of one node.
const DEFAULT_NODE_HEIGHT: 48
DEFAULT_NODE_WIDTHDefault canvas-space size of one node, used to space siblings.
const DEFAULT_NODE_WIDTH: 160
DEFAULT_RADIAL_RINGDefault radial ring spacing in canvas units.
const DEFAULT_RADIAL_RING: 180
Peer dependencies:
@molecule/app-feature-canvas-react ^1.0.1@molecule/app-react ^1.0.1@molecule/app-ui ^1.0.1react ^18.0.0 || ^19.0.0@molecule/app-feature-canvas-react@molecule/app-react@molecule/app-uireactRequires a wired ClassMap bond (setClassMap(...) at startup) and a
React I18nProvider ancestor — getClassMap() and useTranslation()
both throw before wiring. Pair with the companion locale bond
@molecule/app-locales-mind-map-canvas for translated aria labels and
the "New idea" default child text (English fallbacks otherwise).
The surface is FIXED-SIZE: width / height are pixel props
(default 800x600) — the canvas does not track its parent's size. To
fill a panel, measure the parent (e.g. a resize observer) and pass
the measured pixels down; CSS alone will clip, not resize.
Supplying onChange makes the tree fully controlled (every
fold / edit / add-child calls it with the next root); omitting it lets
the canvas manage an internal copy seeded from root.
Translation strings are provided by @molecule/app-locales-mind-map-canvas.