Skip to content

Framework wrappers

The thin framework wrappers version independently of the core libraries they wrap.

  • Forwarding-drift test. test/forwarding-drift.test.ts enumerates the installed core’s option surface (DEFAULT_OPTIONS plus the WaveformPlayerOptions keys), renders each option, and feeds the emitted data-* attributes through the installed core’s own parseDataAttributes() — failing for any option that doesn’t round-trip and isn’t listed in NOT_FORWARDED with a reason. Test-only; adds @types/node as a dev dependency.
  • onNextTrack / onPreviousTrack are no longer props. They were typed — WaveformPlayerProps omitted the lifecycle callbacks but not these two — yet a server-rendered component can’t carry a function into a data-* attribute, so they type-checked and did nothing. Now omitted with the other callbacks. Lock-screen skip buttons need a player constructed in client JS (or @arraypress/waveform-bar / -playlist, which supply the handlers).
  • Core peer floor raised to @arraypress/waveform-player@^1.23.0 (was ^1.20.0). The component emits data-button-radius / data-artwork-position (core 1.22.0) and data-cross-origin (1.23.0); an older core ignores them, so those props silently did nothing on the declared range. The dev dependency moves to ^1.27.1 (was ^1.22.0, which predates the crossOrigin type). A new test pins the floor to the newest attribute emitted.
  • waveformGradient and seekHandle now reach the player, as data-waveform-gradient (the axis string) and data-seek-handle. Both have been typed props since the core added them (1.18.0 / 1.17.0), but the data-* emission is hand-written and never listed them, so they were silently dropped.
  • View Transitions no longer leave players running after navigation. The component only re-initialised on astro:page-load; nothing tore the outgoing players down, so a playing track kept going from its detached new Audio() with no UI left to pause it, and every visited page’s instances stayed registered. Each <WaveformPlayer> (lazy or not) now also ships a one-time astro:before-swap handler that destroys the declarative players whose containers are leaving the page, keeping those inside a transition:persist host the next page carries. Players built by framework islands are left to their framework.
  • crossOrigin prop. Exposes the option added in @arraypress/waveform-player@1.23.0, emitted as data-cross-origin ('anonymous' | 'use-credentials'). Omitted by default so the player behaves like a native <audio> and never forces a CORS request that would break CDN media without Access-Control-Allow-Origin.
  • buttonRadius and artworkPosition props. Exposes the two options added in @arraypress/waveform-player 1.22.0: buttonRadius sets the play button’s corner radius (0 for square, or any CSS length), and artworkPosition ('info' | 'button') chooses whether the cover renders in the info row or becomes the play button itself.

    Both prop types already flowed through automatically, since the props derive from the core’s WaveformPlayerOptions — but the data-* emission is written by hand, so until now they would have type-checked and then silently done nothing. Tests now cover the mapping for exactly that reason.

    The peer range stays ^1.20.0: the props only appear once the consumer’s own core is on 1.22.0, so nothing breaks on an older one.

  • Forward the core player’s new localizable UI-string options — seekValueText, playPauseLabel, speedLabel, artworkAlt, and unknownTrackText — through to the underlying player. Requires @arraypress/waveform-player@^1.20.0.
  • BREAKING: the six DOM-chrome colour props — buttonColor, buttonHoverColor, textColor, textSecondaryColor, backgroundColor, and borderColor — are gone (the core library moved this chrome to CSS variables). Theme the button and text via --wfp-button-color, --wfp-text-color, --wfp-text-secondary-color, etc. in your own CSS instead. waveformColor, progressColor, colorPreset, and waveformGradient remain props.
  • Types are now sourced directly from the core @arraypress/waveform-player package — a single source of truth. The shared option types (WaveformStyle, ColorPreset, AudioMode, AudioPreload, ButtonAlign, WaveformMarker, WaveformPeaks) are re-exported from the core, and WaveformPlayerProps now extends the core’s WaveformPlayerOptions instead of re-declaring every option, so the two packages can no longer drift. Every previously-exported type name is preserved.
  • Bumped the @arraypress/waveform-player peer (and dev) dependency to ^1.8.0, which ships the hand-authored index.d.ts these types adopt.
  • accessibleSeek, seekLabel, barRadius, and the gradient-array forms of waveformColor / progressColor are now exposed on WaveformPlayerProps (inherited from the core option surface), filling gaps where the previous hand-maintained copy had missed or drifted.
  • Bumped the @arraypress/waveform-player peer (and dev) dependency to ^1.7.2. Consumers now get the native accessible keyboard / ARIA seek slider by default. No component API changes.
  • Widened the astro peerDependency to ^6.0.0 || ^7.0.0 for Astro 7 readiness. No runtime changes — the component is unaffected by the Astro 7 compiler / Vite 8 (Rolldown) upgrade.

Initial release.

  • <WaveformPlayer> Astro component wrapping every option exposed by @arraypress/waveform-player 1.6.x as a typed prop:
    • Audio source props (url, audioMode, preload)
    • Waveform visualisation props (waveformStyle, height, samples, barWidth, barSpacing, waveform)
    • Colour props (colorPreset, waveformColor, progressColor, buttonColor, buttonHoverColor, textColor, textSecondaryColor, backgroundColor, borderColor)
    • Playback control props (playbackRate, showPlaybackSpeed, playbackRates)
    • UI toggle props (showControls, showInfo, showTime, showHoverTime, showBPM, buttonAlign)
    • Marker props (markers, showMarkers)
    • Content metadata props (title, artist, artwork, album)
    • Behaviour flags (autoplay, singlePlay, playOnSeek, enableMediaSession)
    • Icon props (playIcon, pauseIcon)
  • Astro-specific lazy prop that switches the init attribute to data-waveform-player-lazy and ships a single deduplicated IntersectionObserver for grids of many previews.
  • Astro-specific id, class, and style pass-throughs.
  • Public TypeScript types: WaveformPlayerProps, WaveformStyle, WaveformMarker, WaveformPeaks, ColorPreset, AudioMode, AudioPreload, ButtonAlign.
  • Ambient declaration for window.WaveformPlayer to type consumer scripts that reach for the global.
  • Vitest suite (29 tests) covering attribute mapping, omission semantics, JSON serialisation for array props, lazy-mount script presence, and pass-through props.
  • Documentation: full prop reference, setup guide, seven usage examples (examples/basic.astro).
  • Forwarding-drift test. test/forwarding-drift.test.tsx enumerates the installed core’s option surface (DEFAULT_OPTIONS plus the WaveformPlayerOptions keys) and fails for any option that isn’t forwarded, doesn’t remount on change, or isn’t listed in NOT_FORWARDED with a reason — so the next core option can’t be dropped the way the ones above were. Test-only; adds @types/node as a dev dependency.
  • waveformGradient and seekHandle now reach the player. Both have been typed props since the core added them (1.18.0 / 1.17.0), because the props derive from WaveformPlayerOptions — but buildLibraryOptions is a hand-written allowlist that never listed them, so they type-checked and were silently dropped.
  • onNextTrack / onPreviousTrack now reach the player, so the lock-screen / system media controls show skip buttons when you supply them. They were typed but never forwarded. They’re passed only when set: the core registers the Media Session action whenever the option is a function, so an unconditional wrapper would show buttons that do nothing. Like the other callbacks they’re routed through a ref — a fresh inline handler doesn’t remount — but adding or removing one does, since the core reads it at construction.
  • Changing layout, buttonStyle or bpm at runtime now remounts the player. They were forwarded on first mount but missing from the remount useEffect deps, so later changes did nothing.
  • Changing only className no longer strips the player’s own classes. The core writes classes onto the host — waveform-player, waveform-layout-preview, waveform-theme-light, waveform-is-placeholder — and a className-only change (correctly) doesn’t remount, so when React rewrote the class attribute those were gone until some other prop happened to change, leaving an unstyled player. React now renders class once (so server markup and hydration are unchanged) and later className changes are applied with classList, adding and removing only the user’s tokens.
  • className and wfp-host survive mount. The core’s createDOM() replaces the host’s whole class list with waveform-player, so both were silently dropped as soon as the player built — className only ever styled the pre-mount placeholder. They’re re-applied right after construction. The DOM structure is unchanged: still one host <div>, which is also the .waveform-player root. (Mounting the core into an inner element was considered and rejected: --wfp-* variables set through style or className would then sit on a parent, shadowed by the core’s own .waveform-player defaults.)
  • WaveformPlayerHandle.setPlaybackRate documents the real range — 0.25..4, what the core clamps to — instead of 0.5..2.
  • Loads @arraypress/waveform-player/no-autoinit instead of the package root. Importing the root scans the whole document for [data-waveform-player] markup and builds a player for every match. This component constructs its own player on its own ref and wants none of that. In a pure React app the scan found nothing and merely cost a querySelectorAll; as an island on a page that does carry such markup — a CMS page, a WordPress template, an Astro or Rails view — mounting this component silently mounted players the React tree never asked for, and owned them for the rest of the page’s life. Same class, same options, same behaviour for everything this component builds; the only thing that changes is that nothing else on the page gets touched.
  • Peer floor raised to @arraypress/waveform-player@^1.27.0, the release that added the /no-autoinit entry point. This is the first hard floor in the family rather than the usual soft one: on an older core the subpath does not exist, so it fails at mount, in the browser. Bump the core alongside this package.
  • crossOrigin prop. Exposes the option added in @arraypress/waveform-player@1.23.0: sets the CORS mode of the underlying <audio> ('anonymous' | 'use-credentials'). Omitted from the options bag by default so the player behaves like a native <audio> and never forces a CORS request that would break CDN media without Access-Control-Allow-Origin.
  • buttonRadius and artworkPosition props. Exposes the two options added in @arraypress/waveform-player 1.22.0: buttonRadius sets the play button’s corner radius (0 for square, or any CSS length), and artworkPosition ('info' | 'button') chooses whether the cover renders in the info row or becomes the play button itself.

    Both prop types already flowed through automatically, since the props derive from the core’s WaveformPlayerOptions — but the options bag is written by hand, so until now they would have type-checked and then silently done nothing. Tests now cover the mapping for exactly that reason.

    The peer range stays ^1.20.0: the props only appear once the consumer’s own core is on 1.22.0, so nothing breaks on an older one.

  • Forward the core player’s new localizable UI-string options — seekValueText, playPauseLabel, speedLabel, artworkAlt, and unknownTrackText — through to the underlying player. Requires @arraypress/waveform-player@^1.20.0.
  • Public types are now adopted from the core @arraypress/waveform-player (v1.8.0+), which ships a hand-authored index.d.ts. The shared option surface (WaveformStyle, ColorPreset, AudioMode, AudioPreload, ButtonAlign, WaveformMarker, WaveformPeaks) is re-exported from the core, and WaveformPlayerProps now extends the core’s WaveformPlayerOptions instead of re-declaring every field. The core is the single source of truth, so the wrapper’s types can no longer drift out of sync. Bumped the @arraypress/waveform-player peer (and dev) dependency to ^1.8.0.
  • Callback props (onLoad, onPlay, onPause, onEnd, onTimeUpdate, onError) and the WaveformPlayerHandle.instance accessor are now typed with the core’s WaveformPlayer class instead of unknown.
  • accessibleSeek, seekLabel, barRadius, and gradient-array waveform / progress colours (string[]) are now exposed as typed props — picked up for free by extending the core options and wired through to the underlying player.
  • Deleted src/core-module-shim.d.ts, the loose ambient declare module '@arraypress/waveform-player' shim that only existed while the core had no shipped types.
  • Bumped the @arraypress/waveform-player peer (and dev) dependency to ^1.7.2. Consumers now get the native accessible keyboard / ARIA seek slider by default. No component API changes.

