@molecule/app-whiteboard-canvas-react
Feature · whiteboard-canvas · App (browser) · v1.0.2 · Apache-2.0
React whiteboard canvas — pen / marker / eraser / sticky-note / line / arrow / shape / text tools layered on top of @molecule/app-feature-canvas-react
npm install @molecule/app-whiteboard-canvas-reactHow it works
@molecule/app-whiteboard-canvas-react is a ready-made whiteboard-canvas feature for the app (browser) side. It composes the core interfaces it needs, so it works with whichever providers your app has bonded.
import { useState } from 'react'
import {
WhiteboardCanvas,
type WhiteboardShape,
type WhiteboardStickyNote,
type WhiteboardStroke,
type WhiteboardTool,
} from '@molecule/app-whiteboard-canvas-react'
function Demo() {
const [tool, setTool] = useState<WhiteboardTool>('pen')
const [strokes, setStrokes] = useState<WhiteboardStroke[]>([])
const [shapes, setShapes] = useState<WhiteboardShape[]>([])
const [stickyNotes, setStickyNotes] = useState<WhiteboardStickyNote[]>([])
return (
<WhiteboardCanvas
tool={tool}
strokes={strokes}
shapes={shapes}
stickyNotes={stickyNotes}
onChange={(c) => {
setStrokes(c.strokes)
setShapes(c.shapes)
setStickyNotes(c.stickyNotes)
}}
width={800}
height={600}
/>
)
}Works with: @molecule/app-feature-canvas-react, @molecule/app-react, @molecule/app-ui
Reference
React whiteboard canvas — pen / marker / eraser / sticky-note /
line / arrow / shape / text tools layered on top of
@molecule/app-feature-canvas-react.
Pan + zoom infrastructure is delegated to <CanvasSurface> from
the base — this package contributes only the whiteboard-specific
tools, geometry helpers, and element types (free-form strokes,
vector shapes, sticky notes / text boxes).
Exports:
<WhiteboardCanvas>— controlled canvas component. Acceptsstrokes,shapes,stickyNotes,tool, and anonChangecallback fired at the end of every gesture.WhiteboardSnapshot,WhiteboardStroke,WhiteboardShape,WhiteboardStickyNote,WhiteboardTooltypes describing the board's element model.buildStrokePath,buildShapePath,shapeBounds,applyEraserStrokes,strokeIntersectsPath,segmentsIntersect,defaultStrokeColor,defaultStrokeWidth,defaultShapeStyle,defaultStickyNoteStyle,generateWhiteboardId— pure geometry / id helpers (no React, no DOM).
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Quick Start
import { useState } from 'react'
import {
WhiteboardCanvas,
type WhiteboardShape,
type WhiteboardStickyNote,
type WhiteboardStroke,
type WhiteboardTool,
} from '@molecule/app-whiteboard-canvas-react'
function Demo() {
const [tool, setTool] = useState<WhiteboardTool>('pen')
const [strokes, setStrokes] = useState<WhiteboardStroke[]>([])
const [shapes, setShapes] = useState<WhiteboardShape[]>([])
const [stickyNotes, setStickyNotes] = useState<WhiteboardStickyNote[]>([])
return (
<WhiteboardCanvas
tool={tool}
strokes={strokes}
shapes={shapes}
stickyNotes={stickyNotes}
onChange={(c) => {
setStrokes(c.strokes)
setShapes(c.shapes)
setStickyNotes(c.stickyNotes)
}}
width={800}
height={600}
/>
)
}
Type
feature
Installation
npm install @molecule/app-whiteboard-canvas-react @molecule/app-feature-canvas-react @molecule/app-react @molecule/app-ui react
npm install -D @types/react
API
Interfaces
WhiteboardChange
onChange payload for WhiteboardCanvas.
interface WhiteboardChange {
/** Updated stroke list (controlled). */
strokes: WhiteboardStroke[]
/** Updated shape list (controlled). */
shapes: WhiteboardShape[]
/** Updated sticky-note list (controlled). */
stickyNotes: WhiteboardStickyNote[]
}
WhiteboardCanvasProps
WhiteboardCanvas props.
interface WhiteboardCanvasProps {
/**
* Free-form strokes (pen / marker / eraser) currently on the board.
* Controlled — feed back through `onChange`.
*/
strokes: WhiteboardStroke[]
/** Vector shapes on the board. Controlled. */
shapes: WhiteboardShape[]
/** Sticky notes / text boxes on the board. Controlled. */
stickyNotes: WhiteboardStickyNote[]
/**
* Called whenever the user finishes a tool gesture that mutates the
* board (stroke ended, shape committed, sticky placed). The payload
* is the COMPLETE post-gesture state — replace your local arrays
* with it.
*/
onChange: (change: WhiteboardChange) => void
/** Active drawing tool. */
tool: WhiteboardTool
/**
* Optional override for in-flight stroke color (pen / marker only).
* If omitted, uses `defaultStrokeColor` for the tool.
*/
strokeColor?: string
/** Optional override for stroke width. */
strokeWidth?: number
/**
* Optional sticky-note background color override (used by the
* sticky tool when placing a new note).
*/
stickyBackground?: string
/** Surface width in CSS pixels. */
width: number
/** Surface height in CSS pixels. */
height: number
/** Optional initial viewport (uncontrolled CanvasSurface viewport). */
initialViewport?: CanvasViewport
/** Optional viewport clamping limits forwarded to `<CanvasSurface>`. */
viewportLimits?: ViewportLimits
/**
* If true, every drawing tool is disabled (background pan still
* works). Useful for read-only viewers / playback mode.
*/
readOnly?: boolean
/** Optional aria-label override (defaults to a translated label). */
ariaLabel?: string
/** Extra classes merged onto the wrapper. */
className?: string
}
WhiteboardStroke
A free-form stroke (pen / marker / eraser). points is the raw
sampled pointer trail in canvas-space; rendering smooths the path
via quadratic Bezier midpoints. The eraser kind is itself a stroke
(so it round-trips through onChange) — consumers use
applyEraserStrokes to diff erasers against earlier strokes when
persisting.
interface WhiteboardStroke {
/** Stable id for selection / undo / realtime broadcast. */
id: string
/** Tool that produced the stroke. */
kind: WhiteboardStrokeKind
/** Raw sampled points in canvas-space. */
points: Point[]
/** CSS color string. Defaults are tool-specific. */
color: string
/** Stroke width in canvas units. Marker uses a fixed width. */
width: number
}
WhiteboardShape
A vector shape defined by two canvas-space corner points (from,
to). rect and ellipse derive their bounding rect from the
two points; line and arrow render as straight segments.
interface WhiteboardShape {
/** Stable id for selection / undo / realtime broadcast. */
id: string
/** Geometry kind. */
kind: WhiteboardShapeKind
/** Canvas-space start point. */
from: Point
/** Canvas-space end point. */
to: Point
/** Stroke color. */
stroke: string
/** Stroke width in canvas units. */
strokeWidth: number
/** Optional fill (rect / ellipse only). */
fill?: string
}
WhiteboardStickyNote
A sticky note / text box positioned in canvas-space. width /
height are canvas-space; text is the user-entered content.
Plain text tool uses the same shape but with a transparent
background.
interface WhiteboardStickyNote {
/** Stable id for selection / undo / realtime broadcast. */
id: string
/** Canvas-space top-left. */
position: Point
/** Canvas-space size. */
width: number
/** Canvas-space size. */
height: number
/** Note body text. */
text: string
/** Background fill. Use a transparent CSS color for plain text. */
background: string
/** Text color. */
color: string
}
WhiteboardSnapshot
Aggregate of every element kind on the board. This is what
persistence layers serialize, what realtime broadcasts mirror, and
what WhiteboardCanvas consumes via strokes, shapes, and
stickyNotes props (controlled).
interface WhiteboardSnapshot {
/** All free-form strokes (pen / marker / eraser) in chronological order. */
strokes: WhiteboardStroke[]
/** All vector shapes. */
shapes: WhiteboardShape[]
/** All sticky notes / text boxes. */
stickyNotes: WhiteboardStickyNote[]
}
Types
WhiteboardStrokeKind
Stroke produced by the pen tool — Bezier-smoothed free-form path.
type WhiteboardStrokeKind = 'pen' | 'marker' | 'eraser'
WhiteboardShapeKind
Vector shape kinds the whiteboard knows how to draw.
type WhiteboardShapeKind = 'line' | 'arrow' | 'rect' | 'ellipse'
WhiteboardTool
The active tool. select is a no-op pass-through for the canvas —
consumers can build their own selection / move logic on top.
type WhiteboardTool =
'pen' | 'marker' | 'eraser' | 'sticky' | 'line' | 'arrow' | 'rect' | 'ellipse' | 'text' | 'select'
Functions
WhiteboardCanvas(props)
React whiteboard canvas — pen / marker / eraser / sticky-note /
line / arrow / shape / text tools layered on top of
CanvasSurface from @molecule/app-feature-canvas-react.
Pan + zoom are delegated entirely to the base — this component only
handles tool-specific pointer-event interpretation and rendering of
strokes, shapes, and sticky notes inside the canvas-space inner
layer. All styling goes through getClassMap(); all user-facing
text goes through t() — both per molecule architecture rules.
Controlled state model: pass strokes / shapes / stickyNotes
and listen for onChange. Each completed gesture fires onChange
with the full post-gesture state.
function WhiteboardCanvas(props: WhiteboardCanvasProps): JSX.Element
props— Component props.
Returns: The whiteboard canvas element.
generateWhiteboardId()
Generate an id unique enough for in-process selection + realtime
broadcast. Not cryptographic. Falls back gracefully when
crypto.randomUUID is unavailable (older Node, jsdom).
function generateWhiteboardId(): string
Returns: A short unique id string.
buildStrokePath(points)
Build an SVG path string for a free-form stroke using quadratic Bezier midpoints — the standard "smooth pen" technique. Each intermediate point becomes a control point and the midpoint between adjacent points becomes the curve endpoint, which produces a smoothly-tangent path even from sparse pointer samples.
Returns an empty string for zero-point strokes and a single
M-command for one-point strokes (so renderers can still emit a
dot).
function buildStrokePath(points: readonly Point[]): string
points— Raw sampled canvas-space points.
Returns: An SVG path data string.
defaultStrokeWidth(kind)
Default stroke width in canvas units for a given tool. Marker is intentionally fixed to communicate "highlighter"; pen / eraser are thinner / wider.
function defaultStrokeWidth(kind: WhiteboardStrokeKind): number
kind— Stroke tool kind.
Returns: Default canvas-space stroke width.
defaultStrokeColor(kind)
Default color for a given stroke tool.
function defaultStrokeColor(kind: WhiteboardStrokeKind): string
kind— Stroke tool kind.
Returns: A CSS color string.
shapeBounds(shape)
Compute the axis-aligned bounding rect of a vector shape's two
canvas-space corner points. Useful for hit-testing and rendering
rect / ellipse.
function shapeBounds(shape: WhiteboardShape): {
x: number
y: number
width: number
height: number
}
shape— The vector shape.
Returns: { x, y, width, height } in canvas-space.
buildShapePath(shape)
Build SVG path data for a vector shape — straight segment for
line, segment with a small arrowhead for arrow. rect and
ellipse are typically rendered as their own SVG elements; this
helper still returns a closed path for them so consumers can
fall back to <path> if desired.
function buildShapePath(shape: WhiteboardShape): string
shape— The vector shape.
Returns: An SVG path data string.
defaultShapeStyle(kind)
Default fill / stroke palette per shape kind. Consumers override via direct prop assignment; this is just the initial-tap default.
function defaultShapeStyle(kind: WhiteboardShapeKind): {
stroke: string
strokeWidth: number
fill?: string
}
kind— Shape kind.
Returns: { stroke, strokeWidth, fill } defaults.
defaultStickyNoteStyle()
Default sticky-note size + colors. Plain text-tool consumers can
override background to a transparent value.
function defaultStickyNoteStyle(): {
width: number
height: number
background: string
color: string
}
Returns: Default sticky-note metrics + palette.
segmentsIntersect(a1, a2, b1, b2)
Test whether two segments intersect using the standard CCW
orientation test. Used by strokeIntersectsPath for eraser
hit-testing against existing strokes.
Collinear endpoints only count when the bounding boxes also overlap — otherwise two parallel-but-distant segments would register as touching just because every CCW determinant is zero.
function segmentsIntersect(a1: Point, a2: Point, b1: Point, b2: Point): boolean
a1— First segment start.a2— First segment end.b1— Second segment start.b2— Second segment end.
Returns: true if the segments cross or share a touching point.
strokeIntersectsPath(eraser, target)
true if the eraser stroke eraser crosses any segment of target
within tolerance canvas units. Used by applyEraserStrokes
to diff erasers against earlier strokes.
function strokeIntersectsPath(eraser: WhiteboardStroke, target: WhiteboardStroke): boolean
eraser— Eraser stroke (path of points).target— Target stroke to test against.
Returns: true when the two paths cross.
applyEraserStrokes(strokes)
Apply every eraser stroke in strokes against the non-eraser
strokes that came before it. Returns a new array containing only
the surviving non-eraser strokes — useful when persisting a
snapshot or compacting an undo history.
Erasers themselves are dropped from the result; consumers that need to keep them for replay should diff manually.
function applyEraserStrokes(strokes: readonly WhiteboardStroke[]): WhiteboardStroke[]
strokes— The full chronological stroke list.
Returns: Surviving non-eraser strokes (eraser entries removed).
Injection Notes
Requirements
Peer dependencies:
@molecule/app-feature-canvas-react^1.0.1@molecule/app-react^1.0.1@molecule/app-ui^1.0.1react^18.0.0 || ^19.0.0
Runtime Dependencies
@molecule/app-feature-canvas-react@molecule/app-react@molecule/app-uireact
width / height are REQUIRED CSS-pixel numbers — the surface does not
auto-size to its container; measure the container (e.g. a ResizeObserver)
for fill layouts. Requires the @molecule/app-feature-canvas-react peer
(pan/zoom base). Eraser gestures round-trip through onChange as
strokes; run applyEraserStrokes before persisting if you want erasures
baked in. UI strings come from the companion
@molecule/app-locales-whiteboard-canvas bond. readOnly disables all
drawing tools but keeps background pan.
Translations
Translation strings are provided by @molecule/app-locales-whiteboard-canvas.