@ankorar/nodex
v0.1.0
Published
Composable React package for building and extending a keyboard-driven mind map experience.
Readme
@ankorar/nodex
Composable React package for building and extending a keyboard-driven mind map experience.
This package exposes state, hooks, and UI primitives so each consumer app can build its own screen composition without being locked to a single page component.
What This Package Provides
- Stateful mind map engine (Zustand-based)
- Ready-to-compose visual components (board, nodes, minimap, popovers)
- Keyboard and interaction handlers
- Debounce hooks for history/state flow
- Type exports for consumer-side extensions
Installation
Workspace (monorepo)
pnpm add @ankorar/nodex --workspaceFuture npm install
pnpm add @ankorar/nodexQuick Usage (Composition)
import "@ankorar/nodex/styles.css";
import {
Background,
Board,
MindMapHeader,
MineMap,
Nodex,
ZenCard,
useMindMapDebounce,
} from "@ankorar/nodex";
export function MindMapPage() {
useMindMapDebounce(() => undefined, { delayMs: 3000 });
return (
<section className="h-[calc(100dvh-11rem)] min-h-[38rem]">
<Nodex readOnly={false}>
<MindMapHeader title="Mind Map" />
<Board>
<Background />
<MineMap />
<ZenCard />
</Board>
</Nodex>
</section>
);
}Read-only Mode
Use Nodex with readOnly when the map should be visual-only:
<Nodex readOnly>
<MindMapHeader title="Mind Map (View)" />
<Board>
<Background />
<MineMap />
<ZenCard />
</Board>
</Nodex>In readOnly mode, the package blocks content mutations (keyboard shortcuts, inline edits, add/remove node actions, and style popover updates).
High-quality image export
You can export the whole map as a high-resolution PNG so that text stays readable when zooming, even on very large maps.
- From the header: set
showExportImageButtononMindMapHeaderto show an “Export image” button that downloads a PNG (scale factor 3× by default). - Programmatic: use
exportMindMapAsHighQualityImage(nodes, options?)with optionalscale(1–4) andfilename. Exported image dimensions are derived from node bounds plus padding, then multiplied byscale.
import { MindMapHeader, exportMindMapAsHighQualityImage, useMindMapState } from "@ankorar/nodex";
// In your page:
<MindMapHeader title="My Map" showExportImageButton />
// Or call manually:
const nodes = useMindMapState.getState().getFlatNodes();
await exportMindMapAsHighQualityImage(nodes, { scale: 3, filename: "my-map" });Initial State
@ankorar/nodex now starts with an empty node list (nodes: []).
If your app loads maps from an API, hydrate the state explicitly:
import { useEffect } from "react";
import { useMindMapState, type MindMapNode } from "@ankorar/nodex";
export function MindMapHydrator({ nodes }: { nodes: MindMapNode[] }) {
useEffect(() => {
useMindMapState.setState({
nodes,
selectedNodeId: null,
editingNodeId: null,
});
}, [nodes]);
return null;
}Custom nodes (persisted)
You can add custom node types that are stored and restored with the map.
- Set
type: "custom"andcustomType: string(e.g."note") on a node. UsecustomPayload?: unknownfor app-specific data (e.g.{ noteId: "..." }). - These fields are part of
MindMapNodeand are persisted when your app saves the node tree (e.g. ascontentin your map API). When you load the map, pass the same tree into state so custom nodes keep theircustomTypeandcustomPayload. - Register a React component per
customTypevia thecustomNodeRenderersprop onNodex:customNodeRenderers={{ note: NoteNodeComponent }}. The component receivesCustomNodeProps(node, style slots). Custom nodes are not auto-resized by layout (they keep the dimensions you set).
<Nodex customNodeRenderers={{ note: NoteNode }}>
<Board>…</Board>
</Nodex>Styling
@ankorar/nodex ships its own precompiled stylesheet, so consumer apps do not need to compile Tailwind classes from this package.
Import it once at app entry:
import "@ankorar/nodex/styles.css";Public API
Components
Nodex(optional:customNodeRenderers,nodeEditorCustomButtons,newNodesTextColor)BoardBackgroundNodesSegmentsCentalNodeDefaultNodeImageNodeNodeStylePopoverKeyboardHelpDialogMineMapZenCardMindMapHeader(optional:showExportImageButtonfor PNG export)
Utilities
getMindMapPreviewDataUrl(nodes)– data URL for minimap-style preview thumbnailsexportMindMapAsHighQualityImage(nodes, options?)– render map to high-resolution PNG and trigger downloadHIGH_QUALITY_EXPORT_SCALE– default scale factor (3) for export
Hooks
useMindMapDebounceuseMindMapHistoryDebounceuseMindMapNode(for custom node components: selection, addChild, destroy, getSide)useMindMapNodeMouseHandlersuseCustomNodeRenderers
State
useMindMapStateuseMindMapHistorycreateMindMapSnapshot
Types
MindMapNode(includes optionalcustomType,customPayloadfor custom nodes)MindMapNodeStyleMindMapNodeType("default" | "central" | "image" | "custom")MindMapNodeTextAlignMindMapNodeFontSizeCustomNodeProps,CustomNodeRenderers(for custom node components)
Package Structure
src/
components/
mindMap/
ui/
config/
handlers/
helpers/
hooks/
mindMap/
lib/
state/
index.tsDevelopment
From the monorepo root:
pnpm --filter @ankorar/nodex devAvailable scripts in this package:
dev: watches types and regeneratesstyles.cssbuild: type validation + stylesheet buildlint: Type validation (tsc --noEmit)
Consumer Responsibilities
Because this package is composition-first, the consumer app is responsible for:
- Defining the route/page shell
- Providing app-level layout and authentication wrappers
- Loading global styles/tokens expected by the selected UI setup
Versioning and Publishing
Before publishing a new version:
- Validate exports in
src/index.ts. - Run package checks:
pnpm --filter @ankorar/nodex buildpnpm --filter @ankorar/nodex lint
- Update docs for any API/behavior change.
- Bump package version in
packages/nodex/package.json.
Documentation Policy
All package documentation must be written in English.
For mandatory documentation rules (including AI/chat workflows), see:
docs/AI_DOCUMENTATION_POLICY.md
