react-modern-audio-player 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +232 -24
  2. package/dist/index.es.js +1382 -1195
  3. package/dist/types/components/AudioPlayer/Context/StateContext/index.d.ts +1 -0
  4. package/dist/types/components/AudioPlayer/Context/UIContext.d.ts +1 -0
  5. package/dist/types/{hooks/context → components/AudioPlayer/Context/hooks}/index.d.ts +0 -1
  6. package/dist/types/components/AudioPlayer/Context/hooks/useAudioAttrsContext.d.ts +2 -0
  7. package/dist/types/components/AudioPlayer/Context/hooks/usePlaybackContext.d.ts +2 -0
  8. package/dist/types/components/AudioPlayer/Context/hooks/useResourceContext.d.ts +2 -0
  9. package/dist/types/components/AudioPlayer/Context/hooks/useTimeContext.d.ts +2 -0
  10. package/dist/types/components/AudioPlayer/Context/hooks/useTrackContext.d.ts +2 -0
  11. package/dist/types/components/AudioPlayer/Context/hooks/useUIContext.d.ts +1 -0
  12. package/dist/types/components/AudioPlayer/Context/index.d.ts +1 -0
  13. package/dist/types/components/AudioPlayer/Interface/Controller/Button/RepeatTypeBtn.d.ts +3 -1
  14. package/dist/types/components/AudioPlayer/Interface/Controller/Button/TransportControls/index.d.ts +4 -0
  15. package/dist/types/components/AudioPlayer/Interface/Controller/Button/VolumeIcon.d.ts +2 -0
  16. package/dist/types/components/AudioPlayer/Interface/Controller/Button/index.d.ts +5 -4
  17. package/dist/types/components/AudioPlayer/Interface/Controller/Drawer/SortablePlayList/index.d.ts +5 -1
  18. package/dist/types/components/AudioPlayer/Interface/Controller/Input/Progress/index.d.ts +12 -1
  19. package/dist/types/components/AudioPlayer/Interface/Controller/Tooltip/Volume/index.d.ts +3 -1
  20. package/dist/types/components/AudioPlayer/Interface/Information/Artwork.d.ts +3 -1
  21. package/dist/types/components/AudioPlayer/Interface/Information/TrackInfo.d.ts +3 -1
  22. package/dist/types/components/AudioPlayer/Interface/Information/TrackTime/index.d.ts +3 -1
  23. package/dist/types/components/AudioPlayer/Interface/PlayListEmpty/index.d.ts +6 -0
  24. package/dist/types/components/AudioPlayer/Interface/compound/index.d.ts +3 -0
  25. package/dist/types/components/AudioPlayer/Interface/compound/slotMetaMap.d.ts +10 -0
  26. package/dist/types/components/AudioPlayer/Interface/compound/useCompoundSlots.d.ts +6 -0
  27. package/dist/types/components/AudioPlayer/Interface/compound/useDuplicateSlotWarning.d.ts +7 -0
  28. package/dist/types/components/AudioPlayer/Interface/contexts/index.d.ts +2 -0
  29. package/dist/types/components/AudioPlayer/Interface/contexts/playListEmptyContext.d.ts +2 -0
  30. package/dist/types/components/AudioPlayer/Interface/contexts/playListPortalContext.d.ts +1 -0
  31. package/dist/types/components/AudioPlayer/Interface/hooks/index.d.ts +2 -0
  32. package/dist/types/components/AudioPlayer/Interface/hooks/useGridTemplate.d.ts +3 -0
  33. package/dist/types/components/AudioPlayer/Interface/hooks/useResolvedGridArea.d.ts +2 -0
  34. package/dist/types/components/AudioPlayer/index.d.ts +23 -2
  35. package/dist/types/components/Drawer/Drawer.d.ts +1 -0
  36. package/dist/types/components/Dropdown/DropdownTrigger.d.ts +4 -2
  37. package/dist/types/components/Grid/Item.d.ts +3 -2
  38. package/dist/types/components/Grid/index.d.ts +1 -0
  39. package/dist/types/components/SortableList/useSortableListItem.d.ts +1 -0
  40. package/dist/types/hooks/index.d.ts +1 -2
  41. package/dist/types/index.d.ts +2 -2
  42. package/dist/types/utils/ssr.d.ts +0 -2
  43. package/llms.txt +61 -0
  44. package/package.json +26 -9
  45. package/dist/types/components/AudioPlayer/Interface/Controller/Button/VolumeTriggerBtn.d.ts +0 -1
  46. package/dist/types/hooks/context/useAudioAttrsContext.d.ts +0 -2
  47. package/dist/types/hooks/context/usePlaybackContext.d.ts +0 -2
  48. package/dist/types/hooks/context/useResourceContext.d.ts +0 -2
  49. package/dist/types/hooks/context/useTimeContext.d.ts +0 -2
  50. package/dist/types/hooks/context/useTrackContext.d.ts +0 -2
  51. package/dist/types/hooks/context/useUIContext.d.ts +0 -1
  52. package/dist/types/hooks/useGridTemplate.d.ts +0 -2
  53. /package/dist/types/components/AudioPlayer/Interface/Controller/Button/{NextBtn.d.ts → TransportControls/NextBtn.d.ts} +0 -0
  54. /package/dist/types/components/AudioPlayer/Interface/Controller/Button/{PlayBtn.d.ts → TransportControls/PlayBtn.d.ts} +0 -0
  55. /package/dist/types/components/AudioPlayer/Interface/Controller/Button/{PrevBtn.d.ts → TransportControls/PrevBtn.d.ts} +0 -0
  56. /package/dist/types/{utils → components/AudioPlayer/utils}/clampVolume.d.ts +0 -0
  57. /package/dist/types/hooks/{context/useNonNullableContext.d.ts → useNonNullableContext.d.ts} +0 -0
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  <!-- markdownlint-disable MD033 MD041 MD028 -->
2
2
 
