Skip to main content

@lupinum/vue-board

Vue 3 components for rendering interactive boards.

Install

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

BoardRoot

The root component that renders the board. It subscribes to the engine, handles all pointer/keyboard events, and provides context to child components and composables.

Props

NameTypeDefaultDescription
engineBoardEngineAuto-createdThe board engine instance. If omitted, a default engine is created internally.
cullMarginnumber200World-space margin around the viewport for node culling. Nodes outside this margin are not rendered.
gridboolean | BoardGridOptionstrueGrid configuration. true uses engine defaults, false hides the grid, or pass an object for fine control.
selectionToolbarbooleantrueShow the bundled selection toolbar.
snapGuidesbooleantrueShow alignment guides during drag and resize.
boxSelectbooleantrueShow the bundled box-selection overlay.
renderersBoardRendererRegistry{}Maps supported JSON Canvas node types to Vue components for custom rendering.
fallbackRendererComponent | nullnullFallback renderer used when no type-specific renderer or slot matches.

Treat the engine instance as stable after mount. To replace board contents, keep the same engine and load the new document through engine commands such as loadDocument.

Emits

EventPayloadDescription
readyengine: BoardEngineFired after the root element is mounted and the viewport is measured.

Slots

default

The default slot renders after the board viewport, on top of the canvas. It receives the engine, runtime state, and debug state.

vue
<BoardRoot :engine="engine">
  <template #default="{ engine, state, debugState }">
    <MyToolbar :engine="engine" />
    <MyMinimap />
  </template>
</BoardRoot>
PropTypeDescription
engineBoardEngineThe engine instance.
stateBoardStateCurrent immutable runtime state.
debugStateobjectDebug info: camera, grid, selection, interaction, visibleNodeCount, last 20 trace entries.

viewport

Renders inside the viewport transform layer (world-space). Use this for custom overlays that should move with the canvas.

vue
<BoardRoot :engine="engine">
  <template #viewport>
    <MyWorldOverlay />
  </template>
</BoardRoot>

node

Fallback slot for node content when no type-specific slot or renderer matches.

vue
<BoardRoot :engine="engine">
  <template #node="{ node, selected, editing, beginEdit, commitText }">
    <div>{{ node.text }}</div>
  </template>
</BoardRoot>
PropTypeDescription
nodeBoardNodeThe node being rendered.
selectedbooleanWhether the node is selected.
editingbooleanWhether the node is in text editing mode.
beginEdit() => voidStart editing for a text node; otherwise no-op.
commitText(text: string) => voidCommit a text-node edit; otherwise no-op.

node:{type}

Type-specific named slot. Takes precedence over the node slot and renderer registry.

vue
<BoardRoot :engine="engine">
  <template #node:file="{ node, selected }">
    <img :src="node.file" :alt="node.label" />
  </template>
  <template #node:text="{ node }">
    <p>{{ node.text }}</p>
  </template>
</BoardRoot>

handle

Custom resize handle slot. Rendered for each of the 8 compass handles when a node is selected.

vue
<BoardRoot :engine="engine">
  <template #handle="{ node, handle }">
    <div class="my-handle" :data-resize="handle">●</div>
  </template>
</BoardRoot>

box-select

Custom box selection overlay.

vue
<BoardRoot :engine="engine">
  <template #box-select="{ bounds }">
    <div class="my-box-select" :style="boxStyle(bounds)" />
  </template>
</BoardRoot>

CSS custom properties

Set these on .board-root to theme the entire board:

PropertyDefaultDescription
--board-bg#fbfbfdBoard UI background token.
--board-canvas-bg#f5f6faCanvas background color.
--board-fg#14161fBoard foreground / text color.
--board-node-bg#fffNode background color.
--board-node-border#e4e5eeNode border color.
--board-node-colorunsetResolved border color for a node with node.color.
--board-node-tintunsetResolved tinted background for a node with node.color.
--board-node-color-softunsetSofter resolved color for group borders and subtle UI surfaces.
--board-node-color-ringunsetResolved selection outline color for a node with node.color.
--board-accent#6366e8Accent color (snap guides, selections).
--board-edge-colorrgba(91, 96, 117, 0.72)Default edge color.
--board-handle-shadowrgba(9, 14, 28, 0.1)Shadow on resize handles.
--board-zoom(auto)Set automatically by BoardViewport to the current zoom level.
css
/* Dark theme example */
.board-root {
  --board-bg: #1e293b;
  --board-fg: #f1f5f9;
  --board-node-bg: #334155;
  --board-node-border: rgba(241, 245, 249, 0.15);
  --board-accent: #22d3ee;
}

Level of detail (LOD)

BoardRoot automatically adjusts rendering based on screen-space size:

LODConditionRendering
fullSelected, or screen size >= 96pxFull node content, handles, custom renderers.
simpleScreen size 6–96pxMinimal placeholder that preserves color and group shape.
hiddenScreen size < 6pxNot rendered at all.

Keyboard shortcuts

BoardRoot handles these keyboard shortcuts out of the box:

KeyAction
EscapeClear selection and end interaction.
Delete / BackspaceDelete selected nodes.
EnterBegin text editing on the selected text node.
Ctrl/Cmd + ASelect all visible nodes.
Ctrl/Cmd + DDuplicate selected nodes.
Ctrl/Cmd + CCopy selected nodes.
Ctrl/Cmd + VPaste from clipboard.
Ctrl/Cmd + 0Zoom to 100%.
Ctrl/Cmd + 1Zoom to fit all nodes.
Ctrl/Cmd + ZUndo (requires history plugin).
Ctrl/Cmd + Shift + Z / Ctrl/Cmd + YRedo (requires history plugin).
Arrow keysNudge selected nodes by grid step.
Shift + Arrow keysNudge by major grid step.
Alt/Option + dragDuplicate the current selection and drag the clone.
Shift + dragLock dragging to the dominant axis.
Space while dragging/resizingTemporarily bypass snapping while the pointer is active.
Space + drag from idlePan the canvas.
Middle mouse dragPan the canvas.
Scroll wheelPan the canvas.
Shift + scrollPan horizontally.
Ctrl/Cmd + scrollZoom at cursor.
Space + scrollZoom at cursor.
Double-click empty areaCreate a new text node.
Double-click text nodeBegin text editing.
Pinch (touch)Zoom in/out.

