Skip to content

v2.2.0

Choose a tag to compare

@github-actions github-actions released this 25 Apr 09:47
· 22 commits to main since this release

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.Progress progress bar / waveform
    AudioPlayer.Volume volume trigger + slider
    AudioPlayer.PlayList sortable playlist drawer
    AudioPlayer.PlayListEmpty fallback rendered inside the playlist drawer when playList is empty
    AudioPlayer.PlayButton Play + Prev + Next group (Prev/Next visibility follows activeUI.prevNnext)
    AudioPlayer.RepeatButton repeat-type button
    AudioPlayer.Artwork track artwork
    AudioPlayer.TrackInfo track title / writer
    AudioPlayer.TrackTime current + duration time
    AudioPlayer.CustomComponent user-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 activeUI and render the compound counterpart with a custom gridArea:

    // 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 GridItemLayoutProps set β€” gridArea?, visible? (defaults to true), width?, padding?, justifySelf?, UNSAFE_className? β€” plus its own domain props. AudioPlayer.TrackTime is the exception: it only exposes visible? 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.warn is emitted when a compound slot is rendered while its preset counterpart is still active, pointing to the activeUI flag 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 activeUI flags 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: when playList is 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. Previously PlayList resolved its portal target through document.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. Interface now 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 full GridItemLayoutProps set in its TypeScript surface but the implementation only forwarded gridArea and visible to the underlying Grid.Item, so width, padding, justifySelf, and UNSAFE_className were silently ignored at runtime. The slots now spread the rest of GridItemLayoutProps onto Grid.Item, and AudioPlayer.Progress treats its previous hard-coded width="100%" as a default (width ?? "100%") so consumers can override.

Full Changelog: v2.1.0...v2.2.0