Initial release.

  • <WaveformPlayer> React component wrapping every option exposed by @arraypress/waveform-player 1.6.x as a typed prop:
    • Audio source (url, audioMode, preload)
    • Waveform visualisation (waveformStyle, height, samples, barWidth, barSpacing, waveform)
    • Colours (colorPreset, waveformColor, progressColor, buttonColor, buttonHoverColor, textColor, textSecondaryColor, backgroundColor, borderColor)
    • Playback (playbackRate, showPlaybackSpeed, playbackRates)
    • UI toggles (showControls, showInfo, showTime, showHoverTime, showBPM, buttonAlign)
    • Markers (markers, showMarkers)
    • Metadata (title, artist, artwork, album)
    • Behaviour (autoplay, singlePlay, playOnSeek, enableMediaSession)
    • Icons (playIcon, pauseIcon)
  • Callback props (onLoad, onPlay, onPause, onEnd, onTimeUpdate, onError) that map to the library’s same-named option fields. Callbacks are deliberately NOT in the effect dep array, so a parent re-rendering with new inline functions doesn’t tear the player down.
  • React-specific extras: id, className, style, and ref forwarding via WaveformPlayerHandle.
  • WaveformPlayerHandle imperative API on the forwarded ref — play(), pause(), togglePlay(), seekTo(), seekToPercent(), setVolume(), setPlaybackRate(), setPlayingState(), setProgress(), loadTrack(), plus the raw instance.
  • SSR / RSC safe: the core library is loaded via dynamic import('@arraypress/waveform-player') inside the effect so the browser-only audio surface never runs server-side.
  • Identity-prop re-mount: when any library-construction prop changes (url, audioMode, etc.), the wrapper destroys the existing instance and creates a new one with the updated options. Simpler and more correct than diffing every option + calling granular updaters.
  • Public TypeScript types: WaveformPlayerProps, WaveformPlayerHandle, WaveformStyle, WaveformMarker, WaveformPeaks, ColorPreset, AudioMode, AudioPreload, ButtonAlign.
  • Ambient module shim for @arraypress/waveform-player so the wrapper typechecks cleanly until the core library ships its own .d.ts.
  • Vitest test suite (17 tests, jsdom + @testing-library/react) covering mount, unmount destroy, option pass-through, callback forwarding, identity-prop re-mount, callback-churn protection, ref forwarding, and the full imperative handle surface. The core library is mocked at the module boundary because jsdom has no Web Audio API.
  • Dual ESM (dist/index.js) + CJS (dist/index.cjs) build via tsup. .d.ts for both. React + the core library are externalised so they resolve to the consumer’s copies.
  • README with full prop reference, seven usage patterns, and the imperative-ref control example. examples/basic.tsx with seven copy-paste-ready snippets.
  • @next-track / @previous-track (onNextTrack / onPreviousTrack props). Supplying one shows the lock-screen / system media controls’ skip button. The core options were already typed on WaveformPlayerProps but had no runtime prop declaration, so they never reached the player and the buttons never appeared. They’re declared as function props rather than emits because the wrapper has to know whether anyone is listening — the core shows the button whenever the option is a function. Adding or removing one remounts the player; swapping one handler for another doesn’t.
  • Forwarding-drift test. test/forwarding-drift.test.ts enumerates the installed core’s option surface (DEFAULT_OPTIONS plus the WaveformPlayerOptions keys) and fails for any option that isn’t declared and forwarded, doesn’t remount on change, or isn’t listed in NOT_FORWARDED with a reason — so the next core option can’t be dropped the way the ones above were. Test-only; adds @types/node as a dev dependency.
  • WaveformPlayerProps omits the core’s style (a shorthand alias for waveformStyle), which typed style as a WaveformStyle while Vue applies it as the fall-through CSS attribute. Matches the other wrappers: use waveformStyle for the visual style.
  • waveformGradient and seekHandle now reach the player. Both have been typed props since the core added them (1.18.0 / 1.17.0), but with no runtime prop declaration Vue treated them as fall-through attributes on the <div>. Both are now declared, forwarded, and in the remount watch().
  • setPlaybackRate documents the real range — 0.25..4, what the core clamps to — instead of 0.5..2.
  • Changing only the fall-through class no longer strips the player’s own classes. The core writes classes onto the host — waveform-player, waveform-layout-preview, waveform-theme-light, waveform-is-placeholder — and a class-only change (correctly) doesn’t remount, so when Vue re-patched the class attribute those were gone until some other prop happened to change, leaving an unstyled player. The component now renders class once (server markup and hydration are unchanged) and applies later changes with classList, adding and removing only the user’s tokens. To keep class out of Vue’s patching it sets inheritAttrs: false and forwards every other attribute (id, style, listeners, data-*) itself — same result on the element.
  • The fall-through class and wfp-host survive mount. The core’s createDOM() replaces the host’s whole class list with waveform-player, so both were silently dropped as soon as the player built. They’re re-applied right after construction. The DOM structure is unchanged: still one host <div>, which is also the .waveform-player root. (Mounting the core into an inner element was considered and rejected: --wfp-* variables set through style or class would then sit on a parent, shadowed by the core’s own .waveform-player defaults.)
  • Loads @arraypress/waveform-player/no-autoinit instead of the package root. Importing the root scans the whole document for [data-waveform-player] markup and builds a player for every match. This component constructs its own player on its own ref and wants none of that. In a pure Vue app the scan found nothing and merely cost a querySelectorAll; as an island on a page that does carry such markup — a CMS page, a WordPress template, an Astro or Laravel view — mounting this component silently mounted players the Vue app never asked for, and owned them for the rest of the page’s life. Same class, same options, same behaviour for everything this component builds; the only thing that changes is that nothing else on the page gets touched.
  • Peer floor raised to @arraypress/waveform-player@^1.27.0, the release that added the /no-autoinit entry point. This is the first hard floor in the family rather than the usual soft one: on an older core the subpath does not exist, so it fails at mount, in the browser. Bump the core alongside this package.
  • crossOrigin prop. Exposes the option added in @arraypress/waveform-player@1.23.0: sets the CORS mode of the underlying <audio> ('anonymous' | 'use-credentials'). Declared as a runtime prop and omitted from the options bag by default so the player behaves like a native <audio> and never forces a CORS request that would break CDN media without Access-Control-Allow-Origin.
  • buttonRadius and artworkPosition props. Exposes the two options added in @arraypress/waveform-player 1.22.0: buttonRadius sets the play button’s corner radius (0 for square, or any CSS length), and artworkPosition ('info' | 'button') chooses whether the cover renders in the info row or becomes the play button itself.

    Both prop types already flowed through automatically, since the props derive from the core’s WaveformPlayerOptions — but the options mapping is written by hand, so until now they would have type-checked and then silently done nothing. Tests now cover the mapping for exactly that reason.

    The peer range stays ^1.20.0: the props only appear once the consumer’s own core is on 1.22.0, so nothing breaks on an older one.

  • buttonSize accepts a number again. The prop was declared type: String, so :button-size="64" failed Vue’s runtime type check and warned, even though the core accepts a number (px) or a unit string. It now declares [String, Number], matching the core. buttonRadius takes the same pair.
  • Forward the core player’s new localizable UI-string options — seekValueText, playPauseLabel, speedLabel, artworkAlt, and unknownTrackText — through to the underlying player. Requires @arraypress/waveform-player@^1.20.0.

