Mind Elixir is a JavaScript mind map rendering library that converts hierarchical NodeObj data structures into interactive DOM visualizations. The library implements node CRUD operations, SVG connection rendering, and an event-driven plugin architecture. It has zero runtime dependencies and exports ES modules compatible with any framework.
Mind Elixir implements the following functionality:
| Feature | Implementation |
|---|---|
| Data Model | Tree structure of NodeObj with recursive children[] arrays src/types/index.ts192-239 |
| Rendering | DOM generation via layout() and linkDiv() functions src/utils/layout.ts src/linkDiv.ts |
| Interaction | Mouse/touch event handlers in initMouseEvent() src/mouse.ts |
| Operations | Node manipulation functions in src/nodeOperation.ts |
| Extensibility | Plugin system via install() method src/methods.ts55-60 |
| State Management | Event bus with bus.fire() and bus.addListener() src/utils/pubsub.ts |
| Build Outputs | Full build, lite IIFE build, SSR layout utility package.json28-46 |
The library is framework-agnostic and provides TypeScript type definitions at dist/types/index.d.ts.
Sources: package.json1-108 src/types/index.ts192-239 src/utils/layout.ts src/linkDiv.ts src/mouse.ts src/nodeOperation.ts src/methods.ts55-60 src/utils/pubsub.ts
The mind-elixir npm package exports multiple entry points defined in package.json28-51:
| Export Path | Format | Source | Type Definitions |
|---|---|---|---|
mind-elixir | ESM/CJS | src/index.ts | dist/types/index.d.ts |
mind-elixir/lite | IIFE | Build output excluding MindElixir.new() | None |
mind-elixir/example | ESM/CJS | src/exampleData/1.ts | dist/types/exampleData/1.d.ts |
mind-elixir/LayoutSsr | ESM/CJS | src/utils/LayoutSsr.ts | dist/types/utils/LayoutSsr.d.ts |
mind-elixir/style | CSS | Compiled from src/index.less | None |
The build process build.js and package.json16 generates:
dist/MindElixir.js: Full ESM/CJS build with all plugins. Conditionally includes MindElixir.new() at src/index.ts174-182 based on import.meta.env.MODE.dist/MindElixirLite.iife.js: IIFE bundle built with MODE='lite' flag, excluding utility methods.dist/LayoutSsr.js: Server-side layout calculator from src/utils/LayoutSsr.ts that operates without DOM APIs.dist/MindElixir.css: Compiled stylesheet from src/index.less and src/markdown.cssdist/types/**/*.d.ts: TypeScript declarations generated by tsc compiler package.json17The library has zero runtime dependencies package.json77-107 Build tools include Vite vite.config.ts TypeScript compiler, and custom build script build.js
Sources: package.json28-51 package.json16-17 build.js src/index.ts174-182 src/utils/LayoutSsr.ts vite.config.ts
System Architecture Diagram:
The following table maps system components to their code implementations:
| Component | Type | Location | Purpose |
|---|---|---|---|
MindElixir | Constructor | src/index.ts19-133 | Factory function returning MindElixirInstance |
MindElixirInstance | Interface | src/types/index.ts91-132 | Central state object with DOM references and data |
MindElixirData | Interface | src/types/index.ts75-88 | Serialization format for mind map data |
NodeObj | Interface | src/types/index.ts192-239 | Single node data structure with children[] |
Options | Interface | src/types/index.ts134-169 | Constructor configuration parameters |
createBus() | Function | src/utils/pubsub.ts1-27 | Factory for event bus with fire() and addListener() |
layout() | Function | src/utils/layout.ts1-200 | DOM tree generation from NodeObj |
linkDiv() | Function | src/linkDiv.ts1-250 | SVG path rendering for connections |
initMouseEvent() | Function | src/mouse.ts1-300 | Pointer event registration |
The constructor src/index.ts19-133 initializes these properties on the instance:
| Property | Type | Purpose |
|---|---|---|
this.el | HTMLElement | Container element from options.el |
this.container | HTMLDivElement | Root element with class map-container |
this.map | HTMLDivElement | Canvas element with class map-canvas |
this.nodes | HTMLElement | Custom element <me-nodes> |
this.lines | SVGSVGElement | SVG element with id lines for branch paths |
this.linkSvgGroup | SVGSVGElement | SVG element with id topiclinks for arrows |
this.summarySvg | SVGSVGElement | SVG element with id summary for brackets |
this.linkController | SVGSVGElement | SVG element with id linkcontroller for editing |
this.bus | EventBus | Pub/sub system from createBus() |
this.nodeData | NodeObj | Root node of data tree |
this.arrows | Arrow[] | Custom connections between nodes |
this.summaries | Summary[] | Grouping brackets for siblings |
this.currentNodes | HTMLElement[] | Selected <me-tpc> elements |
this.scaleVal | number | Current zoom level (default: 1) |
this.dragMoveHelper | Object | Canvas panning helper from createDragMoveHelper() |
this.disposable | Function[] | Plugin cleanup functions |
| Function | Parameters | Returns | Purpose |
|---|---|---|---|
init(data) | MindElixirData | void | Initialize mind map from data object |
layout(instance) | MindElixirInstance | HTMLElement | Generate <me-root> DOM tree |
createWrapper(nodeObj) | NodeObj | HTMLElement | Create <me-wrapper> for single node |
shapeTpc(nodeObj) | NodeObj | HTMLElement | Create <me-tpc> content element |
linkDiv(instance) | MindElixirInstance | void | Draw SVG paths in layers |
Sources: src/index.ts19-133 src/types/index.ts75-239 src/methods.ts72-101 src/utils/pubsub.ts1-27 src/utils/layout.ts src/linkDiv.ts src/utils/createWrapper.ts src/utils/dom.ts src/mouse.ts
Initialization Flow Diagram:
The MindElixir constructor src/index.ts19-133 executes the following initialization steps:
Lines src/index.ts48-55 validate the container element:
Accepts options.el as either HTMLElement or CSS selector string. Throws error if resolution fails.
Lines src/index.ts60-86 initialize properties from Options interface src/types/index.ts134-169:
| Property | Default | Source Option |
|---|---|---|
this.locale | 'en' | options.locale |
this.newTopicName | 'New Node' | options.newTopicName |
this.direction | 1 (RIGHT) | options.direction |
this.draggable | true | options.draggable |
this.editable | true | options.editable |
this.contextMenu | true | options.contextMenu |
this.toolBar | true | options.toolBar |
this.keypress | true | options.keypress |
this.allowUndo | true | options.allowUndo |
this.scaleSensitivity | 0.1 | options.scaleSensitivity |
this.scaleMax | 1.4 | options.scaleMax |
this.scaleMin | 0.2 | options.scaleMin |
this.scaleVal | 1 | Internal state |
this.currentNodes | [] | Internal state |
this.currentArrow | null | Internal state |
this.tempDirection | null | Internal state |
Custom function options src/index.ts75-81:
this.generateMainBranch: Defaults to main from src/utils/generateBranch.tsthis.generateSubBranch: Defaults to sub from src/utils/generateBranch.tsthis.markdown: Optional markdown parser functionthis.imageProxy: Optional image URL transformerLines src/index.ts91-126 create the DOM hierarchy:
options.el (user-provided)
└── this.container (.map-container)
└── this.map (.map-canvas)
├── this.nodes (me-nodes element)
├── this.lines (svg#lines)
├── this.linkSvgGroup (svg#topiclinks)
├── this.summarySvg (svg#summary)
├── this.linkController (svg#linkcontroller)
│ ├── this.line1 (line element)
│ └── this.line2 (line element)
└── this.labelContainer (.label-container)
Bezier control point elements:
this.P2, this.P3: Divs with class circle, initially hiddenthis.bus = createBus() from src/utils/pubsub.ts1-27this.dragMoveHelper = createDragMoveHelper(this) from src/utils/dragMoveHelper.tsthis.disposable = [] for plugin cleanup functionsLines src/index.ts128-132 conditionally register mouse handlers:
If overflowHidden is false, calls initMouseEvent(this) from src/mouse.ts to register pointer, wheel, and touch event handlers on this.container.
Sources: src/index.ts19-133 src/types/index.ts134-169 src/utils/pubsub.ts1-27 src/utils/dragMoveHelper.ts src/mouse.ts src/utils/generateBranch.ts
The init(data) method src/methods.ts72-101 loads mind map data and triggers rendering:
Lines src/methods.ts72-75:
MindElixirData object to prevent external mutationsfillParent(nodeData) from src/utils/index.ts to populate bidirectional parent referencesthis.arrows array (defaults to [] if undefined)this.summaries array (defaults to [] if undefined)Lines src/methods.ts76-77:
data.direction is defined, assigns to this.directiondata.theme is defined, calls this.changeTheme(data.theme, false)Lines src/methods.ts78-90 conditionally install plugins based on options flags:
| Option Flag | Plugin Function | File |
|---|---|---|
this.toolBar === true | toolBar(this) | src/plugin/toolBar.ts |
this.keypress === true | keypress(this) | src/plugin/keypress.ts |
this.mouseSelectionButton !== -1 | selection(this) | src/plugin/selection.ts |
this.contextMenu !== false | contextMenu(this) | src/plugin/contextMenu.ts |
this.draggable === true | nodeDraggable(this) | src/plugin/nodeDraggable.ts |
this.allowUndo === true | operationHistory(this) | src/plugin/operationHistory.ts |
Each plugin receives the instance and may return a cleanup function.
Lines src/methods.ts91-94:
Calls layout(this) from src/utils/layout.ts which:
<me-root> element<me-wrapper> elements via createWrapper(nodeObj)Clears this.nodes and appends the generated root element.
Line src/methods.ts95:
Calls linkDiv(instance) from src/linkDiv.ts which:
this.arrows[] arraythis.summaries[] arrayLines src/methods.ts96-98:
Calls toCenter() from src/interact.ts to center the root node in the viewport.
Sources: src/methods.ts72-101 src/utils/index.ts src/utils/layout.ts src/linkDiv.ts src/interact.ts src/plugin/toolBar.ts src/plugin/keypress.ts src/plugin/selection.ts src/plugin/contextMenu.ts src/plugin/nodeDraggable.ts src/plugin/operationHistory.ts
The layout() function src/utils/layout.ts generates the complete DOM tree:
<me-root> container elementlayoutMainNode(nodeData) to create the central topic elementlayoutChildren(children, instance) for left and right sidescreateWrapper(nodeObj, instance)The DOM hierarchy generated:
<me-root>
<me-main class="lhs">...</me-main> (if direction = LEFT or SIDE)
<me-main class="rhs">...</me-main> (if direction = RIGHT or SIDE)
</me-root>
Each main side contains <me-wrapper> elements generated by createWrapper().
The createWrapper() function src/utils/createWrapper.ts creates the structure for one node:
<me-wrapper>
<me-parent>
<me-tpc> (from shapeTpc)
<me-expander>∨</me-expander> (if has children)
<me-emoji>icon</me-emoji> (if icons exist)
<me-text>topic</me-text>
<me-tags>...</me-tags> (if tags exist)
</me-tpc>
</me-parent>
<me-children> (recursively created children)
<me-wrapper>...</me-wrapper>
<me-wrapper>...</me-wrapper>
</me-children>
</me-wrapper>
Calls shapeTpc(nodeObj) from src/utils/dom.ts to populate the <me-tpc> element with topic content, icons, tags, and hyperlink.
The linkDiv() function src/linkDiv.ts renders SVG paths:
instance.generateMainBranch(params) for main level connectionsinstance.generateSubBranch(params) for deeper level connectionsmain() and sub() from src/utils/generateBranch.tsinstance.arrows[], calls drawArrow(arrow) from src/arrow.tsinstance.summaries[], calls drawSummary(summary) from src/summary.tsSVG path generation delegates to:
generateMainBranch(): Returns SVG path string for main branchesgenerateSubBranch(): Returns SVG path string for sub-branchesBoth can be customized via options.generateMainBranch and options.generateSubBranch.
Sources: src/utils/layout.ts src/utils/createWrapper.ts src/utils/dom.ts src/linkDiv.ts src/arrow.ts src/summary.ts src/utils/generateBranch.ts
Event Bus Architecture Diagram:
The event bus src/utils/pubsub.ts provides pub/sub communication between components. Created by createBus() src/utils/pubsub.ts during constructor initialization src/index.ts89 it exposes:
Implementation uses a Map<string, Function[]> to store listeners. When fire() is called, all registered callbacks for that event name are invoked synchronously with the provided data.
'operation' event: Fired by data modification functions src/nodeOperation.ts
| Operation | Event Payload | Fired By |
|---|---|---|
addChild | {name: 'addChild', obj: parentNode} | addChild(parent) |
insertSibling | {name: 'insertSibling', obj: siblingNode} | insertSibling(sibling, direction) |
insertParent | {name: 'insertParent', obj: childNode} | insertParent(node) |
removeNode | {name: 'removeNode', obj: deletedNode} | removeNodes(nodes) |
moveNodeBefore | {name: 'moveNodeBefore', obj: {from, to}} | moveNodeBefore(target, before) |
moveNodeAfter | {name: 'moveNodeAfter', obj: {from, to}} | moveNodeAfter(target, after) |
moveNodeIn | {name: 'moveNodeIn', obj: {from, to}} | moveNodeIn(target, parent) |
createArrow | {name: 'createArrow', obj: arrowObject} | createArrow(from, to) src/arrow.ts |
removeArrow | {name: 'removeArrow', obj: arrowObject} | removeArrow(arrow) src/arrow.ts |
'selectNodes' / 'unselectNodes': Fired by selection handlers src/mouse.ts
'expandNode': Fired when node expand/collapse toggles src/interact.ts
'changeTheme': Fired when theme is applied src/utils/theme.ts
'changeDirection': Fired when layout direction changes src/interact.ts
Application code subscribes to events for side effects:
The event bus decouples operation execution from side effects. The operationHistory plugin src/plugin/operationHistory.ts uses this pattern to implement undo/redo by listening to 'operation' events and capturing state snapshots.
Sources: src/utils/pubsub.ts src/index.ts89 src/nodeOperation.ts src/mouse.ts src/interact.ts src/arrow.ts src/utils/theme.ts src/plugin/operationHistory.ts
The following tables document the API methods available on MindElixirInstance:
| Method | Parameters | Returns | Description |
|---|---|---|---|
init(data) | MindElixirData | void | Initialize mind map from data object |
getData() | None | MindElixirData | Export current state as serializable object |
getDataString() | None | string | Export as JSON string |
refresh(data) | MindElixirData | void | Replace entire mind map with new data |
Data structures defined in src/types/index.ts75-239:
MindElixirData: Top-level object containing nodeData, arrows, summaries, direction, themeNodeObj: Single node with properties: id, topic, children[], style, tags, icons, hyperLink, image, parent| Method | Parameters | Returns | Event Fired |
|---|---|---|---|
addChild(parent) | NodeObj or HTMLElement | NodeObj | 'operation' with name: 'addChild' |
insertSibling(sibling, direction) | NodeObj/HTMLElement, number | NodeObj | 'operation' with name: 'insertSibling' |
insertParent(node) | NodeObj or HTMLElement | NodeObj | 'operation' with name: 'insertParent' |
removeNodes(nodes) | NodeObj[] | void | 'operation' with name: 'removeNode' |
moveNodeBefore(target, before) | NodeObj, NodeObj | void | 'operation' with name: 'moveNodeBefore' |
moveNodeAfter(target, after) | NodeObj, NodeObj | void | 'operation' with name: 'moveNodeAfter' |
moveNodeIn(target, parent) | NodeObj, NodeObj | void | 'operation' with name: 'moveNodeIn' |
All node operations are implemented in src/nodeOperation.ts
| Method | Parameters | Description |
|---|---|---|
updateNodeTags(node, tags) | NodeObj, string[] | Replace tags array |
updateNodeIcons(node, icons) | NodeObj, string[] | Replace icons array |
updateNodeHyperLink(node, link) | NodeObj, string | Set hyperlink URL |
updateNodeImage(node, image) | NodeObj, ImageObj | Set image with url, width, height |
Image format src/types/index.ts192-239:
| Method | Parameters | Description | File |
|---|---|---|---|
scale(scaleVal, originX?, originY?) | number, number?, number? | Zoom to scale value | src/interact.ts |
move(deltaX, deltaY) | number, number | Pan canvas by offset | src/interact.ts |
toCenter() | None | Center root node in viewport | src/interact.ts |
focusNode(node) | NodeObj | Enter focus mode on node | src/interact.ts |
cancelFocus() | None | Exit focus mode | src/interact.ts |
selectNode(el, notCentered?) | HTMLElement, boolean? | Select node element | src/interact.ts |
unselectNode() | None | Clear selection | src/interact.ts |
expandNode(el, isExpand?) | HTMLElement, boolean? | Toggle node expansion | src/interact.ts |
Layout direction constants src/const.ts1-3:
MindElixir.LEFT = 0MindElixir.RIGHT = 1MindElixir.SIDE = 2| Method | Parameters | Description |
|---|---|---|
changeTheme(theme, refresh?) | Theme, boolean? | Apply theme with palette and CSS variables |
Theme structure src/types/index.ts56-62:
Pre-defined themes src/const.ts5-35:
MindElixir.THEME: Light themeMindElixir.DARK_THEME: Dark theme| Method | Parameters | Returns | Description |
|---|---|---|---|
createArrow(from, to) | NodeObj, NodeObj | Arrow | Create custom connection between nodes |
removeArrow(arrow) | Arrow | void | Delete arrow |
showArrow(arrow) | Arrow | void | Make arrow visible |
hideArrow(arrow) | Arrow | void | Make arrow hidden |
createSummary(nodes) | NodeObj[] | Summary | Create grouping bracket for siblings |
removeSummary(summary) | Summary | void | Delete summary |
selectSummary(summary) | Summary | void | Select summary for editing |
Arrow and Summary types defined in src/types/index.ts47-54
Sources: src/types/index.ts75-239 src/methods.ts src/nodeOperation.ts src/interact.ts src/arrow.ts src/summary.ts src/const.ts1-35 src/utils/theme.ts
Mind Elixir provides multiple ways to extend and customize functionality:
Plugin Architecture:
Plugin Lifecycle:
mind.install(plugin) calls the plugin function with instance referencedisposable[] arraymind.destroy()Built-in Plugins:
keypress: Keyboard shortcuts (Tab, Enter, Delete, etc.)contextMenu: Right-click menu with configurable optionsnodeDraggable: Drag-and-drop node reorderingselection: Multi-node selection with rectangle tooloperationHistory: Undo/redo functionalitytoolBar: UI toolbar with common actionsThe before.* hooks src/types/index.ts21-23 allow async validation before operations:
If a hook returns false or a Promise that resolves to false, the operation is cancelled. Available hooks match all operation names in src/nodeOperation.ts
Markdown Parser src/types/index.ts162:
Image Proxy src/types/index.ts168:
Custom Branch Generators src/types/index.ts148-149:
Subscribe to events for custom behavior:
Sources: src/methods.ts55-129 src/types/index.ts21-23 src/types/index.ts134-169 src/plugin/keypress.ts src/plugin/operationHistory.ts src/utils/pubsub.ts
Plugins extend Mind Elixir's functionality through a standardized lifecycle:
Installation: mind.install(plugin) invokes the plugin function with the instance reference.
Initialization: Built-in plugins are conditionally initialized based on Options flags (keypress, contextMenu, toolBar, etc.). User plugins can be installed manually.
Event Registration: Plugins register listeners on mind.bus to react to events like 'operation', 'selectNodes', 'expandNode'.
Cleanup: Plugins return cleanup functions that are stored in mind.disposable[] and invoked by mind.destroy().
This architecture enables features like undo/redo, keyboard shortcuts, and context menus to be implemented as plugins rather than baked into the core, allowing for a lightweight "lite" build that excludes them.
Sources: src/index.ts128-132 package.json34-36
Refresh this wiki