@behindthemusictree/app-kit-ui 24.0.1 → 28.0.2

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.
@@ -3,7 +3,7 @@ import { ReactNode } from 'react';
3
3
  import { z } from 'zod';
4
4
  import { GenreTreeProps, FocusChangeReason, GenreTreeNode, GenreTreeAction, GenreTreeViewMode, GenreTreeViewport, ControlsOrientation, InfoPanelSections, InfoPanelBody, WheelZoomMode } from '@behindthemusictree/genre-tree-view';
5
5
  export { ControlsOrientation, FocusChangeReason, GenreTreeAction, GenreTreeOutlineSkeleton, GenreTreeSkeleton, GenreTreeViewMode, GenreTreeViewSkeleton, GenreTreeViewSkeletonProps, GenreTreeViewport, GenreTreeWheelSkeleton } from '@behindthemusictree/genre-tree-view';
6
- import { TrackBase, CriteriaOverview, CriteriaPlaylistSimple, ArtistSearchResult, CriteriaMinimum, RenderArtistLink } from '@behindthemusictree/app-kit/genre-tree';
6
+ import { TrackBase, CriteriaOverview, CriteriaPlaylistSimple, GenreUuid, GenrePlaylistUuid, ArtistSearchResult, CriteriaMinimum, RenderArtistLink } from '@behindthemusictree/app-kit/genre-tree';
7
7
  import { Scope } from '@behindthemusictree/app-kit/transport';
8
8
 