Initial release.

  • <WaveformPlayer> Vue 3 component wrapping every option exposed by @arraypress/waveform-player as a typed prop:
    • Audio source (url, src alias, audioMode, preload)
    • Waveform visualisation (waveformStyle, height, samples, barWidth, barSpacing, barRadius, waveform)
    • Colours (colorPreset, waveformColor, progressColor, waveformGradient — strings or string[] gradients). DOM chrome (button, title, meta text) is themed via CSS variables (--wfp-button-color, --wfp-text-color, --wfp-text-secondary-color), not JS options.
    • Playback (playbackRate, showPlaybackSpeed, playbackRates)
    • UI toggles (showControls, showInfo, showTime, showHoverTime, showBPM, buttonAlign, accessibleSeek, seekLabel, errorText)
    • Markers (markers, showMarkers)
    • Metadata (title, artist, artwork, album)
    • Behaviour (autoplay, singlePlay, playOnSeek, enableMediaSession)
    • Icons (playIcon, pauseIcon)
  • Lifecycle emits (load, play, pause, end, timeupdate, error), each forwarding the live WaveformPlayer instance. Wired through Vue’s stable emit, so listeners can change without tearing the player down.
  • Imperative API exposed via a template ref (WaveformPlayerExpose): play(), pause(), togglePlay(), seekTo(), seekToPercent(), setVolume(), setPlaybackRate(), setPlayingState(), setProgress(), loadTrack(), plus the raw instance.
  • class, style, and id fall through to the host element via Vue’s attribute inheritance; the base class wfp-host always applies.
  • SSR / Nuxt safe: the core library is loaded via dynamic import('@arraypress/waveform-player') inside onMounted so the browser-only audio surface never runs server-side.
  • Identity-prop re-mount: when any library-construction prop changes, the wrapper destroys the existing instance and creates a new one with the updated options. A monotonic mount token discards any in-flight async import that a newer mount (or unmount) has superseded.
  • Public types are adopted from the core @arraypress/waveform-player (WaveformStyle, ColorPreset, AudioMode, AudioPreload, ButtonAlign, WaveformMarker, WaveformPeaks), re-exported here so the wrapper’s types can never drift out of sync. WaveformPlayerProps is derived from the core’s WaveformPlayerOptions.
  • Dual ESM (dist/index.js) + CJS (dist/index.cjs) build via tsup, with .d.ts for both. Vue + the core library are externalised so they resolve to the consumer’s copies.
  • Vitest test suite (jsdom + @vue/test-utils) covering mount, option pass-through, the src → url alias, boolean-prop omission, emit forwarding, destroy-on-unmount, identity-prop re-mount, and the exposed imperative API. The core is mocked at the module boundary because jsdom has no Web Audio API.
  • README with full prop reference, seven usage patterns, and the imperative-ref control example. examples/basic.vue with seven copy-paste-ready snippets.
  • onnexttrack / onprevioustrack callback props. Supplying one shows the lock-screen / system media controls’ skip button and forwards to the core’s onNextTrack / onPreviousTrack. The core options were already typed on WaveformPlayerProps but never forwarded (they fell into ...rest), so the buttons never appeared. They’re passed only when set — the core shows the button whenever the option is a function — and adding or removing one remounts the player, while swapping in a fresh inline handler doesn’t.
  • Forwarding-drift test. test/forwarding-drift.test.ts enumerates the installed core’s option surface (DEFAULT_OPTIONS plus the WaveformPlayerOptions keys) and fails for any option that isn’t forwarded, doesn’t remount on change, or isn’t listed in NOT_FORWARDED with a reason — so the next core option can’t be dropped the way the ones above were. Remounts are checked through a fine-grained parent (test/Harness.svelte), because testing-library’s rerender() invalidates every prop at once. Test-only; adds @types/node as a dev dependency.
  • style is typed as the host <div>’s CSS attribute. WaveformPlayerProps now omits the core’s style (a shorthand alias for waveformStyle), which typed style as a WaveformStyle while the component spread it onto the element as CSS. Matches the other wrappers: use waveformStyle for the visual style. The camelCase onNextTrack / onPreviousTrack are omitted too, in favour of the lowercase props above.
  • waveformGradient and seekHandle now reach the player. Both have been typed props since the core added them (1.18.0 / 1.17.0), but they weren’t in the $props() destructure, so they fell into ...rest and were spread onto the host <div> as attributes instead.
  • setPlaybackRate documents the real range — 0.25..4, what the core clamps to — instead of 0.5..2.
  • Changing only class no longer strips the player’s own classes. The core writes classes onto the host — waveform-player, waveform-layout-preview, waveform-theme-light, waveform-is-placeholder — and a class-only change (correctly) doesn’t remount, so when Svelte rewrote the class attribute those were gone until some other prop happened to change, leaving an unstyled player. The host now binds a class value frozen at init (server markup and hydration are unchanged) and later class changes are applied with classList, adding and removing only the user’s tokens.
  • class and wfp-host survive mount. The core’s createDOM() replaces the host’s whole class list with waveform-player, so both were silently dropped as soon as the player built. They’re re-applied right after construction. The DOM structure is unchanged: still one host <div>, which is also the .waveform-player root. (Mounting the core into an inner element was considered and rejected: --wfp-* variables set through style or class would then sit on a parent, shadowed by the core’s own .waveform-player defaults.)
  • Loads @arraypress/waveform-player/no-autoinit instead of the package root. Importing the root scans the whole document for [data-waveform-player] markup and builds a player for every match. This component constructs its own player on its own ref and wants none of that. In a pure Svelte app the scan found nothing and merely cost a querySelectorAll; as an island on a page that does carry such markup — a CMS page, a WordPress template, an Astro or Rails view — mounting this component silently mounted players the Svelte app never asked for, and owned them for the rest of the page’s life. Same class, same options, same behaviour for everything this component builds; the only thing that changes is that nothing else on the page gets touched.
  • Peer floor raised to @arraypress/waveform-player@^1.27.0, the release that added the /no-autoinit entry point. This is the first hard floor in the family rather than the usual soft one: on an older core the subpath does not exist, so it fails at mount, in the browser. Bump the core alongside this package.
  • crossOrigin prop. Exposes the option added in @arraypress/waveform-player@1.23.0: sets the CORS mode of the underlying <audio> ('anonymous' | 'use-credentials'). Omitted from the options bag by default so the player behaves like a native <audio> and never forces a CORS request that would break CDN media without Access-Control-Allow-Origin.
  • buttonRadius and artworkPosition props. Exposes the two options added in @arraypress/waveform-player 1.22.0: buttonRadius sets the play button’s corner radius (0 for square, or any CSS length), and artworkPosition ('info' | 'button') chooses whether the cover renders in the info row or becomes the play button itself.

    Both prop types already flowed through automatically, since the props derive from the core’s WaveformPlayerOptions — but the options mapping is written by hand, so until now they would have type-checked and then silently done nothing. Tests now cover the mapping for exactly that reason.

    The peer range stays ^1.20.0: the props only appear once the consumer’s own core is on 1.22.0, so nothing breaks on an older one.

  • Forward the core player’s new localizable UI-string options — seekValueText, playPauseLabel, speedLabel, artworkAlt, and unknownTrackText — through to the underlying player. Requires @arraypress/waveform-player@^1.20.0.

