@webmusic/ui/stage
@webmusic/ui/stage supplies three domain-neutral presentation lifecycles. mountStage() gives
caller rendering a retained DOM surface, mountCanvasStage() owns canvas sizing and redraws, and
mountSurfaceSlider() adds pointer and keyboard slider behavior to an existing element.
Use status for a standalone loading or failure surface and analysis for ready-made analytical output. Stage is the lower level choice when the caller still owns the domain pixels or DOM.
Drag the surface slider or use its Arrow, Home, and End keys; all three real stage mounts share the same caller-owned State.
Import
Section titled “Import”import {mountCanvasStage} from '@webmusic/ui/stage';
const handle = mountCanvasStage(host, { draw: ({context, width, height}) => drawWaveform(context, width, height), subscribe: (notify) => model.subscribe(notify),});Three mounting models
Section titled “Three mounting models”mountStage() clears and re-runs binding.render(surface) on every update, calling the previous
render cleanup first. If rendering, cleanup, or synchronous subscription notification mounts a
replacement on the same host, the outgoing stage stops updating and immediately releases any
cleanup returned after replacement. mountCanvasStage() retains one canvas, sizes its backing store for the
device pixel ratio, and calls draw(frame) after resize, notification, explicit update, or every
animation frame when animate is enabled.
mountSurfaceSlider() creates no pixels and installs no stylesheet. It augments the supplied
element with slider semantics and delegates domain hit-testing to valueAt(point). commit() is
the only write port. All three follow the
shared presenter ownership contract.
API
The module exports three related contracts; the canvas presenter shown above has no snapshot State because its caller draws each frame directly.
Retained stage binding
| Member | Kind | Description |
|---|---|---|
render(surface) |
(HTMLElement) => void | (() => void) |
Required renderer. Its optional cleanup runs before the next render and during destruction. |
subscribe?(notify) |
(notify: () => void) => () => void |
Publishes when the retained surface must be rendered again. |
Retained stage options
| Member | Kind | Default | Description |
|---|---|---|---|
label |
string |
— | Adds role="region" and an accessible name to the root. |
fill |
boolean |
false |
Makes the stage and its surface fill a host with definite height. |
surfaceMinWidth |
string |
0 |
Minimum CSS width of the inner drawing. Combine with overflow: 'auto' to preserve readable content inside a narrower host. It does not resize the host. |
background |
string |
Stage theme (transparent) |
CSS background color of the retained viewport. Applies behind the whole scroll viewport so long drawings retain their background after scrolling. |
overflow |
'hidden' | 'auto' | 'visible' | 'clip' | 'scroll' |
hidden |
How the stage crops content that exceeds it. Scrolling is a mounting decision, not a theme, so it is an option rather than a --wm-stage-overflow write the caller has to know about. |
classNames |
StageClassNames |
{} |
Adds compatibility classes for root and surface. |
parts |
StageParts |
{} |
Adds part tokens for root and surface. |
onError |
(error: unknown) => void |
— | Receives render, subscription, and cleanup failures. |
Retained stage handle
| Member | Kind | Description |
|---|---|---|
element |
HTMLElement |
Presenter-owned .wui-stage root. |
surface |
HTMLElement |
Stable .wui-stage__surface passed to render(). |
update() |
() => void |
Releases the prior render, clears the surface, and calls render() again. |
destroy() |
() => void |
Releases rendering and subscription resources, then removes the root and style. |
Surface-slider State
| Member | Kind | Description |
|---|---|---|
minimum |
number |
Lower value bound; non-finite input becomes 0. |
maximum |
number |
Upper bound, normalized to be at least minimum. |
value |
number |
Current value, clamped to the normalized bounds. |
disabled? |
boolean |
Strict true suppresses pointer and keyboard commits. |
Surface-slider binding
| Member | Kind | Description |
|---|---|---|
snapshot() |
() => SurfaceSliderState |
Required authoritative slider state. |
gesture?(phase) |
('start' | 'end' | 'cancel') => void |
Pointer lifecycle: start before commands, end after the final release commit, cancel when pending work must be discarded. Keyboard commands do not start a pointer gesture. |
valueAt(point) |
(SurfaceSliderPoint) => number |
Required domain mapping. The point includes client/local coordinates, pointer id, bounds and surface. During a pointer gesture, point.gesture supplies the stable press origin and relative travel described below. |
commit(value) |
(number) => void |
Required write port receiving a finite, clamped pointer or keyboard value. |
preview(value) |
(number) => void |
Optional. With commitOn: 'release' this receives every in-flight drag position, so the caller can paint the scrub while the domain sees exactly one write. |
subscribe?(notify) |
(notify: () => void) => () => void |
Publishes authoritative State changes. |
Surface-slider options
| Member | Kind | Default | Description |
|---|---|---|---|
label |
string |
'Value' |
Accessible name when the element has neither aria-label nor aria-labelledby. |
orientation |
'horizontal' | 'vertical' |
'horizontal' |
Sets ARIA orientation and Arrow-key direction. |
pointerTarget |
HTMLElement |
mounted element | Receives pointer listeners and capture while ARIA remains on the mounted element. |
pointerMode |
'absolute' | 'drag' | 'click' | 'none' |
'absolute' |
Absolute commits on press and movement. Drag waits for 4 CSS pixels of travel; a stationary release remains a click. Click commits only a stationary release. None retains keyboard/ARIA without pointer listeners or touch styling. |
cursor |
string |
— | Idle pointer cursor, such as grab. Ignored in pointer none mode. |
dragCursor |
string |
cursor |
Cursor during an active pointer gesture, such as grabbing. |
keyboardStep |
number | (state, event) => number |
span ÷ 100, or 1 |
Amount committed for each Arrow key. The callback receives the keyboard event too, so a modifier can select a coarser step — the transport uses Shift for a long seek. |
commitOn |
'move' | 'release' |
'move' |
When a pointer drag reaches commit(). 'release' previews the drag and commits once, on pointerup — one seek per scrub rather than one per move. A cancelled drag repaints from the owner’s state and commits nothing. |
formatValue |
(value, state) => string | undefined |
— | Supplies aria-valuetext; undefined removes it. |
onError |
(error: unknown) => void |
— | Receives snapshot, hit-test, commit, formatting, subscription, and pointer-capture failures. |
Surface-slider handle
| Member | Kind | Description |
|---|---|---|
element |
HTMLElement |
Caller element carrying role, focus, and ARIA. |
surface |
HTMLElement |
Pointer surface: pointerTarget or the mounted element. |
update() |
() => void |
Re-reads State and applies ARIA values; disabling cancels an active gesture. |
cancelGesture() |
() => void |
Cancels active pointer work and discards a release-only preview. Idempotent; leaves keyboard and future gestures available. |
destroy() |
() => void |
Removes listeners and restores attributes and touch behavior that the presenter assigned. |
A pointer point’s optional gesture is a readonly snapshot with startClientX,
startClientY, startValue, deltaX, deltaY and moved. Origins use the current
State at press; movement uses CSS client pixels and stays independent of later
layout, viewport or State changes. moved becomes true after 4 CSS pixels of
travel and stays true even if the pointer returns to its origin. The presenter
owns that geometry; the caller owns its units and direction. For example, an
Audio caller can return point.gesture.startValue - point.gesture.deltaX / zoom
from valueAt to move earlier as a track is dragged right. Snapshot the intended
zoom at gesture start if it may change while dragging.
Only one primary pointer owns a gesture. Additional pointers cannot steal it.
Pointer cancellation, lost capture, host/window blur, disable, explicit
cancelGesture(), destruction and same-host replacement cancel pending work.
A cancel does not undo values already committed by a continuous gesture; it
prevents a later release from committing and lets the caller discard queued
work. Release-only preview always maps the actual release coordinates before
its single commit. Continuous input commits a changed final coordinate without
repeating an unchanged final point. Errors from borrowed callbacks reach
onError; reentrant destruction stops remaining pointer commands.
commit() is optimistic only until the owner publishes State: a synchronous
update() during the callback preserves the owner’s accepted value, including
clamping or wraparound, instead of overwriting it with the requested value.
A caller scheduling writes on animation frames can flush its last pending value
in gesture('end') and drop it in gesture('cancel'); the presenter creates no
musical clock or playback commands.
Configured cursors are restored on gesture completion/cancellation and release. A caller replacing the inline cursor while mounted keeps that override. Pointer none mode never takes cursor or touch-action ownership.
The presenter manages tabindex only when it assigned one: a disabled slider leaves the tab order
and rejoins it when re-enabled. An author-supplied tabindex is that author’s focus policy and is
never rewritten.
Canvas binding and frame
| Member | Kind | Description |
|---|---|---|
draw(frame) |
(CanvasStageFrame) => void |
Required drawing callback. The frame carries context, canvas, CSS-pixel width / height, pixelRatio, and frame time. |
status?() |
() => StatusState |
Supplies a ready, loading, empty, or error overlay. |
subscribe?(notify) |
(notify: () => void) => () => void |
Requests a redraw from the caller’s source. |
Canvas options
| Member | Kind | Default | Description |
|---|---|---|---|
label |
string |
— | Adds role="img" and an accessible name to the stage root. |
animate |
boolean |
false |
Continuously redraws through requestAnimationFrame. |
fallbackWidth |
number |
300 |
CSS-pixel width used when the stage has no positive measured width. |
fallbackHeight |
number |
150 |
CSS-pixel height used when the stage has no positive measured height. |
classNames |
CanvasStageClassNames |
{} |
Adds compatibility classes for root, canvas, and status. |
parts |
CanvasStageParts |
{} |
Adds part tokens for root, canvas, and status. |
onError |
(error: unknown) => void |
— | Receives draw, status, subscription, resize, and cleanup failures. |
stylesheet |
boolean |
true |
Installs the presenter’s stylesheet unless explicitly false, for a light-DOM host that cannot inject CSS into its page. |
Canvas handle
| Member | Kind | Description |
|---|---|---|
element |
HTMLElement |
Presenter-owned .wui-stage--canvas root. |
canvas |
HTMLCanvasElement |
DPR-sized drawing surface. |
update() |
() => void |
Redraws now and starts animation when configured. |
redraw() |
() => void |
Draws exactly one frame. |
destroy() |
() => void |
Cancels animation and resize observation, destroys status, and removes owned DOM. |
Accessibility. A labelled retained stage is a named region. A labelled canvas stage is a named image while its canvas is decorative; a supplied status is exposed through the semantics documented by status. Surface sliders preserve pre-existing role, focus and naming attributes, expose min/max/now/disabled values, support Arrow keys plus Home and End, and restore only presenter-assigned attributes on destroy.
Styling
Use the shared surface and palette tokens for
background (including transparent), foreground, accent, outer border and corner radius.
Presenter-specific tokens below take precedence within their documented roles.
mountStage() and mountCanvasStage() install the shared stage stylesheet by default. Set
stylesheet: false to omit it; the canvas stage also omits its nested status stylesheet. The host
must then supply stageStyle, any needed statusStyle, or its own CSS. The presenter root
uses the shared --wm-component-* surface, or
--wm-stage-surface-* for a stage-only override. The --wm-stage-* properties below style the
full-bleed inner drawing viewport. A surface slider installs no visual style because its element
and pixels remain caller-owned.
Tokens
| Property | Falls back to | Default | Controls |
|---|---|---|---|
--wm-stage-background |
literal | transparent |
Drawing background; background also covers the retained root scrollport. |
--wm-stage-foreground |
--wm-foreground → literal |
#444 |
Stage foreground context. |
--wm-stage-border |
literal | 0 |
Inner drawing-viewport border. |
--wm-stage-radius |
--wm-control-radius |
0 |
Inner drawing-viewport corner radius. |
--wm-stage-overflow |
literal | hidden |
Retained root scrollport behavior; canvas stages apply it to their inner viewport. |
--wm-stage-surface-min-width |
literal | 0 |
Minimum width of the retained inner drawing. surfaceMinWidth sets this token on the stage root. |
A canvas status overlay mounts the status presenter’s embedded variant. It fills the inner viewport
without drawing a second box, while its message still accepts the relevant
--wm-status-* text tokens.
Classes
| Node | Canonical class | Present when | classNames key |
|---|---|---|---|
| Root | .wui-stage |
Always | root |
| Fill modifier | .wui-stage--fill |
Retained fill: true |
— |
| Retained surface | .wui-stage__surface |
mountStage() |
surface |
| Canvas modifier | .wui-stage--canvas |
mountCanvasStage() |
— |
| Canvas surface | .wui-stage__surface.wui-stage__canvas-surface |
mountCanvasStage() |
— |
| Canvas | .wui-stage__canvas |
mountCanvasStage() |
canvas |
| Status layer | .wui-stage__status |
Canvas binding has status() |
status |
| Status mount host | .wui-status-host |
Canvas binding has status() |
— |
Parts
| Node | Canonical part | Present when | parts key |
|---|---|---|---|
| Root | root |
Retained or canvas stage | root |
| Retained surface | surface |
mountStage() |
surface |
| Canvas | canvas surface |
mountCanvasStage() |
canvas adds tokens beside both canonical names |
| Status layer | status |
Canvas binding has status() |
status |