3
3
  <p align="center">
4
- <img width="20%" src="https://user-images.githubusercontent.com/70849655/180391190-2b268d23-c9f3-4e95-9fce-090897842c04.png" alt="rm-audio-player" />
4
+ <img width="12%" src="https://raw.githubusercontent.com/slash9494/react-modern-audio-player/main/package/assets/icon.png" alt="rm-audio-player" />
5
5
  <h1 align="center">React Modern Audio Player</h1>
6
6
  </p>
7
7
 
@@ -15,31 +15,58 @@
15
15
  <a href="https://www.npmjs.com/package/react-modern-audio-player">
16
16
  <img src="https://img.shields.io/npm/dt/react-modern-audio-player" alt="Download">
17
17
  </a>
18
- <a href="https://bundlephobia.com/package/react-modern-audio-player@0.0.3">
18
+ <a href="https://bundlephobia.com/package/react-modern-audio-player">
19
19
  <img src="https://img.shields.io/bundlephobia/minzip/react-modern-audio-player" alt="BundleSize">
20
20
  </a>
21
+ <a href="https://github.com/slash9494/react-modern-audio-player/actions/workflows/integration.yml">
22
+ <img src="https://img.shields.io/github/actions/workflow/status/slash9494/react-modern-audio-player/integration.yml?branch=main&label=CI" alt="CI">
23
+ </a>
24
+ <img src="https://img.shields.io/badge/TypeScript-first-3178C6?logo=typescript&logoColor=white" alt="TypeScript">
21
25
  </p>
22
26
 
27
+ ## Highlights
28
+
29
+ - **Waveform** progress bar powered by `wavesurfer.js`
30
+ - **Playlist** with drag-and-drop reorder, repeat, shuffle
31
+ - **Fully customizable** — swap any sub-component, CSS variable theming, light & dark themes
32
+ - **Compound slots** — `AudioPlayer.Volume`, `AudioPlayer.Progress`, `AudioPlayer.PlayList`, etc. for partial customization without losing the preset
33
+ - **Multi-instance playlist** — render multiple players on the same page with isolated playlist drawers and fully independent audio state
34
+ - **Accessible** — WAI-ARIA patterns, full keyboard navigation, axe-tested
35
+ - **TypeScript-first** — typed props and hooks (`useAudioPlayer`, sub-hooks)
36
+ - **SSR-friendly** — works with Next.js App Router / Server Components
37
+
23
38
  ## DEMO
