← All @molecule/* packages · App templates
@molecule/app-whiteboard-canvas-reactFeature · whiteboard-canvas · App (browser) · v1.0.1 · 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-react@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
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.
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. Accepts
strokes, shapes, stickyNotes, tool, and an onChange
callback fired at the end of every gesture.WhiteboardSnapshot, WhiteboardStroke, WhiteboardShape,
WhiteboardStickyNote, WhiteboardTool types 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).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}
/>
)
}
feature
npm install @molecule/app-whiteboard-canvas-react @molecule/app-feature-canvas-react @molecule/app-react @molecule/app-ui react
npm install -D @types/react
WhiteboardCanvasProps{@link 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 {@link 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
}
WhiteboardChangeonChange payload for {@link WhiteboardCanvas}.
interface WhiteboardChange {
/** Updated stroke list (controlled). */
strokes: WhiteboardStroke[]
/** Updated shape list (controlled). */
shapes: WhiteboardShape[]
/** Updated sticky-note list (controlled). */
stickyNotes: WhiteboardStickyNote[]
}
WhiteboardShapeA 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
}
WhiteboardSnapshotAggregate of every element kind on the board. This is what
persistence layers serialize, what realtime broadcasts mirror, and
what {@link 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[]
}
WhiteboardStickyNoteA 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
}
WhiteboardStrokeA 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
}
WhiteboardShapeKindVector shape kinds the whiteboard knows how to draw.
type WhiteboardShapeKind = 'line' | 'arrow' | 'rect' | 'ellipse'
WhiteboardStrokeKindStroke produced by the pen tool — Bezier-smoothed free-form path.
type WhiteboardStrokeKind = 'pen' | 'marker' | 'eraser'
WhiteboardToolThe 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'
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).
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.
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.
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.
defaultStrokeColor(kind)Default color for a given stroke tool.
function defaultStrokeColor(kind: WhiteboardStrokeKind): string
kind — Stroke tool kind.Returns: A CSS color 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.
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.
segmentsIntersect(a1, a2, b1, b2)Test whether two segments intersect using the standard CCW orientation test. Used by {@link 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.
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.
strokeIntersectsPath(eraser, target)true if the eraser stroke eraser crosses any segment of target
within tolerance canvas units. Used by {@link 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.
WhiteboardCanvas(props)React whiteboard canvas — pen / marker / eraser / sticky-note /
line / arrow / shape / text tools layered on top of
{@link 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.
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@molecule/app-feature-canvas-react@molecule/app-react@molecule/app-uireactwidth / 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.
Translation strings are provided by @molecule/app-locales-whiteboard-canvas.