Initial release.

  • <WaveformPlayer> Svelte 5 component (built with runes) wrapping every option exposed by @arraypress/waveform-player as a typed prop:
    • Audio source (url, src alias, audioMode, preload)
    • Waveform visualisation (waveformStyle, height, samples, barWidth, barSpacing, barRadius, waveform)
    • Colours (colorPreset, waveformColor, progressColor — strings or string[] gradients). DOM chrome (button, title, meta text) is themed via CSS variables (--wfp-button-color, --wfp-text-color, --wfp-text-secondary-color), not JS options.
    • Playback (playbackRate, showPlaybackSpeed, playbackRates)
    • UI toggles (showControls, showInfo, showTime, showHoverTime, showBPM, buttonAlign, accessibleSeek, seekLabel, errorText)
    • Markers (markers, showMarkers)
    • Metadata (title, artist, artwork, album)
    • Behaviour (autoplay, singlePlay, playOnSeek, enableMediaSession)
    • Icons (playIcon, pauseIcon)
  • Lowercase lifecycle callback props (onload, onplay, onpause, onend, ontimeupdate, onerror), each forwarding the live WaveformPlayer instance. Wired through reactive closures, so changing a handler never tears the player down.
  • Imperative API exported by the component instance (reachable via bind:this): play(), pause(), togglePlay(), seekTo(), seekToPercent(), setVolume(), setPlaybackRate(), setPlayingState(), setProgress(), loadTrack(), and getInstance().
  • class, style, id, and other element attributes fall through to the host element via ...rest; the base class wfp-host always applies.
  • SSR / SvelteKit safe: the core library is loaded via dynamic import('@arraypress/waveform-player') inside a browser-only $effect, so the audio surface never runs server-side.
  • Identity-prop re-mount: a single $effect reads every construction prop, so changing any of them destroys the existing instance and creates a new one. A monotonic mount token discards any in-flight async import that a newer mount (or unmount) has superseded.
  • Public types adopted from the core @arraypress/waveform-player (WaveformStyle, ColorPreset, AudioMode, AudioPreload, ButtonAlign, WaveformMarker, WaveformPeaks), re-exported here so the wrapper’s types can never drift. WaveformPlayerProps is derived from the core’s WaveformPlayerOptions.
  • Built with svelte-package (dist/ ships the preprocessed .svelte + generated .d.ts). Svelte + the core library are peer dependencies.
  • Vitest test suite (jsdom + @testing-library/svelte) covering mount, option pass-through, the src → url alias, boolean-prop omission, callback forwarding, destroy-on-unmount, identity-prop re-mount, and the exported imperative API. The core is mocked at the module boundary because jsdom has no Web Audio API.
  • README with full prop reference, seven usage patterns, and the imperative bind:this control example. examples/Basic.svelte with seven copy-paste-ready snippets.
  • waveformGradient config key ('vertical' | 'horizontal' | 'diagonal'). The bar has forwarded it to the embedded player since 1.8.0, but it was missing from WaveformBarConfig, so TypeScript rejected a working option.
  • barRadius config key (number | null) — rounded bar caps in px, 0 = square, null = the player’s default. Requires @arraypress/waveform-bar 1.12.0+; older bars ignore it.
  • Peer floors raised: @arraypress/waveform-bar ^1.10.0 → ^1.11.0 and @arraypress/waveform-player ^1.7.2 → ^1.8.0. WaveformBarConfig has declared crossOrigin since 0.4.0, but the bar only accepts it from 1.11.0; and the bar drives audioMode: 'external' inline players, which crashed before core player 1.8.0. The optional barRadius / working showTime (bar 1.12.0) don’t raise the floor — older bars ignore them.
  • waveformColor / progressColor accept gradient stop arrays. They were typed string | null, but the bar forwards them verbatim to a player that takes string | string[] | null — ['#fafafa', '#71717a'] works at runtime and now typechecks.
  • showTime doc comment notes that false actually hides the time display from @arraypress/waveform-bar 1.12.0 (earlier bars ignored it).
  • The barSpacing doc comment claimed @default 0; the bar’s default is 2.
  • crossOrigin config key. Added to WaveformBarConfig and forwarded verbatim to window.WaveformBar.init(). Requires @arraypress/waveform-bar@^1.11.0 for it to reach the embedded player.
  • Accept the new localizable player-string keys — seekValueText, playPauseLabel, speedLabel, artworkAlt, and unknownTrackText — in the bar config; they are forwarded to the embedded player. Requires @arraypress/waveform-bar@^1.10.0.
  • Sync WaveformBarConfig to the bar’s mode API: layout → mode ('waveform' | 'classic'), remove maxWidth, add showShuffle / shuffle. Matches @arraypress/waveform-bar 1.7.0.
  • Bumped the @arraypress/waveform-player peer (and dev) dependency to ^1.7.2, which adds the native accessible keyboard / ARIA seek slider to the underlying player. No component API changes.
  • Widened the astro peerDependency to ^6.0.0 || ^7.0.0 for Astro 7 readiness. No runtime changes — the component is unaffected by the Astro 7 compiler / Vite 8 (Rolldown) upgrade.
  • <WaveformBarTrigger>’s default pause-icon SVG no longer carries an inline style="display:none;". The previous value beat the core library’s class-based toggle (.wb-icon-swap.wb-playing .wb-show-pause { display: inline; }), leaving the pause icon permanently hidden once a track started playing. The library’s own CSS already covers the initial-hidden state via .wb-icon-swap .wb-show-pause { display: none; }, so the inline style is redundant as well as harmful.
  • Added two regression tests pinning the no-inline-display contract so the bug can’t return.

Initial release.

  • <WaveformBar> — singleton mount component for the persistent bottom bar. Renders a transition:persist host div and an inline init script that calls window.WaveformBar.init(config) on every astro:page-load. Relocates the library’s .waveform-bar element into the persist host so view transitions keep it onscreen between navigations.
  • Typed WaveformBarConfig covering every option window.WaveformBar.init() accepts:
    • Persistence: persist, autoResume, continuous, repeat
    • UI toggles: showQueue, showPrevNext, showRepeat, showVolume, showMute, showTime, showTrackLink, showMeta, maxMeta
    • Theming: theme, defaultArtwork
    • Waveform visualisation: waveformStyle, waveformHeight, barWidth, barSpacing, waveformColor, progressColor, markerColor
    • Volume + persistence keys: volume, storageKey
    • Server-side actions: actions.favorite / actions.cart with endpoint URL + optional method / headers
  • <WaveformBarTrigger> — polymorphic click trigger. Renders a <button> by default; override via as="a" | "div" | "span". Emits the full data-wb-* attribute contract:
    • Track identity: url, id (falls back to url), title, artist, album, artwork, link
    • Display chips: duration, bpm, key, meta
    • Audio data: waveform (peaks array, URL, or inline JSON), markers (DJ-mode chapters)
    • Initial state: favorited, inCart
    • Behaviour: mode='play' | 'queue', href (when as="a"), ariaLabel, class, noDefaultIcons
  • Default slot ships the play / pause SVG pair the core library toggles via wb-show-play / wb-show-pause classes. Passing children replaces them.
  • Auto-generated aria-label when one isn’t supplied — Play {title} for play triggers, Add {title} to queue for queue triggers.
  • Public TypeScript types: WaveformBarProps, WaveformBarConfig, WaveformBarTriggerProps, WaveformBarTrackData, WaveformBarMarker, WaveformBarActions, WaveformBarAction, WaveformBarTheme, RepeatMode, TriggerMode, WaveformStyle.
  • Ambient declaration for window.WaveformBar covering the full public surface (play, pause, next, previous, addToQueue, setVolume, setRepeat, seekToMarker, etc.).
  • Vitest suite of 46 tests via Astro’s experimental_AstroContainer covering attribute mapping, omission semantics, JSON serialisation for arrays, polymorphic as, default-slot behaviour, aria-label generation, and a kitchen-sink scenario.
  • Documentation: full README with setup, prop tables, every usage pattern, and the waveformbar:* custom-event API. examples/basic.astro reference page with six demonstrations.
  • waveformGradient config key ('vertical' | 'horizontal' | 'diagonal'). The bar has forwarded it to the embedded player since 1.8.0, but it was missing from WaveformBarConfig, so TypeScript rejected a working option.
  • barRadius config key (number | null) — rounded bar caps in px, 0 = square, null = the player’s default. Requires @arraypress/waveform-bar 1.12.0+; older bars ignore it.
  • Peer floors raised: @arraypress/waveform-bar ^1.10.0 → ^1.11.0 and @arraypress/waveform-player ^1.7.2 → ^1.8.0. WaveformBarConfig has declared crossOrigin since 0.4.0, but the bar only accepts it from 1.11.0; and the bar drives audioMode: 'external' inline players, which crashed before core player 1.8.0. The optional barRadius / working showTime (bar 1.12.0) don’t raise the floor — older bars ignore them.
  • waveformColor / progressColor accept gradient stop arrays. They were typed string | null, but the bar forwards them verbatim to a player that takes string | string[] | null — ['#fafafa', '#71717a'] works at runtime and now typechecks.
  • showTime doc comment notes that false actually hides the time display from @arraypress/waveform-bar 1.12.0 (earlier bars ignored it).
  • crossOrigin config key. Added to WaveformBarConfig and forwarded verbatim to window.WaveformBar.init(). Requires @arraypress/waveform-bar@^1.11.0 for it to reach the embedded player.
  • Accept the new localizable player-string keys — seekValueText, playPauseLabel, speedLabel, artworkAlt, and unknownTrackText — in the bar config; they are forwarded to the embedded player. Requires @arraypress/waveform-bar@^1.10.0.
  • Sync WaveformBarConfig to the bar’s mode API: layout → mode ('waveform' | 'classic'), remove maxWidth, add showShuffle / shuffle. Matches @arraypress/waveform-bar 1.7.0.
  • Bumped the @arraypress/waveform-player peer (and dev) dependency to ^1.7.2 for the native accessible keyboard / ARIA seek slider. No component API changes.

