v2.2.0
v2.2.0 (2026-04-25)
β¨ New Features
-
Compound slots on
AudioPlayer: the default export now exposes the built-in controls as static members that can be rendered as children alongside the preset:Static member Wraps AudioPlayer.Progressprogress bar / waveform AudioPlayer.Volumevolume trigger + slider AudioPlayer.PlayListsortable playlist drawer AudioPlayer.PlayListEmptyfallback rendered inside the playlist drawer when playListis emptyAudioPlayer.PlayButtonPlay + Prev + Next group (Prev/Next visibility follows activeUI.prevNnext)AudioPlayer.RepeatButtonrepeat-type button AudioPlayer.Artworktrack artwork AudioPlayer.TrackInfotrack title / writer AudioPlayer.TrackTimecurrent + duration time AudioPlayer.CustomComponentuser-defined slot (existing) Compound children render additively alongside the preset β they do not replace the default layout by themselves. To replace a preset control, disable the corresponding slot in
activeUIand render the compound counterpart with a customgridArea:// Hide the preset volume and re-place a custom volume at the right edge <AudioPlayer playList={list} activeUI={{ all: true, volume: false }} > <AudioPlayer.Volume gridArea="1 / 5 / 1 / 6" /> </AudioPlayer>
Each compound accepts the full
GridItemLayoutPropsset βgridArea?,visible?(defaults totrue),width?,padding?,justifySelf?,UNSAFE_className?β plus its own domain props.AudioPlayer.TrackTimeis the exception: it only exposesvisible?because the slot maps to two grid areas internally.Native HTML attributes (
className,style,onClick,data-*, etc.) are not forwarded by compound slots. The original internal components (PlayBtn,PrevBtn,NextBtn, etc.) remain exported for users who want fully custom layouts with full DOM control; headless support with native attribute pass-through is planned for v3.In development, a
console.warnis emitted when a compound slot is rendered while its preset counterpart is still active, pointing to theactiveUIflag that resolves the duplication.Why it matters
- Move or re-skin individual controls (e.g. volume at the right edge, a waveform in place of the bar progress) without rebuilding the whole player from primitives.
- Drop specific built-in controls by toggling
activeUIflags and keep everything else intact β no more choosing between "preset" and "fully custom". - Fall back to the preset layout the moment you stop passing compound children β adopting the new API is zero-cost for existing consumers.
- Mix preset and compound in the same player when you need two of the same control (e.g. a secondary volume) without forking the component tree.
-
Custom empty-playlist UI via
AudioPlayer.PlayListEmpty: whenplayListis empty the drawer previously rendered nothing; consumers can now opt into a custom fallback by passing children to the slot. Omitting the slot preserves the previous default.<AudioPlayer playList={[]}> <AudioPlayer.PlayListEmpty> <div className="my-empty">Playlist is empty</div> </AudioPlayer.PlayListEmpty> </AudioPlayer>
-
audioInitialState.playListExpanded(Fixes #21): the playlist drawer can now start in the expanded state without user interaction.<AudioPlayer playList={list} audioInitialState={{ curPlayId: 1, playListExpanded: true }} />
Consistent with the rest of
audioInitialState: read once at mount, not tracked in reducer state. -
Multi-instance playlist isolation (Fixes #11, #15): multiple
<AudioPlayer>instances on the same page no longer leak playlist content into each other's drawer. PreviouslyPlayListresolved its portal target throughdocument.querySelector(".rmap-sortable-playlist"), which matched the first target in document order β every secondary player would render its playlist into the first player's drawer.Interfacenow publishes its own portal node per instance, and each drawer renders exclusively into its own target. Audio state (play/pause, volume, mute, current track) was already isolated per instance via the v2 provider split.
π Bug Fixes
- Compound slots silently dropped layout props: each compound slot (
AudioPlayer.Volume,AudioPlayer.Progress,AudioPlayer.Artwork,AudioPlayer.TrackInfo,AudioPlayer.RepeatButton,AudioPlayer.PlayList,AudioPlayer.PlayButton) advertised the fullGridItemLayoutPropsset in its TypeScript surface but the implementation only forwardedgridAreaandvisibleto the underlyingGrid.Item, sowidth,padding,justifySelf, andUNSAFE_classNamewere silently ignored at runtime. The slots now spread the rest ofGridItemLayoutPropsontoGrid.Item, andAudioPlayer.Progresstreats its previous hard-codedwidth="100%"as a default (width ?? "100%") so consumers can override.
Full Changelog: v2.1.0...v2.2.0