BoardSelectionToolbar

Floating toolbar rendered by BoardRoot for selected nodes. It is also exported for custom board shells.

The toolbar appears above the selected node bounds when the board is idle. It hides during node drag, resize, and text editing.

Built-in actions:

ActionBehavior
RemoveDeletes unlocked selected nodes.
Set colourOpens the preset palette and applies the chosen color to every unlocked selected node.
Zoom to selectionFits the selected nodes in the viewport.
EditStarts text editing for a single selected text node.

Palette options apply BoardColorPreset IDs, which are one supported form of BoardNode.color.


Node color helpers

@lupinum/vue-board exports the shared color helpers for custom renderers.

ts
import {
  BOARD_COLOR_PRESETS,
  colorForPreset,
  resolveNodeColorStyle,
} from '@lupinum/vue-board'

Use resolveNodeColorStyle(node.color) when custom wrappers need the same CSS variables as the built-in renderer.

vue
<template>
  <article class="task-card" :style="resolveNodeColorStyle(node.color)">
    {{ node.text }}
  </article>
</template>

resolveNodeColorStyle() returns an empty style object when the node has no color.


BoardViewport

Applies the camera transform (scale + translate) to child content. All children render in world-space coordinates. Used internally by BoardRoot.

CSS

css
.board-viewport {
  position: absolute;
  inset: 0;
  transform-origin: 0 0;
  will-change: transform;
}

BoardNode

Renders a single node with selection state, editing support, resize handles, and slot-based content.

Props

NameTypeDefaultDescription
nodeBoardNoderequiredThe node data to render.
selectedbooleanrequiredWhether the node is currently selected.
editingbooleanrequiredWhether the node is in text editing mode.
customRendererbooleanundefinedHint that a custom renderer is provided. Falls back to slot detection when omitted.

Slots

default

Custom node content. When provided, replaces the built-in text display.

PropTypeDescription
nodeBoardNodeThe node data.
selectedbooleanSelection state.
editingbooleanEditing state.
beginEdit() => voidStart editing for a text node; otherwise no-op.
commitText(text: string) => voidCommit a text-node edit; otherwise no-op.

handle

Custom resize handle. Rendered for each of the 8 compass directions when the node is selected and not editing or locked.

PropTypeDescription
nodeBoardNodeThe node data.
handleResizeHandleWhich handle ('n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw').

CSS classes

ClassConditionDescription
.board-nodeAlwaysBase node styling.
.is-selectedselected === trueSelection border and ring styling.
.is-editingediting === trueText editing mode.
.is-lockednode.locked === trueLocked state hook for custom styling.
.is-groupnode.type === 'group'Group frame styling.

Built-in text editing

When no custom renderer is provided and the node type is 'text', BoardNode renders:

  • A <textarea> when editing is true (auto-focused, auto-selected)
  • A <div> with the content text when not editing

The textarea commits on blur, Cmd/Ctrl + Enter, and cancels on Escape.


BoardNodeHandle

Renders a single resize handle square at the appropriate position on a node.

Props

NameTypeDescription
handleResizeHandleWhich handle to render ('n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw').

The default handle uses an 18px hit target and a 7px visible dot, both scaled by 1 / zoom. It uses data-resize attributes for pointer event detection.


BoardGrid

Renders the grid pattern as CSS background gradients. Supports dot, line, cross, and none patterns. Automatically adjusts opacity based on zoom level.

The grid fades at edges when fadeEdges is enabled (via CSS mask-image).


BoardBoxSelect

Renders the box selection rectangle during a box-select interaction. The default overlay uses a solid blue window style for contain-mode selection and a dashed teal crossing style for intersecting selection.

Slots

default

Custom box selection rendering.

PropTypeDescription
boundsBoundsScreen-space bounds of the selection rectangle.
mode'window' | 'crossing'Active box-select mode for custom styling.

BoardSnapGuides

Renders alignment snap guides during drag and resize operations. Converts world-space guide data to screen-space lines using the current camera transform.

  • Vertical guides (x-axis) render as 1px wide lines in --board-snap-guide-color, falling back to --board-accent.
  • Horizontal guides (y-axis) render as 1px tall lines in --board-snap-guide-color, falling back to --board-accent.

Types

BoardGridOptions

Presentation-only grid options passed to the grid prop on BoardRoot. Configure size, snapping, thresholds, and pattern through createBoardEngine({ grid }) or engine.updateGridSettings() so rendering and interaction use one canonical value.

ts
interface BoardGridOptions {
  visible?: boolean // show/hide the grid (default: true)
  minorOpacity?: number // minor grid line opacity (default: 0.14)
  majorOpacity?: number // major grid line opacity (default: 0.18)
  fadeEdges?: boolean // fade grid at viewport edges (default: true)
}

BoardRendererRegistry

Maps supported JSON Canvas node types to Vue components.

ts
type BoardRendererRegistry = Partial<Record<JsonCanvasNodeType, Component>>
ts
import TextCard from './TextCard.vue'
import GroupCard from './GroupCard.vue'

const renderers: BoardRendererRegistry = {
  text: TextCard,
  group: GroupCard,
}