Initial release.

  • <WaveformBar> — singleton mount component for the persistent bottom bar. Renders a persist host <div> and runs window.WaveformBar.init(config) inside an effect. Tears down on unmount (StrictMode-safe) and re-inits only when the structural shape of the config changes — passing a fresh object reference with the same shape doesn’t trigger churn. Relocates the bar element into the persist host so route changes / re-renders don’t tear it down.
  • <WaveformBarTrigger> — polymorphic click trigger. Defaults to <button>; override via as="a" | "div" | "span". Emits the full data-wb-* attribute contract:
    • Track identity: url, id, title, artist, album, artwork, link
    • Display chips: duration, bpm, musicalKey, meta
    • Audio data: waveform (peaks array, URL, or inline JSON), markers (DJ-mode chapters)
    • Initial state: favorited, inCart
    • Behaviour: mode='play' | 'queue', href (when as="a"), aria-label, className, style, noDefaultIcons, children
  • Default play / pause SVG pair rendered as the trigger’s children when no custom content is passed. The library’s wb-icon-swap CSS toggles them based on the active track state.
  • Auto-generated aria-label when one isn’t supplied — Play {title} for play triggers, Add {title} to queue for queue triggers.
  • Public TypeScript types: WaveformBarProps, WaveformBarConfig, WaveformBarTriggerProps, WaveformBarTrackData, WaveformBarMarker, WaveformBarActions, WaveformBarAction, WaveformBarTheme, RepeatMode, TriggerMode, WaveformStyle.
  • Ambient module shim for @arraypress/waveform-bar and @arraypress/waveform-player so the wrapper typechecks cleanly until the core libraries ship .d.ts of their own.
  • SSR / RSC safe: the core library loads via dynamic import() inside the effect, so the browser-only audio surface never evaluates server-side.
  • 46 Vitest tests via jsdom + @testing-library/react:
    • 28 tests for <WaveformBarTrigger> rendering + attribute mapping (no module mocking needed)
    • 18 tests for <WaveformBar> lifecycle (window global mocked since jsdom has no Web Audio)
  • Dual ESM (dist/index.js) + CJS (dist/index.cjs) build via tsup. .d.ts for both. React + the two core libraries externalised so they resolve to the consumer’s copies.
  • README with full prop reference, seven usage patterns, and the waveformbar:* custom-event API documented as the callback alternative.
  • examples/basic.tsx with seven copy-paste-ready snippets.
  • waveformGradient config key ('vertical' | 'horizontal' | 'diagonal'). The bar has forwarded it to the embedded player since 1.8.0, but it was missing from WaveformBarConfig, so TypeScript rejected a working option.
  • barRadius config key (number | null) — rounded bar caps in px, 0 = square, null = the player’s default. Requires @arraypress/waveform-bar 1.12.0+; older bars ignore it.
  • Peer floors raised: @arraypress/waveform-bar ^1.10.0 → ^1.11.0 and @arraypress/waveform-player ^1.7.2 → ^1.8.0. WaveformBarConfig has declared crossOrigin since 0.3.0, but the bar only accepts it from 1.11.0; and the bar drives audioMode: 'external' inline players, which crashed before core player 1.8.0. The optional barRadius / working showTime (bar 1.12.0) don’t raise the floor — older bars ignore them.
  • waveformColor / progressColor accept gradient stop arrays. They were typed string | null, but the bar forwards them verbatim to a player that takes string | string[] | null — ['#fafafa', '#71717a'] works at runtime and now typechecks.
  • showTime doc comment notes that false actually hides the time display from @arraypress/waveform-bar 1.12.0 (earlier bars ignored it).
  • crossOrigin config key. Added to WaveformBarConfig and forwarded verbatim to window.WaveformBar.init(). Requires @arraypress/waveform-bar@^1.11.0 for it to reach the embedded player.
  • Accept the new localizable player-string keys — seekValueText, playPauseLabel, speedLabel, artworkAlt, and unknownTrackText — in the bar config; they are forwarded to the embedded player. Requires @arraypress/waveform-bar@^1.10.0.

Initial release.

  • <WaveformBar> — singleton mount for the persistent bottom bar. Render once in your root layout. On mount it dynamically imports @arraypress/waveform-bar (SSR-safe) and calls window.WaveformBar.init(config); re-inits when the config’s structural shape changes (compared via JSON.stringify, so a fresh object with the same shape doesn’t churn); relocates the bar element into a persist host <div>; and calls destroy() on unmount. config, persist, hostId props; class / style fall through to the host (base class wb-host).
  • <WaveformBarTrigger> — polymorphic (as="button" | "a" | "div" | "span", default button) click trigger that emits the data-wb-* attribute contract the core library scans for. Maps track-data props (url, id, title, artist, album, artwork, link, duration, bpm, musicalKey, meta, waveform, markers, favorited, inCart) to attributes (arrays JSON-encoded; absent props emit no attribute). mode="play" | "queue", href (for as="a"), noDefaultIcons. Renders default play/pause SVGs unless slot content is provided. Auto-generates an aria-label from title. class, native listeners (@click), and other attributes fall through; the base class wb-icon-swap is always applied.
  • No lifecycle callback props — the bar dispatches every state change as a bubbling waveformbar:* CustomEvent; listen with addEventListener. This keeps callbacks from forcing a bar re-init (matching the React wrapper).
  • Public types mirroring the core surface: WaveformBarConfig, WaveformBarProps, WaveformBarTriggerProps, WaveformBarTrackData, WaveformBarMarker, WaveformBarActions, WaveformBarAction, WaveformBarTheme, RepeatMode, TriggerMode, WaveformStyle.
  • Dual ESM + CJS build via tsup with .d.ts for both. Vue + the core libraries are peer dependencies.
  • Vitest test suite (jsdom + @vue/test-utils, 13 tests) covering the singleton’s host render + init / re-init / destroy lifecycle, and the trigger’s data-wb-* contract, polymorphism, modes, default-icon handling, class merge, and click forwarding.
  • waveformGradient config key ('vertical' | 'horizontal' | 'diagonal'). The bar has forwarded it to the embedded player since 1.8.0, but it was missing from WaveformBarConfig, so TypeScript rejected a working option.
  • barRadius config key (number | null) — rounded bar caps in px, 0 = square, null = the player’s default. Requires @arraypress/waveform-bar 1.12.0+; older bars ignore it.
  • Peer floors raised: @arraypress/waveform-bar ^1.10.0 → ^1.11.0 and @arraypress/waveform-player ^1.7.2 → ^1.8.0. WaveformBarConfig has declared crossOrigin since 0.3.0, but the bar only accepts it from 1.11.0; and the bar drives audioMode: 'external' inline players, which crashed before core player 1.8.0. The optional barRadius / working showTime (bar 1.12.0) don’t raise the floor — older bars ignore them.
  • waveformColor / progressColor accept gradient stop arrays. They were typed string | null, but the bar forwards them verbatim to a player that takes string | string[] | null — ['#fafafa', '#71717a'] works at runtime and now typechecks.
  • showTime doc comment notes that false actually hides the time display from @arraypress/waveform-bar 1.12.0 (earlier bars ignored it).
  • crossOrigin config key. Added to WaveformBarConfig and forwarded verbatim to window.WaveformBar.init(). Requires @arraypress/waveform-bar@^1.11.0 for it to reach the embedded player.
  • Accept the new localizable player-string keys — seekValueText, playPauseLabel, speedLabel, artworkAlt, and unknownTrackText — in the bar config; they are forwarded to the embedded player. Requires @arraypress/waveform-bar@^1.10.0.

