@webmusic/ui/playlist
mountPlaylist() renders previous, play/pause, next, and seek controls over an ordered list of
entries. It paints caller-owned PlaylistState and routes every transport or selection gesture
through a required structural PlaylistBinding; it never owns a player, queue, decoder, or file.
Use transport for one current item without a queue, timeline for labelled time regions, or minimap for overview-range navigation. Mount this presenter directly when entry identity, loading policy, and player commands live in your store.
Use Previous, Play and Next, drag Seek, or choose a row; then expose loading/error items or change Options.
Import
Section titled “Import”import {mountPlaylist} from '@webmusic/ui/playlist';
const handle = mountPlaylist(host, { snapshot: () => store.playlist, toggle: () => player.toggle(), previous: () => player.previous(), next: () => player.next(), seek: (fraction) => player.seek(fraction), select: (id) => player.select(id),});Entry identity and patching
Section titled “Entry identity and patching”items is the caller’s ordered queue. Each item supplies a stable id, a label, optional
preformatted duration, optional active state, and optional loading or error status. The
presenter deliberately does not format duration or decide whether an errored entry may be
selected; those are domain policies implemented by the binding.
The row list is rebuilt only when item ids, labels, or order change. Active state, status, and
duration are patched onto existing rows so frequent progress notifications do not steal focus
or cancel an in-flight row click. Keep ids unique and stable if code uses controls.item(id).
Empty or whitespace-only ids reject the snapshot through onError; the prior DOM remains.
A rebuilt row list restores focus to the same id when that entry remains present.
Binding and command settlement
Section titled “Binding and command settlement”All five user commands are required and may return promises. The presenter calls the requested
command, then re-reads the authoritative snapshot after it fulfills; synchronous throws and
current-command rejections reach onError. Older settlement may refresh accepted state but
cannot report over a newer command. Settlement after replacement/destruction is ignored, and
error-callback faults are contained. Subscription notifications can repaint independently. See
the shared presenter shape for common ownership and
cleanup rules.
The presenter does not apply an optimistic queue transition. toggle(), previous(), next(),
seek(), and select() must update caller state—then notify, fulfill, or both—for the visible
state to change.
API
mountPlaylist(host, binding, options?) accepts an HTMLElement or ShadowRoot and returns a
PlaylistHandle.
State
| Member | Kind | Description |
|---|---|---|
playing |
boolean |
Strict true chooses the pause glyph/label; otherwise uses play. |
progress |
number |
Current-entry position. Finite values clamp to 0…1; non-finite values paint as 0. |
disabled? |
boolean |
Strict true disables transport controls and blocks every command, including row selection; the root exposes aria-disabled. |
items |
readonly PlaylistItem[] |
Ordered entries. Absence at runtime is treated as an empty list. |
PlaylistItem contains required id and label, optional preformatted duration, optional
active, and optional status: 'loading' | 'error'. Strict active: true sets
aria-current; status adds a styling data attribute. The presenter does not synthesize status
text or mutate the item objects.
Binding
| Member | Kind | Description |
|---|---|---|
snapshot() |
() => PlaylistState |
Required pull port, read on mount, notification, command fulfillment, and explicit update. A throw reaches onError and leaves the current DOM unchanged. |
toggle() |
() => void | Promise<void> |
Required play/pause intent from the central transport button. |
previous() |
() => void | Promise<void> |
Required previous-entry intent. |
next() |
() => void | Promise<void> |
Required next-entry intent. |
seek(value) |
(number) => void | Promise<void> |
Required current-entry seek intent; receives a normalized 0…1 fraction. |
select(id) |
(string) => void | Promise<void> |
Required entry-selection intent from click, Enter, or Space. |
subscribe?(notify) |
(notify: () => void) => () => void |
Optional change subscription; its disposer is called by destroy() and on same-host remount. |
Options
| Member | Kind | Default | Description |
|---|---|---|---|
label |
string |
'Playlist' |
Accessible name of the ordered entry list. |
stylesheet |
boolean |
true |
Installs playlistStyle unless explicitly false. |
transport |
boolean |
true |
False hides the transport bar for selection-only companion use; row selection remains available. |
previousLabel / nextLabel |
string |
Previous / Next |
Accessible navigation names. |
playLabel / pauseLabel |
string |
Play / Pause |
Accessible toggle names selected by playback state. |
seekLabel |
string |
Seek |
Accessible range name. |
classNames |
PlaylistClassNames |
{} |
Adds classes for root, bar, button, previous, play, next, seek, list, item, number, label, and duration. |
parts |
PlaylistParts |
{} |
Adds part tokens for the same node roles, preserving canonical tokens. |
onError |
(error: unknown) => void |
— | Receives snapshot, command, subscription, and cleanup failures. |
Handle
| Member | Kind | Description |
|---|---|---|
element |
HTMLElement |
Presenter-owned .wui-playlist root. |
controls.previous |
HTMLButtonElement |
Always-present previous button. |
controls.toggle |
HTMLButtonElement |
Always-present play/pause button. |
controls.next |
HTMLButtonElement |
Always-present next button. |
controls.seek |
HTMLInputElement |
Always-present range with DOM values 0…1000. |
controls.list |
HTMLOListElement |
Always-present ordered list. |
controls.item(id) |
(string) => HTMLLIElement | undefined |
Returns the currently rendered row mapped to an entry id. |
update() |
() => void |
Re-reads and patches state; becomes a no-op after destruction. |
destroy() |
() => void |
Idempotently disposes the subscription and removes presenter DOM and any installed stylesheet. |
Accessibility. Previous, toggle, and next are native buttons with accessible names; the
toggle name follows playing. Seek is a native range labelled Seek, and all four transport
controls reflect disabled through the native attribute. The ordered list receives label as
its accessible name. Each row is a focusable role="button" with click, Enter, and Space
activation plus aria-current for the active item. Root disabled blocks row selection as well as transport commands. Loading/error remain
visual data states, so the binding owns status messaging and per-item selection policy. Caller nodes outside the presenter root are never
rewritten.
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.
The installed stylesheet resolves presenter-specific playlist tokens through shared --wm-*
values and then literals.
Tokens
| Property | Falls back to | Default | Controls |
|---|---|---|---|
--wm-playlist-height |
--wm-control-size → --wui-row-h |
2rem |
Height of the transport buttons and of the seek rail. |
--wm-playlist-track |
--wm-surface-muted |
#f3f3f3 |
Unfilled seek rail. |
--wm-playlist-thumb |
--wm-foreground |
#111 |
Seek handle fill. |
--wm-playlist-thumb-radius |
--wm-control-radius |
0 |
Seek handle corners. |
--wm-playlist-handle-size |
literal | .9rem |
Seek handle width. Its height follows the control size. |
--wm-playlist-border |
--wm-border |
#d8d8d8 |
Root border. |
--wm-playlist-radius |
--wm-control-radius |
0 |
Root, button, and item corners. |
--wm-playlist-background |
--wm-surface |
#fff |
Root background. |
--wm-playlist-text |
--wm-foreground |
#444 |
Root text color. |
--wm-font-family |
--wm-font |
system-ui, sans-serif |
Playlist UI font. |
--wm-playlist-button-border |
--wm-control-border → --wm-border |
#d8d8d8 |
Previous and next button borders. |
--wm-control-border |
--wm-border |
#d8d8d8 |
Shared non-primary control border fallback. |
--wm-playlist-button |
--wm-surface |
#fff |
Previous and next button backgrounds. |
--wm-playlist-button-text |
--wm-foreground |
#444 |
Previous and next button glyphs. |
--wm-playlist-primary-border |
--wm-playlist-button-border → --wm-accent |
#111 |
Primary play/pause button border. |
--wm-playlist-primary-background |
--wm-playlist-button → --wm-accent |
#111 |
Primary play/pause button background. |
--wm-playlist-primary-foreground |
--wm-playlist-button-text → --wm-accent-foreground |
#fff |
Primary play/pause button glyph. |
--wm-playlist-accent |
--wm-accent |
#111 |
Native seek accent. |
--wm-playlist-active |
Literal | rgba(17, 17, 17, .08) |
Active-row background. |
--wm-playlist-error |
--wm-error |
#c0392b |
Error-row text. |
--wm-focus |
currentColor |
Current text color | 2px keyboard-focus outline on rows, transport buttons and seek control. |
--wm-playlist-muted |
--wm-foreground-muted |
#666 |
Duration text. |
Classes
classNames adds compatibility classes alongside the canonical classes for its six public node
roles. Labels and durations expose canonical classes but have no classNames key.
| Node | Canonical class | classNames key |
|---|---|---|
| Root | .wui-playlist |
root |
| Transport bar | .wui-playlist__bar |
bar |
| Each transport button | .wui-playlist__button |
button |
| Seek range | .wui-playlist__seek |
seek |
| Ordered list | .wui-playlist__items |
list |
| Entry row | .wui-playlist__item |
item |
| Entry number | .wui-playlist__number |
number |
| Entry label | .wui-playlist__label |
label |
| Entry duration | .wui-playlist__duration |
duration |
Parts
parts adds tokens alongside canonical tokens and removes duplicates. Each transport button also
carries its action modifier.
| Node | Canonical part | parts key |
|---|---|---|
| Root | root |
root |
| Transport bar | bar |
bar |
| Previous button | button prev |
button, previous |
| Toggle button | button play |
button, play |
| Next button | button next |
button, next |
| Seek range | seek |
seek |
| Ordered list | list |
list |
| Entry row | item |
item |
| Entry number | number |
number |
| Entry label | label |
label |
| Entry duration | duration |
duration |
With stylesheet: false, the presenter installs no style node; the caller must style these
classes and parts itself.