9
9
  type GenrePlaylistTracksSlots = {
@@ -22,8 +22,8 @@ type GenreDetailSlots<O extends CriteriaOverview> = {
22
22
  renderEssentialTracks?: (overview: O) => ReactNode;
23
23
  };
24
24
 
25
- /** Controlled edit/expand state and edit-mode slots of the info panel, plus the generic overlay panel, as typed by genre-tree-view. */
26
- type InfoPanelCurationProps = Partial<Pick<GenreTreeProps, "editMode" | "onEditModeChange" | "renderTitleEditor" | "renderParentsEditor" | "renderDangerZone" | "expanded" | "onExpandedChange" | "collapsed" | "hideExpandButton" | "overlayPanel">>;
25
+ /** Controlled expand state and danger-zone slot of the info panel, plus the generic overlay panel, as typed by genre-tree-view. */
26
+ type InfoPanelCurationProps = Partial<Pick<GenreTreeProps, "renderDangerZone" | "expanded" | "onExpandedChange" | "collapsed" | "hideExpandButton" | "overlayPanel">>;
27
27
 
28
28
  /** What a tree node's play button plays, and which node is the one currently playing. */
29
29
  type GenrePlaylistPlayback = {
@@ -31,6 +31,8 @@ type GenrePlaylistPlayback = {
31
31
  play: (genrePlaylist: CriteriaPlaylistSimple) => Promise<void>;
32
32
  };
33
33
 
34
+ type GenreTreeSizeMetric = "songs" | "views";
35
+
34
36
  /** Pop-core focus is controlled-only: pass both props or neither. Ignored in outline mode. */
35
37
  type GenreFocusProps = {
36
38
  focusGenreUuid?: undefined;
@@ -46,6 +48,30 @@ type GenreFocusProps = {
46
48
  onFocusGenreChange: (uuid: string | null, reason: FocusChangeReason) => void;
47
49
  };
48
50
 
51
+ type ParentChange = {
52
+ genre: GenreUuid;
53
+ from: GenreUuid | null;
54
+ to: GenreUuid;
55
+ };
56
+
57
+ /** Identifies the detail-panel entity whose current host route should be shared. */
58
+ type GenreTreeShareTarget = {
59
+ kind: "genre";
60
+ /** Tree selection/route identity; distinct from the associated criteria UUID. */
61
+ genrePlaylistUuid: GenrePlaylistUuid;
62
+ name: string;
63
+ scope: Scope;
64
+ /** Preserve an active artist filter when sharing one of its genre details. */
65
+ artistUuid: string | null;
66
+ } | {
67
+ kind: "artist";
68
+ uuid: string;
69
+ name: string;
70
+ scope: Scope;
71
+ };
72
+ /** Host applications own routing and return an absolute or relative URL for this target. */
73
+ type GetGenreTreeShareUrl = (target: GenreTreeShareTarget) => string | null | undefined;
74
+
49
75
  /** Artists are searched by default; picking one opens its panel (see `artistUuid`). */
50
76
  type ArtistSearchProps = {
51
77
  artistSearch: false;
@@ -65,6 +91,8 @@ type GenreTreeViewProps<O extends CriteriaOverview = CriteriaOverview> = {
65
91
  handleGenreCreationAction: (parent: CriteriaMinimum | null) => void;
66
92
  handleGenreRenameAction: (genre: CriteriaMinimum) => void;
67
93
  getBackendBaseUrl: () => string;
94
+ /** Called after the Parent popup moved a genre; `from` and `to` are genre (criteria) uuids, `from` null for a root genre. */
95
+ onParentChanged?: (change: ParentChange) => void;
68
96
  additionalActions?: (node: GenreTreeNode) => GenreTreeAction[];
69
97
  /** Controlled view mode. When provided, the internal Stacked/Wheel toggle is not rendered — the consumer owns that UI. */
70
98
  viewMode?: GenreTreeViewMode;
@@ -94,6 +122,12 @@ type GenreTreeViewProps<O extends CriteriaOverview = CriteriaOverview> = {
94
122
  renderGenreDetailExtras?: (overview: O) => ReactNode;
95
123
  /** Layout of the wheel/tree floating zoom/pop/fit controls. Defaults to "horizontal". */
96
124
  controlsOrientation?: ControlsOrientation;
125
+ /** Host control displayed before the size metric in the floating controls. */
126
+ floatingControlsLeading?: ReactNode;
127
+ /** Controlled size metric of the wheel and pop-core nodes: song count or summed song views. Uncontrolled when omitted
128
+ * (starts on songs). The toggle is hidden while an artist filters the tree, which only has song counts. */
129
+ sizeMetric?: GenreTreeSizeMetric;
130
+ onSizeMetricChange?: (metric: GenreTreeSizeMetric) => void;
97
131
  /** When set, artist names in the info panel's track list render through it (e.g. a router `Link`). In the artist
98
132
  * panel, names otherwise render as buttons that switch the panel to that artist. */
99
133
  renderArtistLink?: RenderArtistLink;
@@ -107,12 +141,14 @@ type GenreTreeViewProps<O extends CriteriaOverview = CriteriaOverview> = {
107
141
  /** Controlled expanded state of the artist panel. Uncontrolled, it resets to compact on each artist. */
108
142
  artistExpanded?: boolean;
109
143
  onArtistExpandedChange?: (expanded: boolean) => void;
144
+ /** Resolves a selected genre or artist to a share URL using the host app's routes. */
145
+ getShareUrl?: GetGenreTreeShareUrl;
110
146
  } & ArtistSearchProps & GenreDetailSlots<O> & GenrePlaylistTracksSlots & InfoPanelCurationProps & GenreFocusProps & {
111
147
  /** Hides the info panel behind its edge tab; uncontrolled when omitted. Only applies with `placement="regions"`. */
112
148
  collapsed?: boolean;
113
149
  onCollapsedChange?: (collapsed: boolean) => void;
114
150
  };
115
- declare function GenreTreeView<O extends CriteriaOverview = CriteriaOverview>({ scope, handleGenreCreationAction, handleGenreRenameAction, getBackendBaseUrl, additionalActions, viewMode: controlledViewMode, selectedNodeId: controlledSelectedNodeId, onSelectedNodeIdChange, defaultViewport, onViewportChange, readOnly, className, showDisplayOptions, showAddRoot, allowWheelRotation: controlledAllowWheelRotation, criteriaOverviewSchema, renderGenreDetailExtras, controlsOrientation, renderArtistLink, artistUuid: controlledArtistUuid, onArtistUuidChange, artistExpanded: controlledArtistExpanded, onArtistExpandedChange, artistSearch, onArtistSelect, artistSearchBaseUrl, renderSummary, renderEssentialTracks, renderSongLeading, renderSongActions, renderTracksToolbar, editMode, onEditModeChange, renderTitleEditor, renderParentsEditor, renderDangerZone, expanded, onExpandedChange, collapsed, onCollapsedChange, overlayPanel, placement, ...focus }: GenreTreeViewProps<O>): react.JSX.Element;
151
+ declare function GenreTreeView<O extends CriteriaOverview = CriteriaOverview>({ scope, handleGenreCreationAction, handleGenreRenameAction, onParentChanged, getBackendBaseUrl, additionalActions, viewMode: controlledViewMode, selectedNodeId: controlledSelectedNodeId, onSelectedNodeIdChange, defaultViewport, onViewportChange, readOnly, className, showDisplayOptions, showAddRoot, allowWheelRotation: controlledAllowWheelRotation, criteriaOverviewSchema, renderGenreDetailExtras, controlsOrientation, floatingControlsLeading, sizeMetric: controlledSizeMetric, onSizeMetricChange, renderArtistLink, artistUuid: controlledArtistUuid, onArtistUuidChange, artistExpanded: controlledArtistExpanded, onArtistExpandedChange, getShareUrl, artistSearch, onArtistSelect, artistSearchBaseUrl, renderSummary, renderEssentialTracks, renderSongLeading, renderSongActions, renderTracksToolbar, renderDangerZone, expanded, onExpandedChange, collapsed, onCollapsedChange, overlayPanel, placement, ...focus }: GenreTreeViewProps<O>): react.JSX.Element;
116
152
 
117
153
  type GenrePlaylistTreePerRootProps<T extends TrackBase> = {
118
154
  scope: Scope;
@@ -159,6 +195,8 @@ type GenreSearchExtraResults = {
159
195
  }[];
160
196
  /** Announced politely, e.g. "No artists found". */
161
197
  status?: string;
198
+ /** Shows a spinner in place of the search icon while the host resolves this group. */
199
+ loading?: boolean;
162
200
  };
163
201
  type GenreSearchProps<T extends CriteriaPlaylistSimple = CriteriaPlaylistSimple> = {
164
202
  genrePlaylists: T[];
@@ -182,16 +220,20 @@ interface TrackListSidebarProps<T extends TrackBase> {
182
220
  renderDuration?: (track: T) => ReactNode;
183
221
  renderActions?: (track: T) => ReactNode;
184
222
  layout?: "fixed" | "inline";
223
+ isCollapsed?: boolean;
224
+ onExpand?: () => void;
225
+ onMinimize?: () => void;
185
226
  /** When set, artist names render through it (e.g. a router `Link`). */
186
227
  renderArtistLink?: RenderArtistLink;
187
228
  }
188
- declare function TrackListSidebar<T extends TrackBase>({ className, renderDuration, renderActions, layout, renderArtistLink, }: TrackListSidebarProps<T>): react.JSX.Element | null;
229
+ declare function TrackListSidebar<T extends TrackBase>({ className, renderDuration, renderActions, layout, isCollapsed, onExpand, onMinimize, renderArtistLink, }: TrackListSidebarProps<T>): react.JSX.Element | null;
189
230
 
190
231
  interface PlayerPanelProps {
191
232
  className?: string;
192
- /** Hidden via CSS, never unmounted, so the video keeps playing while the panel is closed. */
233
+ /** Hidden via CSS, never unmounted, so the video keeps playing while the host hides the panel. */
193
234
  isVisible?: boolean;
194
- onClose: () => void;
235
+ /** @deprecated The player no longer closes; its header action minimizes to video-only mode. */
236
+ onClose?: () => void;
195
237
  /** Controlled extended state; uncontrolled when omitted. */
196
238
  expanded?: boolean;
197
239
  onExpandedChange?: (expanded: boolean) => void;
@@ -199,7 +241,7 @@ interface PlayerPanelProps {
199
241
  extendedWidth?: string;
200
242
  renderArtistLink?: RenderArtistLink;
201
243
  }
202
- declare function PlayerPanel({ className, isVisible, onClose, expanded, onExpandedChange, extendedWidth, renderArtistLink, }: PlayerPanelProps): react.JSX.Element;
244
+ declare function PlayerPanel({ className, isVisible, expanded, onExpandedChange, extendedWidth, renderArtistLink, }: PlayerPanelProps): react.JSX.Element;
203
245
 
204
246
  interface TrackItemProps<T extends TrackBase> {
205
247
  track: T;
@@ -215,8 +257,9 @@ interface TrackPositionPlayPauseProps {
215
257
  position: number;
216
258
  uuid: string;
217
259
  handlePlayPauseClick: (event: React.MouseEvent) => void;
260
+ isSelected?: boolean;
218
261
  }
219
- declare function TrackPositionPlayPause({ position, uuid, handlePlayPauseClick }: TrackPositionPlayPauseProps): react.JSX.Element;
262
+ declare function TrackPositionPlayPause({ position, uuid, handlePlayPauseClick, isSelected, }: TrackPositionPlayPauseProps): react.JSX.Element;
220
263
 
221
264
  interface RatingProps {
222
265
  rating: number | null | undefined;
@@ -243,4 +286,4 @@ interface PanelEdgeTabProps {
243
286
  }
244
287
  declare function PanelEdgeTab({ side, edge, center, controls, className }: PanelEdgeTabProps): react.JSX.Element | null;
245
288
 
246
- export { type ArtistSearchProps, type GenreDetailSlots, type GenreFocusProps, type GenrePlaylistTracksSlots, GenrePlaylistTreePerRoot, type GenrePlaylistTreePerRootProps, GenreSearch, type GenreSearchExtraResults, type GenreSearchProps, GenreTreeView, type GenreTreeViewProps, type InfoPanelCurationProps, PanelEdgeTab, type PanelEdgeTabAction, type PanelEdgeTabProps, PlayerPanel, type PlayerPanelProps, Rating, type RatingProps, TrackItem, type TrackItemProps, TrackListSidebar, type TrackListSidebarProps, TrackPositionPlayPause, type TrackPositionPlayPauseProps };
289
+ export { type ArtistSearchProps, type GenreDetailSlots, type GenreFocusProps, type GenrePlaylistTracksSlots, GenrePlaylistTreePerRoot, type GenrePlaylistTreePerRootProps, GenreSearch, type GenreSearchExtraResults, type GenreSearchProps, type GenreTreeShareTarget, GenreTreeView, type GenreTreeViewProps, type GetGenreTreeShareUrl, type InfoPanelCurationProps, PanelEdgeTab, type PanelEdgeTabAction, type PanelEdgeTabProps, type ParentChange, PlayerPanel, type PlayerPanelProps, Rating, type RatingProps, TrackItem, type TrackItemProps, TrackListSidebar, type TrackListSidebarProps, TrackPositionPlayPause, type TrackPositionPlayPauseProps };
@@ -12,8 +12,8 @@ import {
12
12
  TrackItem,
13
13
  TrackListSidebar,
14
14
  TrackPositionPlayPause
15
- } from "../chunk-BPCFNBFO.js";
16
- import "../chunk-VDXUW7PP.js";
15
+ } from "../chunk-5PQWJRDZ.js";
16
+ import "../chunk-DKQQLLMY.js";
17
17
  export {
18
18
  GenrePlaylistTreePerRoot,
19
19
  GenreSearch,
@@ -1 +1,81 @@
1
1
  @import "@behindthemusictree/genre-tree-view/styles.css";
2
+
3
+ .track-position-play-pause__spinner {
4
+ display: inline-block;
5
+ width: 16px;
6
+ height: 16px;
7
+ flex: none;
8
+ border: 2px solid rgb(255 255 255 / 65%);
9
+ border-top-color: #22c55e;
10
+ border-radius: 50%;
11
+ animation: app-kit-track-spinner 700ms linear infinite;
12
+ }
13
+
14
+ .track-position-play-pause__bars {
15
+ display: flex;
16
+ width: 16px;
17
+ height: 16px;
18
+ align-items: flex-end;
19
+ justify-content: center;
20
+ gap: 2px;
21
+ }
22
+
23
+ .track-position-play-pause__bar {
24
+ display: block;
25
+ width: 3px;
26
+ flex: none;
27
+ border-radius: 9999px;
28
+ background-color: var(--color-green-500, #22c55e);
29
+ transform-origin: bottom center;
30
+ }
31
+
32
+ .track-position-play-pause__bar:nth-child(1) {
33
+ height: 8px;
34
+ }
35
+
36
+ .track-position-play-pause__bar:nth-child(2) {
37
+ height: 13px;
38
+ }
39
+
40
+ .track-position-play-pause__bar:nth-child(3) {
41
+ height: 10px;
42
+ }
43
+
44
+ .track-position-play-pause__bars[data-playing="true"] .track-position-play-pause__bar {
45
+ animation: app-kit-playing-bars 700ms ease-in-out infinite alternate;
46
+ }
47
+
48
+ .track-position-play-pause__bars[data-playing="true"] .track-position-play-pause__bar:nth-child(2) {
49
+ animation-delay: 180ms;
50
+ }
51
+
52
+ .track-position-play-pause__bars[data-playing="true"] .track-position-play-pause__bar:nth-child(3) {
53
+ animation-delay: 360ms;
54
+ }
55
+
56
+ @keyframes app-kit-playing-bars {
57
+ from {
58
+ transform: scaleY(0.35);
59
+ }
60
+
61
+ to {
62
+ transform: scaleY(1);
63
+ }
64
+ }
65
+
66
+ @keyframes app-kit-track-spinner {
67
+ to {
68
+ transform: rotate(360deg);
69
+ }
70
+ }
71
+
72
+ @media (prefers-reduced-motion: reduce) {
73
+ .track-position-play-pause__spinner {
74
+ animation-duration: 1400ms;
75
+ }
76
+
77
+ .track-position-play-pause__bars[data-playing="true"] .track-position-play-pause__bar {
78
+ animation: none;
79
+ }
80
+ }
81
+