Initial release.

  • <WaveformBar> — singleton mount (Svelte 5 runes) for the persistent bottom bar. Render once in your root layout. A browser-only $effect dynamically imports @arraypress/waveform-bar (SSR-safe) and calls window.WaveformBar.init(config); a $derived config key re-inits only when the config’s structural shape (or persist) changes; the bar element is relocated into a persist host <div>; destroy() is called on unmount. config, persist, hostId props; class / attributes fall through to the host (base class wb-host).
  • <WaveformBarTrigger> — polymorphic (as="button" | "a" | "div" | "span" via <svelte:element>, default button) click trigger that emits the data-wb-* attribute contract the core library scans for. Maps track-data props (url, id, title, artist, album, artwork, link, duration, bpm, musicalKey, meta, waveform, markers, favorited, inCart) to attributes (arrays JSON-encoded; absent props emit no attribute). mode="play" | "queue", href (for as="a"), noDefaultIcons. Renders default play/pause SVGs unless children are slotted. Auto-generates an aria-label from title. class, native listeners (onclick), and other attributes fall through via ...rest; the base class wb-icon-swap is always applied.
  • No lifecycle callback props — the bar dispatches every state change as a bubbling waveformbar:* CustomEvent; listen with addEventListener.
  • Public types mirroring the core surface: WaveformBarConfig, WaveformBarProps, WaveformBarTriggerProps, WaveformBarTrackData, WaveformBarMarker, WaveformBarActions, WaveformBarAction, WaveformBarTheme, RepeatMode, TriggerMode, WaveformStyle.
  • Built with svelte-package (dist/ ships the preprocessed .svelte + generated .d.ts). Svelte + the core libraries are peer dependencies.
  • Vitest test suite (jsdom + @testing-library/svelte, 12 tests) covering the singleton’s host render + init / re-init / no-churn / destroy lifecycle, and the trigger’s data-wb-* contract, polymorphism, modes, default-icon handling, class merge, and click forwarding.
  • The hero and grid layouts: layout now accepts 'hero' and 'grid' alongside 'list' / 'minimal' (it was typed 'list' | 'minimal' although both ship in the core), and their options are typed props, emitted as the container attributes the playlist parses: showArtist (data-show-artist), coverSize (data-cover-size), thumbnailSize (data-thumbnail-size), density ('comfortable' | 'compact', data-density), coverPosition ('left' | 'top', data-cover-position) and barPosition ('top' | 'bottom', data-bar-position). Exported as WaveformPlaylistLayoutProps. Types come from the playlist core’s index.d.ts (1.8.0 declares them all); against an older core they fall back to the same 1.8.0 shapes instead of degrading to unknown.
  • Per-track waveform peaks on WaveformPlaylistTrackInput (number[] | string), rendered as the track’s data-waveform: an array is JSON-encoded (an empty one is omitted), a string (e.g. a .json peaks URL) emitted verbatim. With peaks the player skips decoding that track’s audio. Playlist 1.8.0 is the first version that reads data-waveform.
  • waveformGradient, seekHandle, buttonSize, buttonRadius and artworkPosition reach the embedded player, as data-waveform-gradient, data-seek-handle, data-button-size, data-button-radius and data-artwork-position. All five are core player options the props type inherited, but the component never destructured or emitted them — they typechecked and were silently dropped. buttonRadius={0} emits "0".
  • Requires @arraypress/waveform-playlist@^1.8.0 (was ^1.7.2) — the upcoming release that makes these props work. It is the first version that reads the per-track data-waveform this wrapper now emits, and it fixes the hero layout’s cover art for artless tracks, the chapter list / play-state overlay landing on the wrong track, chapter seeks into another track or under preload: 'none', H:MM:SS chapter times, the lock-screen album sticking between tracks, and keyboard shortcuts hijacking Cmd/Ctrl combinations. The range also drops 1.7.2 and 1.7.3, where a hero / grid container’s data-layout leaked into the embedded player (fixed in 1.7.4).
  • Requires @arraypress/waveform-player@^1.24.5 (was ^1.23.0), the playlist core’s own floor.
  • Forward the core player’s crossOrigin option to the embedded player. Added to the Astro.props destructure and emitted as the data-cross-origin container attribute. This option shipped across the rest of the waveform family in @arraypress/waveform-player@1.23.0 but was missed in the playlist wrappers, so it was previously accepted by the types and silently dropped at runtime. Requires @arraypress/waveform-player@^1.23.0 and @arraypress/waveform-playlist@^1.7.2 (the version that began forwarding it to each track’s player).
  • Forward the core player’s new localizable UI-string options — seekValueText, playPauseLabel, speedLabel, artworkAlt, and unknownTrackText — through to the underlying player. Requires @arraypress/waveform-player@^1.20.0.
  • <WaveformPlaylist> Astro component wrapping @arraypress/waveform-playlist. Renders the library’s nested markup contract — a [data-waveform-playlist] container with one [data-track] child per track and one [data-chapter] child per chapter — from a single typed tracks array.
  • Typed props for the playlist’s own options:
    • layout ('list' | 'minimal'), continuous, expandChapters, showDuration, showChapterMarkers (boolean | null), chapterMarkerColor, showPlayState.
  • Typed pass-through of the WaveformPlayer options the playlist forwards to its embedded player — waveformStyle, height, samples, barWidth, barSpacing, barRadius, colorPreset, the colour options (including gradient-array waveformColor / progressColor), playbackRate, showPlaybackSpeed, playbackRates, showControls, showInfo, showTime, showHoverTime, showBPM, bpm, buttonAlign, buttonStyle, accessibleSeek, seekLabel, errorText, showMarkers, autoplay, singlePlay, playOnSeek, enableMediaSession, preload, playIcon, pauseIcon. The per-track content fields (url, title, artist, artwork, album, markers) live on each tracks entry instead, and the player’s layout / audioMode are intentionally not exposed (the playlist owns data-layout and always drives a self-mode player).
  • A typed tracks array (WaveformPlaylistTrackInput[]) with per-track url, title, artist, artwork, album, duration, markers, and chapters ({ time, label, color? }).
  • Astro-specific lazy prop that switches the init attribute to data-waveform-playlist-lazy and ships a single deduplicated IntersectionObserver that promotes the playlist on viewport entry, plus a non-lazy astro:page-load re-init for Astro View Transitions.
  • Astro-specific id, class, and style pass-throughs (wfpl-host is always applied to the container).
  • Public TypeScript types derived from the two core packages so they never drift: WaveformPlaylistProps, WaveformPlaylistTrackInput, and re-exports of WaveformPlaylistOptions / WaveformPlaylistTrack / WaveformPlaylistChapter / WaveformPlaylistMarker (from the playlist core) and WaveformStyle / WaveformMarker / WaveformPeaks / ColorPreset / AudioMode / AudioPreload / ButtonAlign (from the player core).
  • Vitest suite covering container option serialisation, omission semantics, JSON serialisation for array props, per-track and per-chapter rendering, lazy-mount and View-Transitions script presence, and the Astro extras.
  • Documentation: full prop reference, setup guide, and usage examples (examples/basic.astro).
  • The hero and grid layouts: layout now accepts 'hero' and 'grid' alongside 'list' / 'minimal' (it was typed 'list' | 'minimal' although both ship in the core), and their options are typed props, forwarded to the playlist and in the remount dependency array: showArtist, coverSize, thumbnailSize, density ('comfortable' | 'compact'), coverPosition ('left' | 'top') and barPosition ('top' | 'bottom'). Exported as WaveformPlaylistLayoutProps. Types come from the playlist core’s index.d.ts (1.8.0 declares them all); against an older core they fall back to the same 1.8.0 shapes instead of degrading to unknown.
  • Player callback props — onLoad, onPlay, onPause, onEnd, onTimeUpdate, onError, onNextTrack, onPreviousTrack — forwarded to the embedded player. Playlist 1.8.0 runs them after its own handling (before, it overwrote them, which is why the wrapper didn’t offer them; onNextTrack / onPreviousTrack were even accepted by the props type and silently dropped). Handed over as stable trampolines that read the latest prop, so a new handler never re-mounts the playlist.
  • Per-track waveform peaks on WaveformPlaylistTrackInput (number[] | string), rendered as the track’s data-waveform: an array is JSON-encoded, a string (e.g. a .json peaks URL) passed through. With peaks the player skips decoding that track’s audio. Playlist 1.8.0 is the first version that reads data-waveform.
  • waveformGradient, seekHandle, buttonSize, buttonRadius and artworkPosition reach the embedded player. All five are core player options the props type inherited, but the options builder never forwarded them — they typechecked and were silently dropped. They are now forwarded and in the remount dependency array.
  • Changing only className no longer strips the playlist’s own host classes (waveform-playlist, wp-hero-layout, wp-grid-layout, wp-density-compact, wp-cover-top, wp-no-artist, wp-minimal). A className-only change (correctly) doesn’t remount, so when React rewrote the class attribute those were gone until some other prop changed — hero/grid layouts collapsed and density/artist styling reverted. React now renders class once (server markup and hydration are unchanged) and later className changes are applied with classList, adding and removing only the user’s tokens. The DOM structure is unchanged — the tracks and the playlist UI still live directly in the one host <div>. (Mounting the playlist into an inner element was considered and rejected: it would break .your-class.waveform-playlist selectors and push CSS variables set via style / className onto a parent, where the core’s defaults shadow them.)
  • Requires @arraypress/waveform-playlist@^1.8.0 (was ^1.7.2) — the upcoming release that makes these props work. Before it, the playlist ignored the constructor options this wrapper passes (expandChapters, showDuration, showPlayState, showChapterMarkers, chapterMarkerColor), leaked layout into the embedded player (fixed in 1.7.4), overwrote the forwarded callbacks, never read data-waveform, and its destroy() wiped the rendered tracks, so any prop change re-mounted an empty playlist. With 1.8.0 a re-mount keeps the tracks (now covered by a test).
  • Requires @arraypress/waveform-player@^1.24.5 (was ^1.23.0), the playlist core’s own floor.
  • The audioMode prop. The playlist always owns its audio, and an 'external' embedded player dispatches request-play events nobody answers — a playlist that never plays. It was forwarded to the constructor; @arraypress/waveform-playlist@1.8.0 ignores it, and the wrapper no longer accepts or forwards it.
  • Forward the core player’s crossOrigin option to the embedded player. Forwarded in the constructor options builder and added to the remount useEffect dependency array. This option shipped across the rest of the waveform family in @arraypress/waveform-player@1.23.0 but was missed in the playlist wrappers, so it was previously accepted by the types and silently dropped at runtime. Requires @arraypress/waveform-player@^1.23.0 and @arraypress/waveform-playlist@^1.7.2 (the version that began forwarding it to each track’s player).
  • Forward the core player’s new localizable UI-string options — seekValueText, playPauseLabel, speedLabel, artworkAlt, and unknownTrackText — through to the underlying player. Requires @arraypress/waveform-player@^1.20.0.

