@lupinum/board-core
The supported public API for the headless board engine.
@lupinum/board-core exports the engine factory, public board types, JSON Canvas
document types, color helpers, and selection helpers.
It does not expose transaction roots, plugin persistence hooks, or history internals as stable consumer API.
The package also publishes @lupinum/board-core/internal so first-party
packages such as @lupinum/board-history and @lupinum/board-connections can
compose with the core engine after they are installed from npm. That subpath is
the first-party feature ABI, not an application plugin API; app code should use
the top-level @lupinum/board-core entrypoint and install supported features
through plugins.
createBoardEngine
import { createBoardEngine } from '@lupinum/board-core'
const engine = createBoardEngine({
grid: { size: 24, snap: true },
onUnhandledError(error, context) {
reportError(error, context)
},
})Listener, reactive subscriber, and finalized plugin commit-effect failures are
reported through onUnhandledError after a commit; they cannot roll committed
state back. Inspect context.source to distinguish event-listener,
subscriber, and commit-effect failures. Without a hook, the engine falls
back to console.error.
Install first-party packages with plugins.
const engine = createBoardEngine({
plugins: [historyPlugin(), connectionsPlugin()],
})Plugin names must be unique. Construction throws BoardConflictError before
any plugin installs when the tuple contains a duplicate name.
Nodes
Nodes use JSON Canvas fields directly.
engine.createNode({
type: 'text',
x: 120,
y: 80,
width: 240,
height: 120,
text: 'Release plan',
})Supported node fields are text, file, subpath, url, label,
background, and backgroundStyle. There is no legacy data compatibility layer.
Persistence
Use exportDocument() and loadDocument() for persisted documents.
const document = engine.exportDocument()
engine.loadDocument(document, { mode: 'replace' })Core persists nodes and board metadata. Edges belong to
@lupinum/board-connections; importing a document with edges without that
connections package installed fails instead of dropping edge data.
Engine reference
The engine is the public mutation boundary. App code should use these methods instead of editing node maps, selection sets, or camera objects directly.
State and lifecycle
| API | Purpose |
|---|---|
engine.plugins | Feature APIs installed through plugins. |
engine.$camera | Subscribable camera state. |
engine.$nodes | Subscribable read-only map of nodes. |
engine.$selection | Subscribable read-only set of selected node IDs. |
engine.$interaction | Subscribable current pointer/editing interaction state. |
engine.$snapGuides | Subscribable active snap guides. |
destroy() | Tear down engine subscriptions and resources. |
batch(fn) | Run multiple commands while deferring subscribable notifications. |
getState() | Read the current board state. |
getGridSettings() | Read resolved grid settings. |
updateGridSettings(patch) | Update grid settings and return the resolved settings. |
getViewportSize() | Read the last viewport size reported by the renderer. |
setViewportSize(size) | Update viewport size. Renderers call this from resize observation. |
exportTrace() | Read command trace entries. |
addCommandGuard(fn) | Register a synchronous command guard for concrete product policy. |
Events
const unsubscribe = engine.on('node:created', (node) => {
console.log(node.id)
})
engine.once('destroy', () => {
console.log('engine destroyed')
})
engine.off('node:created', handler)
unsubscribe()See events and subscriptions for the full event catalog and payloads.
Coordinate and camera methods
| API | Purpose |
|---|---|
screenToWorld(point) | Convert a screen-space point to world coordinates. |
worldToScreen(point) | Convert a world-space point to screen coordinates. |
getVisibleBounds(width, height) | Compute visible world bounds for a viewport size. |
panBy(dx, dy) | Move the camera by a world-space delta. |
panTo(worldPoint, animated?) | Pan to a world point, optionally animated. |
zoomAt(screenPoint, delta) | Zoom around a screen-space point. |
zoomTo(level, animated?) | Zoom to an absolute level, clamped by zoom settings. |
zoomToFit(padding?, animated?) | Fit all visible nodes into the viewport. |
zoomToNodes(ids, padding?, animated?) | Fit specific nodes into the viewport. |
Node methods
| API | Purpose |
|---|---|
getNode(id) | Return a node or throw if it does not exist. |
findNode(id) | Return a node or null. |
hasNode(id) | Check whether a node exists. |
getNodeAt(worldPoint) | Return the topmost visible node at a world point. |
getNodesInBounds(bounds) | Return visible nodes intersecting the bounds. |
createNode(input) | Create a node and select it unless select: false is passed. |
updateNode(id, patch) | Update node fields. |
deleteNode(id) | Delete a node. Deleting a group also deletes descendants. |
moveNode(id, dx, dy) | Move one node by a world-space delta. |
translateSelectedNodes(dx, dy) | Move the current selection, respecting group hierarchy. |
resizeNode(id, handle, dx, dy) | Resize one node from a compass handle. |
bringToFront(id) | Move a node above other nodes. |
sendToBack(id) | Move a node behind other nodes. |
lockNode(id) | Prevent interactive move, resize, and delete. |
unlockNode(id) | Re-enable interactive changes. |
duplicateNodes(ids, offset?) | Duplicate nodes and preserve supported hierarchy between copies. |
Selection and clipboard methods
| API | Purpose |
|---|---|
select(ids, mode?) | Replace, add, remove, or toggle selected node IDs. |
selectAll() | Select all visible nodes. |
clearSelection() | Clear the selection. |
deleteSelected() | Delete unlocked selected nodes and group descendants. |
getSelection() | Return selected node IDs as an array. |
copySelected() | Copy selected nodes into the engine clipboard. |
pasteClipboard(offset?) | Paste nodes from the engine clipboard. |
The engine clipboard is internal to the engine. It does not read from or write to the system clipboard.
Text editing and document methods
Pointer interactions are intentionally framework-internal. Text editing remains public so custom renderers can provide their own editors.
| API | Purpose |
|---|---|
beginTextEdit(id) | Enter text-editing mode for a text node. |
commitTextEdit(id, text?) | Commit text editing and update the text node. |
cancelTextEdit() | Leave text editing without changing content. |
exportDocument() | Return a typed JSON Canvas document. |
loadDocument(document, options?) | Validate and atomically load with replace or merge. |
Public helpers
The public helpers are the helpers consumers need to build boards:
- color helpers such as
BOARD_COLOR_PRESETS,colorForPreset, andisBoardColorPreset - id helpers such as
asNodeIdandasEdgeId - selection helpers such as
getSelectionNodes,getSelectionBounds, andtoggleIds - geometry helpers used by renderers and overlays
Math helpers
- These utility functions are exported from
@lupinum/board-core. They are pure functions and can be used outside the engine for custom calculations.
The engine also exposes instance methods such as engine.screenToWorld() and engine.worldToScreen() when the calculation depends on the current board camera.
General math
clamp
Constrains a value to a range.
clamp(value: number, min: number, max: number): numberclamp(15, 0, 10) // 10
clamp(-5, 0, 10) // 0Bounds logic
boundsIntersect
Returns true if two bounds overlap.
boundsIntersect(a: Bounds, b: Bounds): booleangetBoundsFromPoints
Creates a Bounds from two arbitrary points. Minimum and maximum corners are computed automatically.
getBoundsFromPoints(a: Point, b: Point): Boundsconst bounds = getBoundsFromPoints({ x: 10, y: 20 }, { x: 50, y: 5 })
// { minX: 10, minY: 5, maxX: 50, maxY: 20 }getVisibleBounds
Returns the world-space bounds visible within a viewport for the given camera.
getVisibleBounds(width: number, height: number, camera: Camera): Boundsconst bounds = getVisibleBounds(1280, 720, camera)