24
39
 
25
- <https://codesandbox.io/s/basic-91y82y?file=/src/App.tsx>
40
+ <https://codesandbox.io/p/sandbox/basic-nfrpfq>
26
41
 
27
42
  # **Flexible and Customizable UI**
28
43
 
29
- ## This can offer waveform by `wavesurfer.js`
44
+ ## Waveform progress with `wavesurfer.js`
45
+
46
+ <img width="100%" src="https://raw.githubusercontent.com/slash9494/react-modern-audio-player/main/package/assets/screenshots/waveform-light.png" alt="Waveform view" />
47
+
48
+ ## Customizable layout and placement — with light & dark themes
30
49
 
31
- <img width="100%" src="https://user-images.githubusercontent.com/70849655/180435472-f043dbb4-54df-43e0-bc5c-67492510e817.png" alt="">
50
+ ### Full View
32
51
 
33
- ## This can offer various UI and you can also customize each component position
52
+ <p align="center">
53
+ <img width="49%" src="https://raw.githubusercontent.com/slash9494/react-modern-audio-player/main/package/assets/screenshots/full-view-light.png" alt="Full view — light" />
54
+ <img width="49%" src="https://raw.githubusercontent.com/slash9494/react-modern-audio-player/main/package/assets/screenshots/full-view-dark.png" alt="Full view — dark" />
55
+ </p>
34
56
 
35
- > Full View
36
- > <img width="100%" src="https://user-images.githubusercontent.com/70849655/180435489-263fae23-f066-4a37-a524-58918eb40b0c.png" alt="">
57
+ ### Position Change
37
58
 
38
- > Position Change
39
- > <img width="110%" src="https://user-images.githubusercontent.com/70849655/180435493-2c2e08c5-b67b-4ab7-aded-5a0403d42050.png" alt="">
59
+ <p align="center">
60
+ <img width="49%" src="https://raw.githubusercontent.com/slash9494/react-modern-audio-player/main/package/assets/screenshots/position-change-light.png" alt="Position change — light" />
61
+ <img width="49%" src="https://raw.githubusercontent.com/slash9494/react-modern-audio-player/main/package/assets/screenshots/position-change-dark.png" alt="Position change — dark" />
62
+ </p>
40
63
 
41
- > Particular View
42
- > <br/> > <img width="50%" margin='10px' src="https://user-images.githubusercontent.com/70849655/180435497-0f839cd1-e1fd-400f-a013-82ba441ca79b.png" alt=""> > <img width="20%" margin='10px' src="https://user-images.githubusercontent.com/70849655/180435479-4f056620-f850-4d21-ab23-24efc4300d68.png" alt=""> > <br/> > <img width="20%" margin='10px' src="https://user-images.githubusercontent.com/70849655/180435484-3331b7cb-1555-4ffb-a36c-a5343f72c8c3.png" alt=""> > <img width="50%" margin='10px' src="https://user-images.githubusercontent.com/70849655/180435486-2402ba80-7121-410c-9a06-9a737be72ec2.png" alt="">
64
+ ### Particular View
65
+
66
+ <p align="center" style="display:flex; gap: 10%;">
67
+ <img width="39%" src="https://raw.githubusercontent.com/slash9494/react-modern-audio-player/main/package/assets/screenshots/particular-view-dark.png" alt="Particular view — compact compound" />
68
+ <img width="10%" height="50%" src="https://raw.githubusercontent.com/slash9494/react-modern-audio-player/main/package/assets/screenshots/particular-view-play-only-dark.png" alt="Particular view — play button only" />
69
+ </p>
43
70
 