Initial release.

  • <WaveformPlaylist> React component wrapping @arraypress/waveform-playlist:
    • A declarative, required tracks array (WaveformPlaylistTrackInput[]) rendered into the [data-track] / [data-chapter] child markup the playlist constructor parses on mount. Each track accepts url, title, artist, artwork, album, duration, markers, and chapters ({ time, label, color? }, where time is a seconds number or a 'M:SS' string).
    • Playlist options as typed props: layout ('list' | 'minimal'), continuous, expandChapters, showDuration, showChapterMarkers, chapterMarkerColor, showPlayState.
    • Pass-through player options forwarded to the embedded player (waveformStyle, height, samples, barWidth, barSpacing, barRadius, colours, playbackRate, showPlaybackSpeed, playbackRates, UI toggles, accessibleSeek, seekLabel, errorText, behaviour flags, icons, audioMode, preload).
    • React-specific extras: id, className, style, and ref forwarding via WaveformPlaylistHandle.
  • WaveformPlaylistHandle imperative API on the forwarded ref — selectTrack(), seekToChapter(), nextTrack(), previousTrack(), getPlayer(), getCurrentTrackIndex(), getTracks(), plus the raw instance escape hatch.
  • SSR / RSC safe: the playlist library is loaded via dynamic import('@arraypress/waveform-playlist') inside the effect so the browser-only audio surface never runs server-side.
  • Identity-prop re-mount: when any construction prop changes — the serialised tracks, layout, continuous, colours, sizing, etc. — the wrapper destroys the existing instance and creates a new one against the freshly-rendered markup. DOM-only props (className, style, id) do not trigger a re-mount.
  • The host container deliberately omits data-waveform-playlist so the library’s global auto-init never double-mounts on top of the instance the wrapper creates explicitly.
  • Public TypeScript types: WaveformPlaylistProps, WaveformPlaylistHandle, WaveformPlaylistTrackInput, WaveformPlaylistChapterInput, plus the re-exported core types (WaveformPlaylistOptions, WaveformPlaylistTrack, WaveformPlaylistChapter, WaveformPlaylistMarker, WaveformStyle, WaveformMarker, WaveformPeaks, ColorPreset, AudioMode, AudioPreload, ButtonAlign).
  • Vitest test suite (jsdom + @testing-library/react) covering track / chapter markup rendering, mount, unmount destroy, option pass-through, identity-prop re-mount, ref forwarding, and the full imperative handle surface. The core library is mocked at the module boundary because jsdom has no Web Audio API.
  • Dual ESM (dist/index.js) + CJS (dist/index.cjs) build via tsup, with .d.ts for both. React and both @arraypress/waveform-* cores are externalised so they resolve to the consumer’s copies.
  • README with full prop reference and usage patterns, and examples/basic.tsx with seven copy-paste-ready snippets.
  • @arraypress/waveform-playlist ^1.3.0
  • @arraypress/waveform-player ^1.8.0
  • react ^18.0.0 || ^19.0.0
  • The hero and grid layouts: layout now accepts 'hero' and 'grid' alongside 'list' / 'minimal' (both the runtime prop and the type said 'list' | 'minimal' although both ship in the core), and their options are runtime props, forwarded to the playlist and in the remount watcher: showArtist, coverSize, thumbnailSize, density ('comfortable' | 'compact'), coverPosition ('left' | 'top') and barPosition ('top' | 'bottom'). Exported as WaveformPlaylistLayoutProps. Types come from the playlist core’s index.d.ts (1.8.0 declares them all); against an older core they fall back to the same 1.8.0 shapes instead of degrading to unknown.
  • Lifecycle emits — load, play, pause, end, timeupdate, error, nexttrack, previoustrack — with the core player’s arguments, the same idiom as @arraypress/waveform-player-vue. Playlist 1.8.0 runs the embedded player’s callbacks after its own handling (before, it overwrote them, which is why this wrapper offered none). emit is stable, so a new listener never re-mounts the playlist.
  • Per-track waveform peaks on WaveformPlaylistTrackInput (number[] | string), rendered as the track’s data-waveform: an array is JSON-encoded, a string (e.g. a .json peaks URL) passed through. With peaks the player skips decoding that track’s audio. Playlist 1.8.0 is the first version that reads data-waveform.
  • waveformGradient, seekHandle, buttonSize, buttonRadius and artworkPosition reach the embedded player. All five are core player options the props type inherited, but they had no runtime prop declaration — Vue treated them as fallthrough attributes and they never reached the playlist. They are now runtime props, forwarded and in the remount watcher.
  • Changing only the fall-through class no longer strips the playlist’s own host classes (waveform-playlist, wp-hero-layout, wp-grid-layout, wp-density-compact, wp-cover-top, wp-no-artist, wp-minimal). A class-only change (correctly) doesn’t remount, so when Vue re-patched the class attribute those were gone until some other prop changed — hero/grid layouts collapsed and density/artist styling reverted. The component now renders class once (server markup and hydration are unchanged) and applies later changes with classList, adding and removing only the user’s tokens. To keep class out of Vue’s patching it sets inheritAttrs: false and forwards every other attribute (id, style, listeners, data-*) itself — same result on the element. The DOM structure is unchanged — the tracks and the playlist UI still live directly in the one host <div>. (Mounting the playlist into an inner element was considered and rejected: it would break .your-class.waveform-playlist selectors and push CSS variables set via style / class onto a parent, where the core’s defaults shadow them.)
  • Requires @arraypress/waveform-playlist@^1.8.0 (was ^1.7.2) — the upcoming release that makes these props work. Before it, the playlist ignored the constructor options this wrapper passes (expandChapters, showDuration, showPlayState, showChapterMarkers, chapterMarkerColor), leaked layout into the embedded player (fixed in 1.7.4), overwrote the player callbacks the new emits ride on, never read data-waveform, and its destroy() wiped the rendered tracks, so any prop change re-mounted an empty playlist. With 1.8.0 a re-mount keeps the tracks (now covered by a test).
  • Requires @arraypress/waveform-player@^1.24.5 (was ^1.23.0), the playlist core’s own floor.
  • The audioMode prop. The playlist always owns its audio, and an 'external' embedded player dispatches request-play events nobody answers — a playlist that never plays. It was forwarded to the constructor; @arraypress/waveform-playlist@1.8.0 ignores it, and the wrapper no longer declares or forwards it.
  • Forward the core player’s crossOrigin option to the embedded player. Added as a runtime props declaration (PropType<AudioCrossOrigin>), set in the options builder, and added to the remount watcher. This option shipped across the rest of the waveform family in @arraypress/waveform-player@1.23.0 but was missed in the playlist wrappers, so it was previously accepted by the types and silently dropped at runtime. Requires @arraypress/waveform-player@^1.23.0 and @arraypress/waveform-playlist@^1.7.2 (the version that began forwarding it to each track’s player).
  • Forward the core player’s new localizable UI-string options — seekValueText, playPauseLabel, speedLabel, artworkAlt, and unknownTrackText — through to the underlying player. Requires @arraypress/waveform-player@^1.20.0.

