Skip to main content

@lupinum/board-history

Deterministic structural undo/redo with connection-aware state restoration.

Install

pnpm add @lupinum/board-history @lupinum/board-core
npm install @lupinum/board-history @lupinum/board-core

historyPlugin

Creates the history plugin. Install it during engine creation via createBoardEngine({ plugins }).

ts
const plugin = historyPlugin({ maxSteps: 100 })

Options

NameTypeDefaultDescription
maxStepsnumber200Maximum number of undo steps to retain.
ts
import { createBoardEngine } from '@lupinum/board-core'
import { historyPlugin } from '@lupinum/board-history'

const engine = createBoardEngine({
  plugins: [historyPlugin({ maxSteps: 100 })],
})

Command capture

History capture is driven by command metadata emitted by the engine and internal features. Camera movement, transient pointer setup/teardown, text-edit begin, imports, and selection-only commands mark themselves as ignored; geometry and content mutations mark themselves as recorded.


Plugin API

After installing the plugin, the history API is available on engine.plugins.history.

undo

Restores the previous state. No-op if there is nothing to undo.

ts
undo(): void
ts
engine.plugins.history.undo()

redo

Re-applies the next state. No-op if there is nothing to redo.

ts
redo(): void

canUndo

Returns true if there is at least one undo step available.

ts
canUndo(): boolean

canRedo

Returns true if there is at least one redo step available.

ts
canRedo(): boolean

clear

Clears the entire history stack (both undo and redo).

ts
clear(): void

getState

Returns the current history state.

ts
getState(): HistoryState
ts
const state = engine.plugins.history.getState()
console.log(`${state.undoDepth} undo steps, ${state.redoDepth} redo steps`)

History boundaries

History has no timers or implicit coalescing. A completed gesture, text edit, or outer batch() call creates one step. Separate programmatic commands create separate steps.

BoardHistoryShortcuts

Import BoardHistoryShortcuts from @lupinum/board-history/vue and render it inside BoardRoot with <BoardHistoryShortcuts :history="engine.plugins.history" />. The required prop identifies the history API, while the enclosing root identifies which DOM events it may handle. Editable controls and events already handled by another component keep their native behavior.


Connection-aware restoration

When the connections plugin is also installed, the history plugin automatically captures and restores edge state. Undoing a node deletion also restores any edges that were connected to that node.


Types

HistoryState

ts
interface HistoryState {
  undoDepth: number // number of undo steps available
  redoDepth: number // number of redo steps available
  current: string | null // label of the current history entry
}

HistoryEntry

ts
interface HistoryEntry {
  label: string // command name that created this entry
  timestamp: number // Date.now() when the entry was created
}

HistoryPluginOptions

ts
interface HistoryPluginOptions {
  maxSteps?: number // default: 200
}

Events

The history plugin adds these events to BoardEventMap:

EventHandlerDescription
history:push(entry: HistoryEntry) => voidA new entry was added to the undo stack.
history:undo(entry: HistoryEntry | null) => voidAn undo operation was performed.
history:redo(entry: HistoryEntry | null) => voidA redo operation was performed.
history:clear() => voidThe history stack was cleared.
ts
engine.on('history:push', (entry) => {
  console.log('New undo step:', entry.label)
})

engine.on('history:undo', () => {
  console.log('Undo depth:', engine.plugins.history.getState().undoDepth)
})