44
71
  # **Installation**
45
72
 
@@ -83,7 +110,13 @@ This library includes the `'use client'` directive and can be imported directly
83
110
  import AudioPlayer from "react-modern-audio-player";
84
111
 
85
112
  const playList = [
86
- { name: "track", writer: "artist", img: "cover.jpg", src: "audio.mp3", id: 1 },
113
+ {
114
+ name: "track",
115
+ writer: "artist",
116
+ img: "cover.jpg",
117
+ src: "audio.mp3",
118
+ id: 1,
119
+ },
87
120
  ];
88
121
 
89
122
  export default function Page() {
@@ -123,7 +156,9 @@ export default function PlayerPage() {
123
156
  | **Props** | [PlayList](#playlist) · [InitialStates](#initialstates) · [ActiveUI](#activeui) · [Placement](#placement) · [RootContainerProps](#rootcontainerprops) |
124
157
  | **Override & Style** | [CustomIcons](#customicons) · [CoverImgsCss](#coverimgscss) · [Theme mode](#theme-mode-dark-mode) · [ID & Classnames](#id--classnames) |
125
158
  | **Player Hook API** | [useAudioPlayer](#useaudioplayer) · [AudioPlayerControls](#audioplayercontrols) · [Sub-Hooks](#sub-hooks) |
126
- | **Custom Component** | [Custom Component](#custom-component) |
159
+ | **Custom Component** | [Custom Component](#custom-component) · [Compound Slots](#compound-slots) |
160
+ | **Accessibility** | [Keyboard support](#keyboard-support) |
161
+ | **Gotchas** | [Gotchas](#gotchas) |
127
162
  | **Example** | [Example](#example) |
128
163
 
129
164
  # Props
@@ -173,6 +208,26 @@ type AudioData = {
173
208
  };
174
209
  ```
175
210
 
211
+ ### Empty playlist handling
212
+
213
+ Passing `playList={[]}` renders the player in an empty state without crashing. This is useful while waiting for asynchronous track data:
214
+
215
+ ```tsx
216
+ function App() {
217
+ const [tracks, setTracks] = useState<PlayList>([]);
218
+
219
+ useEffect(() => {
220
+ fetchTracks().then(setTracks);
221
+ }, []);
222
+
223
+ // Safe — the player mounts with no audio source and activates once tracks arrive.
224
+ return <AudioPlayer playList={tracks} />;
225
+ }
226
+ ```
227
+
228
+ - When the playlist becomes empty after updates, playback stops and all time state resets.
229
+ - When `audioInitialState.curPlayId` doesn't match any track in the current list, the player falls back to `playList[0]` automatically.
230
+
176
231
  ## InitialStates
177
232
 
178
233
  ```tsx
@@ -186,9 +241,12 @@ type InitialStates = Omit<
186
241
  currentTime?: number;
187
242
  duration?: number;
188
243
  curPlayId: number;
244
+ playListExpanded?: boolean;
189
245
  };
190
246
  ```
191
247
 
248
+ > `playListExpanded: true` opens the playlist drawer on mount. Consistent with the other fields on `audioInitialState`, this is read once at mount and is not tracked in reducer state.
249
+
192
250
  ## ActiveUI
193
251
 
194
252
  ```tsx
@@ -327,6 +385,12 @@ const defaultInterfacePlacement = {
327
385
 
328
386
  - rm-audio-player
329
387
 
388
+ > **Multi-instance note**: when multiple `<AudioPlayer>` instances share a
389
+ > page the root `id` is duplicated across them. Playlist and audio state are
390
+ > still isolated per instance (each player has its own React provider tree
391
+ > and its own `<audio>` DOM node). If you need per-instance selectors, target
392
+ > via the class names below rather than the id.
393
+
330
394
  ### root ClassName
331
395
 
332
396
  - rmap-player-provider
@@ -389,8 +453,7 @@ function PlayerControls() {
389
453
  <button onClick={() => setVolume(0.5)}>Volume 50%</button>
390
454
  <button onClick={() => setTrack(1)}>Track 2</button>
391
455
  <p>
392
- {currentTrack?.name} — {currentTime.toFixed(0)}s / {duration.toFixed(0)}
393
- s
456
+ {currentTrack?.name} — {currentTime.toFixed(0)}s / {duration.toFixed(0)}s
394
457
  </p>
395
458
  </div>
396
459
  );
@@ -451,17 +514,21 @@ function PlayButton() {
451
514
  // Only re-renders on time updates
452
515
  function TimeDisplay() {
453
516
  const { currentTime, duration } = useAudioPlayerTime();
454
- return <span>{currentTime.toFixed(0)}s / {duration.toFixed(0)}s</span>;
517
+ return (
518
+ <span>
519
+ {currentTime.toFixed(0)}s / {duration.toFixed(0)}s
520
+ </span>
521
+ );
455
522
  }
456
523
  ```
457
524
 
458
- | Hook | Returns |
459
- | -------------------------- | ------------------------------------------------------------------------------- |
460
- | `useAudioPlayerPlayback` | `{ isPlaying, repeatType, play, pause, togglePlay }` |
461
- | `useAudioPlayerTrack` | `{ currentPlayId, currentIndex, playList, currentTrack, setTrack, next, prev }` |
462
- | `useAudioPlayerVolume` | `{ volume, muted, setVolume, toggleMute }` |
463
- | `useAudioPlayerTime` | `{ currentTime, duration, seek }` |
464
- | `useAudioPlayerElement` | `{ audioEl, waveformInst }` (advanced) |
525
+ | Hook | Returns |
526
+ | ------------------------ | ------------------------------------------------------------------------------- |
527
+ | `useAudioPlayerPlayback` | `{ isPlaying, repeatType, play, pause, togglePlay }` |
528
+ | `useAudioPlayerTrack` | `{ currentPlayId, currentIndex, playList, currentTrack, setTrack, next, prev }` |
529
+ | `useAudioPlayerVolume` | `{ volume, muted, setVolume, toggleMute }` |
530
+ | `useAudioPlayerTime` | `{ currentTime, duration, seek }` |
531
+ | `useAudioPlayerElement` | `{ audioEl, waveformInst }` (advanced) |
465
532
 
466
533
  # Context Hooks
467
534
 
@@ -535,6 +602,147 @@ const CustomComponent = () => {
535
602
  </AudioPlayer>;
536
603
  ```
537
604
 
605
+ # Compound Slots
606
+
607
+ `AudioPlayer` exposes its built-in controls as static members so you can re-place or augment individual pieces without rebuilding the whole layout.
608
+
609
+ | Member | Renders |
610
+ | --- | --- |
611
+ | `AudioPlayer.Progress` | progress bar / waveform |
612
+ | `AudioPlayer.Volume` | volume trigger + slider |
613
+ | `AudioPlayer.PlayList` | sortable playlist drawer (accepts `initialExpanded?`) |
614
+ | `AudioPlayer.PlayListEmpty` | fallback rendered inside the playlist drawer when `playList` is empty |
615
+ | [`AudioPlayer.PlayButton`](#audioplayerplaybutton-and-activeuiprevnnext) | Play + Prev + Next group (Prev/Next visibility follows `activeUI.prevNnext`) |
616
+ | `AudioPlayer.RepeatButton` | repeat-type button |
617
+ | `AudioPlayer.Artwork` | track artwork |
618
+ | `AudioPlayer.TrackInfo` | track title / writer |
619
+ | `AudioPlayer.TrackTime` | current + duration time |
620
+ | `AudioPlayer.CustomComponent` | user-defined slot |
621
+
622
+ Each slot accepts the full `GridItemLayoutProps` set — `gridArea?`, `visible?`, `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.
623
+
624
+ Native HTML attributes (`className`, `style`, `onClick`, `data-*`, etc.) are **not** forwarded by compound slots. Compose the underlying primitives (`PlayBtn`, `PrevBtn`, `NextBtn`, etc., still exported) when full DOM control is needed; headless support with native attribute pass-through is planned for v3.
625
+
626
+ ## Mental model — `activeUI` vs compound children
627
+
628
+ - **`activeUI`** governs the **preset** (default layout) — which built-in controls are shown.
629
+ - **Compound children** are **explicit placements** that always render (`visible` defaults to `true`).
630
+
631
+ The two layers are orthogonal. Compound children render **additively** alongside the preset. To truly replace a preset control, disable it in `activeUI` and render the compound counterpart:
632
+
633
+ ```tsx
634
+ // Remove the default volume, re-place it with a custom gridArea
635
+ <AudioPlayer
636
+ playList={playList}
637
+ activeUI={{ all: true, volume: false }}
638
+ >
639
+ <AudioPlayer.Volume gridArea="1 / 5 / 1 / 6" />
640
+ </AudioPlayer>
641
+ ```
642
+
643
+ In development, a `console.warn` is emitted when a compound slot is rendered while its preset counterpart is still active, so silent duplication is easy to catch.
644
+
645
+ ## `AudioPlayer.PlayButton` and `activeUI.prevNnext`
646
+
647
+ `AudioPlayer.PlayButton` is a single slot that renders the **Play + Prev + Next** group as one unit. There is no separate `AudioPlayer.PrevButton` / `AudioPlayer.NextButton` compound slot — Prev / Next are part of the `PlayButton` compound, and there are two ways to render them.
648
+
649
+ ### Method 1 — `AudioPlayer.PlayButton` compound (group)
650
+
651
+ Place the whole group at a custom grid area; Prev / Next visibility inside the group follows `activeUI.prevNnext` (which defaults to `activeUI.all`):
652
+
653
+ ```tsx
654
+ // Re-place the full Play + Prev + Next group at a custom area
655
+ <AudioPlayer
656
+ playList={playList}
657
+ activeUI={{ all: true, playButton: false }} // hide the preset transport
658
+ >
659
+ <AudioPlayer.PlayButton gridArea="row1-3" />
660
+ </AudioPlayer>
661
+
662
+ // Same compound, but render Play only — Prev / Next hidden by activeUI.prevNnext
663
+ <AudioPlayer
664
+ playList={playList}
665
+ activeUI={{ all: true, playButton: false, prevNnext: false }}
666
+ >
667
+ <AudioPlayer.PlayButton gridArea="row1-3" />
668
+ </AudioPlayer>
669
+ ```
670
+
671
+ ### Method 2 — primitives via `AudioPlayer.CustomComponent`
672
+
673
+ When you need finer control — placing Prev / Play / Next in different grid cells, applying custom wrappers, or attaching native HTML attributes — drop down to the `PlayBtn`, `PrevBtn`, and `NextBtn` primitives (still exported) and render them inside `AudioPlayer.CustomComponent`:
674
+
675
+ ```tsx
676
+ import AudioPlayer, {
677
+ PrevBtn,
678
+ PlayBtn,
679
+ NextBtn,
680
+ } from "react-modern-audio-player";
681
+
682
+ <AudioPlayer
683
+ playList={playList}
684
+ activeUI={{ all: true, playButton: false }} // hide the preset transport
685
+ placement={{
686
+ interface: { customComponentsArea: { "custom-transport": "row1-3" } },
687
+ }}
688
+ >
689
+ <AudioPlayer.CustomComponent id="custom-transport">
690
+ <div className="my-transport">
691
+ <PrevBtn isVisible />
692
+ <PlayBtn />
693
+ <NextBtn isVisible />
694
+ </div>
695
+ </AudioPlayer.CustomComponent>
696
+ </AudioPlayer>;
697
+ ```
698
+
699
+ `PrevBtn` / `NextBtn` accept an `isVisible` boolean for symmetry with the compound group's visibility logic; pass `false` to omit them. Use Method 2 when Method 1's group placement and `activeUI.prevNnext` toggle aren't enough.
700
+
701
+ ## Custom empty-playlist UI
702
+
703
+ Pass children to `AudioPlayer.PlayListEmpty` to render a custom node inside the playlist drawer when `playList` is empty. Omit the slot to keep the default (drawer content renders nothing).
704
+
705
+ ```tsx
706
+ <AudioPlayer playList={[]}>
707
+ <AudioPlayer.PlayListEmpty>
708
+ <div className="my-empty">Playlist is empty</div>
709
+ </AudioPlayer.PlayListEmpty>
710
+ </AudioPlayer>
711
+ ```
712
+
713
+ `PlayListEmpty` is a marker slot; its children are consumed by the drawer via context and the component itself does not render in-place.
714
+
715
+ # **Accessibility**
716
+
717
+ The player follows WAI-ARIA patterns and is fully navigable by keyboard and screen readers.
718
+
719
+ ## Keyboard support
720
+
721
+ All controls are reachable via `Tab` and respond to standard keyboard activation. The playlist uses the WAI-ARIA "Listbox with Rearrangeable Options" pattern:
722
+
723
+ | Key | Action |
724
+ | --- | --- |
725
+ | `Tab` / `Shift+Tab` | Move focus between player controls |
726
+ | `Space` / `Enter` | Activate the focused button (play/pause, prev/next, repeat, mute, playlist) |
727
+ | `ArrowUp` / `ArrowDown` | Move focus between playlist items |
728
+ | `Alt+ArrowUp` / `Alt+ArrowDown` | Reorder the focused playlist item |
729
+ | `Enter` / `Space` on a playlist item | Select and play that track |
730
+
731
+ Drag-and-drop reordering is preserved as an alternative — keyboard and mouse both call the same `onReorder` handler.
732
+
733
+ # **Gotchas**
734
+
735
+ Common integration mistakes to avoid:
736
+
737
+ - **Don't toggle the theme via `rootContainerProps.style.colorScheme`.** The native CSS `color-scheme` property does not switch the player's theme. Use the top-level [`colorScheme`](#theme-mode-dark-mode) prop, which drives the `[data-theme]` attribute and re-initializes the waveform colors.
738
+ - **Set the `InterfacePlacement` generic when placing `customComponentsArea` beyond row 9.** TypeScript rejects values past the default range, so use `InterfacePlacement<N>` where `N` is `(max row length + 1)` — e.g. `InterfacePlacement<11>` for `"row1-10"` (see [Custom Component](#custom-component)).
739
+ - **`AudioPlayer.CustomComponent` accepts a single React element child.** It uses `React.cloneElement` internally, so passing multiple children or a primitive (string, number) will throw.
740
+ - **Volume is `0..1`, not `0..100`.** `setVolume` clamps out-of-range values, so `setVolume(50)` silently becomes `setVolume(1)`.
741
+ - **Compound slots don't forward native HTML attributes.** `<AudioPlayer.Volume className="...">` is rejected by TypeScript — only `GridItemLayoutProps` (layout) pass through. Compose the underlying primitives (`PlayBtn`, `PrevBtn`, `NextBtn`, etc., still exported) when you need `className`, `style`, `onClick`, or `data-*`. Full headless support is planned for v3.
742
+ - **`id: 0` is a valid track id.** The reducer uses nullish checks, so tracks with `id: 0` are handled correctly — don't filter them out of `playList` on the assumption that zero is falsy.
743
+ - **Don't import the CSS manually.** Styles are auto-injected via `sideEffects: ["*.css"]`; `import "react-modern-audio-player/dist/index.css"` will 404 or double-load.
744
+ - **Multiple mounted `<AudioPlayer>` instances don't share React state, but they do share the user's speakers.** Each instance has its own provider and its own `<audio>` element, so the state is isolated — but if two instances both play, the user hears both tracks simultaneously. Coordinate playback yourself (e.g. pause the others when one `play()` fires).
745
+
538
746
  # **Example**
539
747
 
540
748
  ```tsx