Initial release.

  • <WaveformPlaylist> Vue 3 component wrapping @arraypress/waveform-playlist:
    • Declarative tracks prop (with optional per-track chapters and markers), rendered into the [data-track] / [data-chapter] markup the playlist constructor parses on mount.
    • Playlist options as typed props: layout ('list' | 'minimal'), continuous, expandChapters, showDuration, showChapterMarkers, chapterMarkerColor, showPlayState.
    • The full pass-through player-option surface (waveform style, sizing, colours, playback, UI toggles, accessibility, icons) — inherited from the core WaveformPlayerOptions via Omit<>, minus per-track content fields (which come from tracks).
  • Imperative navigation API exposed via a template ref (WaveformPlaylistExpose): selectTrack(), seekToChapter(), nextTrack(), previousTrack(), getPlayer(), getCurrentTrackIndex(), getTracks(), plus the raw instance.
  • class, style, and id fall through to the host element via Vue’s attribute inheritance; the base class wfp-host always applies.
  • SSR / Nuxt safe: the core library is loaded via dynamic import('@arraypress/waveform-playlist') inside onMounted.
  • Identity-prop re-mount: any construction-prop change (a serialised tracks change, layout, options, …) destroys and rebuilds the instance. A monotonic mount token discards a superseded in-flight import; the watcher uses flush: 'post' so the fresh markup is in the DOM before the constructor re-parses it.
  • No lifecycle emits — the playlist owns the embedded player’s callbacks internally (matching the React wrapper). Observe playback via the embedded player from getPlayer().
  • Public types adopted from both cores (@arraypress/waveform-playlist + @arraypress/waveform-player), re-exported so they can never drift.
  • Dual ESM + CJS build via tsup with .d.ts for both. Vue + both cores are peer dependencies.
  • Vitest test suite (jsdom + @vue/test-utils) covering host + track markup rendering, option mapping (tracks excluded), boolean-prop omission, destroy-on-unmount, identity-prop re-mount, and the exposed navigation API. The core is mocked at the module boundary.
  • The hero and grid layouts: layout now accepts 'hero' and 'grid' alongside 'list' / 'minimal' (it was typed 'list' | 'minimal' although both ship in the core), and their options are typed props, destructured from $props() and forwarded to the playlist: showArtist, coverSize, thumbnailSize, density ('comfortable' | 'compact'), coverPosition ('left' | 'top') and barPosition ('top' | 'bottom'). Exported as WaveformPlaylistLayoutProps. Types come from the playlist core’s index.d.ts (1.8.0 declares them all); against an older core they fall back to the same 1.8.0 shapes instead of degrading to unknown.
  • onnexttrack / onprevioustrack callback props (Media Session next/previous), forwarded like the other callbacks. The camelCase onNextTrack / onPreviousTrack the props type used to inherit are removed: they were never destructured, so they fell into ...rest.
  • Per-track waveform peaks on WaveformPlaylistTrackInput (number[] | string), rendered as the track’s data-waveform: an array is JSON-encoded, a string (e.g. a .json peaks URL) passed through. With peaks the player skips decoding that track’s audio. Playlist 1.8.0 is the first version that reads data-waveform.
  • onload, onplay, onpause, onend, ontimeupdate and onerror fire. They were wired correctly, but the playlist overwrote the embedded player’s callbacks with its own, so they never ran; @arraypress/waveform-playlist@1.8.0 chains them after its own handling. Every argument the core passes now reaches the handler, and a new handler still never re-mounts the playlist (now tested with a harness that updates one prop at a time).
  • waveformGradient, seekHandle, buttonRadius and artworkPosition reach the embedded player. All four are core player options the props type inherited, but they were never destructured from $props(), so each fell into ...rest, landed on the host <div> as an attribute and was never forwarded. They are now destructured and set in the options builder (buttonSize already was).
  • Changing only class no longer strips the playlist’s own host classes (waveform-playlist, wp-hero-layout, wp-grid-layout, wp-density-compact, wp-cover-top, wp-no-artist, wp-minimal). A class-only change (correctly) doesn’t remount, so when Svelte rewrote the class attribute those were gone until some other prop changed — hero/grid layouts collapsed and density/artist styling reverted. The host now binds a class value frozen at init (server markup and hydration are unchanged) and later class changes are applied with classList, adding and removing only the user’s tokens. The DOM structure is unchanged — the tracks and the playlist UI still live directly in the one host <div>. (Mounting the playlist into an inner element was considered and rejected: it would break .your-class.waveform-playlist selectors and push CSS variables set via style / class onto a parent, where the core’s defaults shadow them.)
  • Requires @arraypress/waveform-playlist@^1.8.0 (was ^1.7.2) — the upcoming release that makes these props work. Before it, the playlist ignored the constructor options this wrapper passes (expandChapters, showDuration, showPlayState, showChapterMarkers, chapterMarkerColor), leaked layout into the embedded player (fixed in 1.7.4), overwrote the forwarded callbacks, never read data-waveform, and its destroy() wiped the rendered tracks, so any prop change re-mounted an empty playlist. With 1.8.0 a re-mount keeps the tracks (now covered by a test).
  • Requires @arraypress/waveform-player@^1.24.5 (was ^1.23.0), the playlist core’s own floor.
  • The audioMode prop. The playlist always owns its audio, and an 'external' embedded player dispatches request-play events nobody answers — a playlist that never plays. It was forwarded to the constructor; @arraypress/waveform-playlist@1.8.0 ignores it, and the wrapper no longer accepts or forwards it (a stray one is swallowed rather than landing on the host element via ...rest).
  • Forward the core player’s crossOrigin option to the embedded player. Added to the $props() destructure (an undestructured prop falls into ...rest and is never forwarded) and set in the options builder. This option shipped across the rest of the waveform family in @arraypress/waveform-player@1.23.0 but was missed in the playlist wrappers, so it was previously accepted by the types and silently dropped at runtime. Requires @arraypress/waveform-player@^1.23.0 and @arraypress/waveform-playlist@^1.7.2 (the version that began forwarding it to each track’s player).
  • Forward the core player’s new localizable UI-string options — seekValueText, playPauseLabel, speedLabel, artworkAlt, and unknownTrackText — through to the underlying player. Requires @arraypress/waveform-player@^1.20.0.

Initial release.

  • <WaveformPlaylist> Svelte 5 component (built with runes) wrapping @arraypress/waveform-playlist:
    • Declarative tracks prop (with optional per-track chapters and markers), rendered into the [data-track] / [data-chapter] markup the playlist constructor parses on mount.
    • Playlist options as typed props: layout ('list' | 'minimal'), continuous, expandChapters, showDuration, showChapterMarkers, chapterMarkerColor, showPlayState.
    • The full pass-through player-option surface (waveform style, sizing, colours, playback, UI toggles, accessibility, icons) — inherited from the core WaveformPlayerOptions via Omit<>, minus per-track content fields (which come from tracks).
  • Imperative navigation API exported by the component instance (reachable via bind:this): selectTrack(), seekToChapter(), nextTrack(), previousTrack(), getPlayer(), getCurrentTrackIndex(), getTracks(), and getInstance().
  • class, style, id, and other element attributes fall through to the host element via ...rest; the base class wfp-host always applies.
  • SSR / SvelteKit safe: the core library is loaded via dynamic import('@arraypress/waveform-playlist') inside a browser-only $effect.
  • Identity-prop re-mount: a single $effect reads JSON.stringify(tracks)
    • every construction option, so any change destroys and rebuilds the instance over the freshly-rendered markup. A monotonic mount token discards a superseded in-flight import.
  • Public types adopted from both cores (@arraypress/waveform-playlist + @arraypress/waveform-player), re-exported so they can never drift.
  • Built with svelte-package (dist/ ships the preprocessed .svelte + generated .d.ts). Svelte + both cores are peer dependencies.
  • Vitest test suite (jsdom + @testing-library/svelte) covering host + track markup rendering, option mapping (tracks excluded), boolean-prop omission, destroy-on-unmount, identity-prop re-mount, and the exported navigation API. The core is mocked at the module boundary.