Skip to main content

@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

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

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

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

ts
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

APIPurpose
engine.pluginsFeature APIs installed through plugins.
engine.$cameraSubscribable camera state.
engine.$nodesSubscribable read-only map of nodes.
engine.$selectionSubscribable read-only set of selected node IDs.
engine.$interactionSubscribable current pointer/editing interaction state.
engine.$snapGuidesSubscribable 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

ts
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

APIPurpose
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

APIPurpose
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

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

APIPurpose
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, and isBoardColorPreset
  • id helpers such as asNodeId and asEdgeId
  • selection helpers such as getSelectionNodes, getSelectionBounds, and toggleIds
  • 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.

ts
clamp(value: number, min: number, max: number): number
ts
clamp(15, 0, 10) // 10
clamp(-5, 0, 10) // 0

Bounds logic

boundsIntersect

Returns true if two bounds overlap.

ts
boundsIntersect(a: Bounds, b: Bounds): boolean

getBoundsFromPoints

Creates a Bounds from two arbitrary points. Minimum and maximum corners are computed automatically.

ts
getBoundsFromPoints(a: Point, b: Point): Bounds
ts
const 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.

ts
getVisibleBounds(width: number, height: number, camera: Camera): Bounds
ts
const bounds = getVisibleBounds(1280, 720, camera)