@behindthemusictree/app-kit-ui 24.0.1 → 26.1.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.
package/dist/index.cjs CHANGED
@@ -471,8 +471,9 @@ function AuthCallbackHandler({ renderStatus, ...options }) {
471
471
 
472
472
  // src/genre-tree/GenreTreeView.tsx
473
473
  var import_react10 = require("react");
474
+ var import_react_query3 = require("@tanstack/react-query");
474
475
  var import_lucide_react12 = require("lucide-react");
475
- var import_ui16 = require("@behindthemusictree/ui");
476
+ var import_ui17 = require("@behindthemusictree/ui");
476
477
  var import_genre_tree_view4 = require("@behindthemusictree/genre-tree-view");
477
478
  var import_genre_tree17 = require("@behindthemusictree/app-kit/genre-tree");
478
479
  var import_genre_tree18 = require("@behindthemusictree/app-kit/genre-tree");
@@ -964,6 +965,11 @@ var import_genre_tree_view2 = require("@behindthemusictree/genre-tree-view");
964
965
  var import_player4 = require("@behindthemusictree/app-kit/player");
965
966
  var import_popup4 = require("@behindthemusictree/app-kit/popup");
966
967
  var import_transport7 = require("@behindthemusictree/app-kit/transport");
968
+
969
+ // src/genre-tree/playlist-tree/size-metric.ts
970
+ var genrePlaylistSize = (genrePlaylist, metric) => metric === "views" ? genrePlaylist.viewsCount : genrePlaylist.tracksCount;
971
+
972
+ // src/genre-tree/playlist-tree/TreeWheel.tsx
967
973
  var import_track_noun5 = require("@behindthemusictree/app-kit/track-noun");
968
974
  var import_jsx_runtime17 = require("react/jsx-runtime");
969
975
  function GenrePlaylistTreeWheel({
@@ -984,6 +990,9 @@ function GenrePlaylistTreeWheel({
984
990
  allowWheelRotation,
985
991
  showToolbar,
986
992
  controlsOrientation,
993
+ sizeMetric = "songs",
994
+ floatingControlsLeading,
995
+ busy,
987
996
  defaultViewport,
988
997
  onViewportChange,
989
998
  playback,
@@ -999,12 +1008,12 @@ function GenrePlaylistTreeWheel({
999
1008
  id: genrePlaylist.uuid,
1000
1009
  parentId: genrePlaylist.parent?.uuid ?? null,
1001
1010
  name: genrePlaylist.name,
1002
- itemCount: genrePlaylist.tracksCount,
1011
+ itemCount: genrePlaylistSize(genrePlaylist, sizeMetric),
1003
1012
  actionable: Boolean(genrePlaylist.criteria),
1004
1013
  side: genrePlaylist.criteria?.side ?? void 0,
1005
1014
  wikidataId: genrePlaylist.criteria?.wikidataId ?? null
1006
1015
  })),
1007
- [genrePlaylists]
1016
+ [genrePlaylists, sizeMetric]
1008
1017
  );
1009
1018
  const handlePlayPause = (0, import_react5.useCallback)(
1010
1019
  (nodeId) => {
@@ -1056,7 +1065,7 @@ function GenrePlaylistTreeWheel({
1056
1065
  {
1057
1066
  className,
1058
1067
  nodes,
1059
- itemCountLabel: `${(0, import_track_noun5.capitalize)(trackNoun.singular)} count`,
1068
+ itemCountLabel: sizeMetric === "views" ? "Views" : `${(0, import_track_noun5.capitalize)(trackNoun.singular)} count`,
1060
1069
  playingNodeId,
1061
1070
  playState: isPlaying ? "playing" : "paused",
1062
1071
  onPlayPause: handlePlayPause,
@@ -1074,6 +1083,8 @@ function GenrePlaylistTreeWheel({
1074
1083
  allowWheelRotation,
1075
1084
  showToolbar,
1076
1085
  controlsOrientation,
1086
+ floatingControlsLeading,
1087
+ busy,
1077
1088
  defaultViewport,
1078
1089
  onViewportChange,
1079
1090
  ...infoPanelCuration,
@@ -1107,6 +1118,9 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
1107
1118
  allowWheelRotation,
1108
1119
  showToolbar,
1109
1120
  controlsOrientation,
1121
+ sizeMetric = "songs",
1122
+ floatingControlsLeading,
1123
+ busy,
1110
1124
  outline = false,
1111
1125
  defaultViewport,
1112
1126
  onViewportChange,
@@ -1114,10 +1128,6 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
1114
1128
  } = props;
1115
1129
  const focus = props;
1116
1130
  const infoPanelCuration = {
1117
- editMode: props.editMode,
1118
- onEditModeChange: props.onEditModeChange,
1119
- renderTitleEditor: props.renderTitleEditor,
1120
- renderParentsEditor: props.renderParentsEditor,
1121
1131
  renderDangerZone: props.renderDangerZone,
1122
1132
  expanded: props.expanded,
1123
1133
  onExpandedChange: props.onExpandedChange,
@@ -1134,14 +1144,15 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
1134
1144
  id: genrePlaylist.uuid,
1135
1145
  parentId: genrePlaylist.parent?.uuid ?? null,
1136
1146
  name: genrePlaylist.name,
1137
- itemCount: genrePlaylist.tracksCount,
1147
+ itemCount: genrePlaylistSize(genrePlaylist, sizeMetric),
1138
1148
  actionable: Boolean(genrePlaylist.criteria),
1139
1149
  detached: !genrePlaylist.criteria,
1140
1150
  side: genrePlaylist.criteria?.side ?? void 0,
1141
1151
  wikidataId: genrePlaylist.criteria?.wikidataId ?? null,
1142
- umbrella: genrePlaylist.isUmbrella
1152
+ umbrella: genrePlaylist.isUmbrella,
1153
+ scene: genrePlaylist.scene ?? void 0
1143
1154
  })),
1144
- [genrePlaylists]
1155
+ [genrePlaylists, sizeMetric]
1145
1156
  );
1146
1157
  const handlePlayPause = (0, import_react6.useCallback)(
1147
1158
  (nodeId) => {
@@ -1183,7 +1194,7 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
1183
1194
  const treeProps = {
1184
1195
  className,
1185
1196
  nodes,
1186
- itemCountLabel: `${(0, import_track_noun6.capitalize)(trackNoun.singular)} count`,
1197
+ itemCountLabel: sizeMetric === "views" ? "Views" : `${(0, import_track_noun6.capitalize)(trackNoun.singular)} count`,
1187
1198
  playingNodeId,
1188
1199
  playState: isPlaying ? "playing" : "paused",
1189
1200
  onPlayPause: handlePlayPause,
@@ -1208,6 +1219,8 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
1208
1219
  ...treeProps,
1209
1220
  allowWheelRotation,
1210
1221
  controlsOrientation,
1222
+ floatingControlsLeading,
1223
+ busy,
1211
1224
  defaultViewport,
1212
1225
  onViewportChange,
1213
1226
  ...focus.onFocusGenreChange ? { focusNodeId: focus.focusGenreUuid, onFocusChange: focus.onFocusGenreChange } : { focusNodeId: void 0, onFocusChange: void 0 }
@@ -1273,6 +1286,7 @@ function GenreSearch({
1273
1286
  });
1274
1287
  const extraItems = query !== "" && query !== selectedName ? extraResults?.items ?? [] : [];
1275
1288
  const label = extraResults ? "Search" : "Search a genre";
1289
+ const loading = query !== "" && query !== selectedName && (extraResults?.loading ?? false);
1276
1290
  const showList = results.length > 0 || extraItems.length > 0;
1277
1291
  const heading = "px-3 pt-2 pb-1 text-xs font-medium uppercase text-[var(--color-text-muted)]";
1278
1292
  return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "genre-search relative w-[calc(var(--layout-detail-panel-w,25.5rem)-2*var(--layout-panel-inset,1rem))] shrink-0", children: [
@@ -1298,11 +1312,34 @@ function GenreSearch({
1298
1312
  },
1299
1313
  placeholder: placeholder ?? `${label}\u2026`,
1300
1314
  "aria-label": label,
1315
+ "aria-busy": loading,
1301
1316
  className: "h-[var(--layout-search-h,3rem)] pl-6 pr-20 text-base transition-[padding] duration-200 ease-out motion-reduce:transition-none bg-[var(--color-surface)] border-[var(--color-border)] rounded-full shadow-md"
1302
1317
  }
1303
1318
  ),
1304
1319
  /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "absolute right-3 top-1/2 flex items-center -translate-y-1/2", children: [
1305
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "p-2 text-[var(--color-text-muted)] pointer-events-none", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react7.Search, { "aria-hidden": "true", className: "w-6 h-6" }) }),
1320
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
1321
+ "span",
1322
+ {
1323
+ "data-loading": loading,
1324
+ className: "relative p-2 text-[var(--color-text-muted)] pointer-events-none",
1325
+ children: [
1326
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
1327
+ import_lucide_react7.Search,
1328
+ {
1329
+ "aria-hidden": "true",
1330
+ className: `w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? "opacity-0" : ""}`
1331
+ }
1332
+ ),
1333
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
1334
+ import_lucide_react7.LoaderCircle,
1335
+ {
1336
+ "aria-hidden": "true",
1337
+ className: `absolute inset-2 w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? "opacity-100 delay-300 animate-spin motion-reduce:animate-none" : "opacity-0"}`
1338
+ }
1339
+ )
1340
+ ]
1341
+ }
1342
+ ),
1306
1343
  canClear && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
1307
1344
  "button",
1308
1345
  {
@@ -1485,13 +1522,28 @@ function ChangeParentPopup({
1485
1522
  genrePlaylists,
1486
1523
  scope,
1487
1524
  getBackendBaseUrl,
1488
- onClose
1525
+ onClose,
1526
+ onParentChanged
1489
1527
  }) {
1490
1528
  const { mutate, isPending, formErrors } = (0, import_genre_tree11.useUpdateGenre)(scope, getBackendBaseUrl);
1491
1529
  const candidates = (0, import_react8.useMemo)(
1492
1530
  () => (0, import_genre_tree11.getReparentCandidates)(genrePlaylists, genrePlaylist.uuid),
1493
1531
  [genrePlaylists, genrePlaylist.uuid]
1494
1532
  );
1533
+ const handleSelect = (newParent) => {
1534
+ const genre = genrePlaylist.criteria.uuid;
1535
+ const parentPlaylistUuid = genrePlaylist.parent?.uuid;
1536
+ const from = parentPlaylistUuid ? genrePlaylists.find(({ uuid }) => uuid === parentPlaylistUuid)?.criteria?.uuid ?? null : null;
1537
+ mutate(
1538
+ { uuid: genre, data: { parent: newParent.criteria.uuid } },
1539
+ {
1540
+ onSuccess: () => {
1541
+ onClose();
1542
+ onParentChanged?.({ genre, from, to: newParent.criteria.uuid });
1543
+ }
1544
+ }
1545
+ );
1546
+ };
1495
1547
  (0, import_react8.useEffect)(() => {
1496
1548
  const handleKeyDown = (event) => {
1497
1549
  if (event.key === "Escape") onClose();
@@ -1508,7 +1560,7 @@ function ChangeParentPopup({
1508
1560
  GenreSearch,
1509
1561
  {
1510
1562
  genrePlaylists: candidates,
1511
- onSelect: (newParent) => mutate({ uuid: genrePlaylist.criteria.uuid, data: { parent: newParent.criteria.uuid } }, { onSuccess: onClose }),
1563
+ onSelect: handleSelect,
1512
1564
  placeholder: "Search a new parent\u2026"
1513
1565
  }
1514
1566
  ) }),
@@ -1939,8 +1991,30 @@ function useArtistPanel({
1939
1991
  }
1940
1992
  }
1941
1993
 
1942
- // src/genre-tree/GenreTreeView.tsx
1994
+ // src/genre-tree/SizeMetricToggle.tsx
1995
+ var import_md2 = require("react-icons/md");
1996
+ var import_ui16 = require("@behindthemusictree/ui");
1943
1997
  var import_jsx_runtime33 = require("react/jsx-runtime");
1998
+ function SizeMetricToggle({
1999
+ value,
2000
+ onChange
2001
+ }) {
2002
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
2003
+ import_ui16.SegmentedControl,
2004
+ {
2005
+ "aria-label": "Size nodes by",
2006
+ value,
2007
+ onChange,
2008
+ options: [
2009
+ { value: "songs", label: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_md2.MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
2010
+ { value: "views", label: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_md2.MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
2011
+ ]
2012
+ }
2013
+ );
2014
+ }
2015
+
2016
+ // src/genre-tree/GenreTreeView.tsx
2017
+ var import_jsx_runtime34 = require("react/jsx-runtime");
1944
2018
  var PREVIOUS_BUTTON_CSS = `
1945
2019
  .genre-tree-view .search-overlay .gtv-info-panel-previous { display: none; }
1946
2020
  .genre-tree-view:has(.search-overlay .gtv-info-panel-previous) .genre-search input { padding-left: 2.75rem; }
@@ -1962,7 +2036,7 @@ function GenreNodeActionBand({ genrePlaylist, scope, playback, ...bandProps }) {
1962
2036
  console.error("Failed to load genre playlist tracks:", error);
1963
2037
  });
1964
2038
  };
1965
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
2039
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
1966
2040
  GenreActionBand,
1967
2041
  {
1968
2042
  ...bandProps,
@@ -1977,6 +2051,7 @@ function GenreTreeView({
1977
2051
  scope,
1978
2052
  handleGenreCreationAction,
1979
2053
  handleGenreRenameAction,
2054
+ onParentChanged,
1980
2055
  getBackendBaseUrl,
1981
2056
  additionalActions,
1982
2057
  viewMode: controlledViewMode,
@@ -1992,6 +2067,8 @@ function GenreTreeView({
1992
2067
  criteriaOverviewSchema,
1993
2068
  renderGenreDetailExtras,
1994
2069
  controlsOrientation = "horizontal",
2070
+ sizeMetric: controlledSizeMetric,
2071
+ onSizeMetricChange,
1995
2072
  renderArtistLink,
1996
2073
  artistUuid: controlledArtistUuid,
1997
2074
  onArtistUuidChange,
@@ -2005,10 +2082,6 @@ function GenreTreeView({
2005
2082
  renderSongLeading,
2006
2083
  renderSongActions,
2007
2084
  renderTracksToolbar,
2008
- editMode,
2009
- onEditModeChange,
2010
- renderTitleEditor,
2011
- renderParentsEditor,
2012
2085
  renderDangerZone,
2013
2086
  expanded,
2014
2087
  onExpandedChange,
@@ -2021,12 +2094,20 @@ function GenreTreeView({
2021
2094
  const [internalArtistUuid, setInternalArtistUuid] = (0, import_react10.useState)(null);
2022
2095
  const isArtistControlled = controlledArtistUuid !== void 0;
2023
2096
  const artistUuid = isArtistControlled ? controlledArtistUuid : internalArtistUuid;
2097
+ const [internalSizeMetric, setInternalSizeMetric] = (0, import_react10.useState)("songs");
2098
+ const sizeMetric = controlledSizeMetric ?? internalSizeMetric;
2099
+ const deferredSizeMetric = (0, import_react10.useDeferredValue)(sizeMetric);
2100
+ const changeSizeMetric = (metric) => {
2101
+ if (controlledSizeMetric === void 0) setInternalSizeMetric(metric);
2102
+ onSizeMetricChange?.(metric);
2103
+ };
2024
2104
  const changeArtist = (uuid) => {
2025
2105
  if (!isArtistControlled) setInternalArtistUuid(uuid);
2026
2106
  onArtistUuidChange?.(uuid);
2027
2107
  };
2028
2108
  const {
2029
2109
  isLoading,
2110
+ isFetching: isTreeFetching,
2030
2111
  genrePlaylistByNodeId,
2031
2112
  visibleGenrePlaylists,
2032
2113
  groupedGenrePlaylistsByRoot,
@@ -2051,6 +2132,8 @@ function GenreTreeView({
2051
2132
  criteriaOverviewSchema,
2052
2133
  artistUuid
2053
2134
  });
2135
+ const isGenreUpdating = (0, import_react_query3.useIsMutating)({ mutationKey: import_genre_tree18.genreUpdateMutationKey }) > 0;
2136
+ const busy = deferredSizeMetric !== sizeMetric || isTreeFetching || isGenreUpdating;
2054
2137
  const artistResult = (0, import_genre_tree18.useArtist)(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
2055
2138
  const filterArtistName = artistResult.status === "success" ? artistResult.artist.name : null;
2056
2139
  const filterArtist = (0, import_react10.useMemo)(
@@ -2067,7 +2150,7 @@ function GenreTreeView({
2067
2150
  const artistPanel = useArtistPanel({
2068
2151
  uuid: artistUuid,
2069
2152
  getBackendBaseUrl: artistBaseUrl,
2070
- renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
2153
+ renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
2071
2154
  expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
2072
2155
  onExpandedChange: (on) => {
2073
2156
  if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
@@ -2105,10 +2188,6 @@ function GenreTreeView({
2105
2188
  const detailExpanded = detailOverlay ? !!detailOverlay.expanded : isExpanded;
2106
2189
  const onDetailExpandedChange = detailOverlay ? detailOverlay.onExpandedChange : changeExpanded;
2107
2190
  const infoPanelCuration = {
2108
- editMode,
2109
- onEditModeChange,
2110
- renderTitleEditor,
2111
- renderParentsEditor,
2112
2191
  renderDangerZone,
2113
2192
  expanded: placement === "regions" ? isExpanded : expanded,
2114
2193
  onExpandedChange: placement === "regions" ? changeExpanded : onExpandedChange,
@@ -2130,15 +2209,15 @@ function GenreTreeView({
2130
2209
  label: "Artists",
2131
2210
  items: artistSearchResult.artists.map((artist) => ({
2132
2211
  key: artist.uuid,
2133
- content: /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
2134
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("span", { className: "block truncate font-medium", children: [
2212
+ content: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2213
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "block truncate font-medium", children: [
2135
2214
  artist.name,
2136
- artist.disambiguation && /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("span", { className: "font-normal text-[var(--color-neutral-500)]", children: [
2215
+ artist.disambiguation && /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "font-normal text-[var(--color-neutral-500)]", children: [
2137
2216
  " \xB7 ",
2138
2217
  artist.disambiguation
2139
2218
  ] })
2140
2219
  ] }),
2141
- artist.matchedAlias && /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [
2220
+ artist.matchedAlias && /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [
2142
2221
  "also known as ",
2143
2222
  artist.matchedAlias
2144
2223
  ] })
@@ -2148,7 +2227,8 @@ function GenreTreeView({
2148
2227
  onArtistSelect?.(artist);
2149
2228
  }
2150
2229
  })),
2151
- status: artistSearchResult.isError ? "Search failed" : artistSearchResult.settled && artistSearchResult.artists.length === 0 ? "No artists found" : ""
2230
+ status: artistSearchResult.isError ? "Search failed" : artistSearchResult.settled && artistSearchResult.artists.length === 0 ? "No artists found" : "",
2231
+ loading: artistSearchResult.pending
2152
2232
  } : void 0;
2153
2233
  const handleSelectedNodeChange = (node) => {
2154
2234
  selectNode(node);
@@ -2169,15 +2249,15 @@ function GenreTreeView({
2169
2249
  (node, { toolbar, ancestors, family }) => {
2170
2250
  const genrePlaylist = genrePlaylistByNodeId.get(node.id);
2171
2251
  const criteria = genrePlaylist?.criteria;
2172
- if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
2252
+ if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2173
2253
  toolbar,
2174
2254
  family
2175
2255
  ] }), aside: ancestors };
2176
2256
  const genreUuid = criteria.uuid;
2177
2257
  return {
2178
- primary: /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
2258
+ primary: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2179
2259
  toolbar,
2180
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
2260
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2181
2261
  GenreNodeActionBand,
2182
2262
  {
2183
2263
  node,
@@ -2187,14 +2267,15 @@ function GenreTreeView({
2187
2267
  onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
2188
2268
  onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
2189
2269
  onChangeParent: readOnly ? void 0 : () => showPopup(
2190
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
2270
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2191
2271
  ChangeParentPopup,
2192
2272
  {
2193
2273
  genrePlaylist: { ...genrePlaylist, criteria },
2194
2274
  genrePlaylists: visibleGenrePlaylists,
2195
2275
  scope,
2196
2276
  getBackendBaseUrl,
2197
- onClose: hidePopup
2277
+ onClose: hidePopup,
2278
+ onParentChanged
2198
2279
  }
2199
2280
  )
2200
2281
  ),
@@ -2202,7 +2283,7 @@ function GenreTreeView({
2202
2283
  playback
2203
2284
  }
2204
2285
  ),
2205
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
2286
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2206
2287
  GenreDetailExtras,
2207
2288
  {
2208
2289
  family,
@@ -2234,6 +2315,7 @@ function GenreTreeView({
2234
2315
  handleGenreRenameAction,
2235
2316
  showPopup,
2236
2317
  hidePopup,
2318
+ onParentChanged,
2237
2319
  visibleGenrePlaylists,
2238
2320
  playback,
2239
2321
  filterArtist,
@@ -2251,7 +2333,7 @@ function GenreTreeView({
2251
2333
  );
2252
2334
  const isRotationControlled = controlledAllowWheelRotation !== void 0;
2253
2335
  const allowWheelRotation = controlledAllowWheelRotation ?? internalAllowWheelRotation;
2254
- const detailTab = placement === "regions" && (selectedNodeId || detailOverlay) ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
2336
+ const detailTab = placement === "regions" && (selectedNodeId || detailOverlay) ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2255
2337
  PanelEdgeTab,
2256
2338
  {
2257
2339
  side: "left",
@@ -2259,8 +2341,8 @@ function GenreTreeView({
2259
2341
  center: detailExpanded ? null : isCollapsed ? { label: "Show panel", onClick: () => changeCollapsed(false) } : onDetailExpandedChange ? { label: "Extend panel", onClick: () => onDetailExpandedChange(true) } : null
2260
2342
  }
2261
2343
  ) : null;
2262
- const actions = /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
2263
- !isLoading && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
2344
+ const actions = /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2345
+ !isLoading && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2264
2346
  GenreSearch,
2265
2347
  {
2266
2348
  genrePlaylists: visibleGenrePlaylists,
@@ -2272,8 +2354,8 @@ function GenreTreeView({
2272
2354
  extraResults: artistExtraResults
2273
2355
  }
2274
2356
  ),
2275
- !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
2276
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
2357
+ !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
2358
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2277
2359
  "button",
2278
2360
  {
2279
2361
  type: "button",
@@ -2285,7 +2367,7 @@ function GenreTreeView({
2285
2367
  children: filterArtist.name
2286
2368
  }
2287
2369
  ),
2288
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
2370
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2289
2371
  "button",
2290
2372
  {
2291
2373
  type: "button",
@@ -2296,8 +2378,8 @@ function GenreTreeView({
2296
2378
  }
2297
2379
  )
2298
2380
  ] }),
2299
- !isLoading && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
2300
- import_ui16.SegmentedControl,
2381
+ !isLoading && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2382
+ import_ui17.SegmentedControl,
2301
2383
  {
2302
2384
  "aria-label": "Tree view mode",
2303
2385
  value: viewMode,
@@ -2310,14 +2392,14 @@ function GenreTreeView({
2310
2392
  ]
2311
2393
  }
2312
2394
  ),
2313
- !isLoading && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
2395
+ !isLoading && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2314
2396
  "div",
2315
2397
  {
2316
2398
  className: "flex items-center gap-1",
2317
2399
  role: "group",
2318
2400
  "aria-label": "Tree display options",
2319
- children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
2320
- import_ui16.Button,
2401
+ children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2402
+ import_ui17.Button,
2321
2403
  {
2322
2404
  variant: allowWheelRotation ? "default" : "outline",
2323
2405
  size: "sm",
@@ -2327,8 +2409,8 @@ function GenreTreeView({
2327
2409
  )
2328
2410
  }
2329
2411
  ),
2330
- !isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
2331
- import_ui16.IconTextButton,
2412
+ !isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2413
+ import_ui17.IconTextButton,
2332
2414
  {
2333
2415
  icon: import_lucide_react12.Plus,
2334
2416
  text: "Add root",
@@ -2336,33 +2418,33 @@ function GenreTreeView({
2336
2418
  }
2337
2419
  )
2338
2420
  ] });
2339
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
2421
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
2340
2422
  "div",
2341
2423
  {
2342
2424
  ref: rootRef,
2343
2425
  className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className].filter(Boolean).join(" "),
2344
2426
  children: [
2345
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("style", { children: PREVIOUS_BUTTON_CSS }),
2346
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
2427
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("style", { children: PREVIOUS_BUTTON_CSS }),
2428
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2347
2429
  "div",
2348
2430
  {
2349
2431
  className: `actions-container absolute z-30 flex items-center gap-2 ${placement === "regions" ? "left-[calc(var(--layout-gutter)+var(--layout-panel-inset))] top-[calc(var(--layout-gutter)+var(--layout-panel-inset))]" : "left-5 top-5"}`,
2350
2432
  children: actions
2351
2433
  }
2352
2434
  ),
2353
- detailTab && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
2435
+ detailTab && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2354
2436
  "div",
2355
2437
  {
2356
2438
  className: `absolute top-1/2 z-30 -translate-y-1/2 transition-[opacity,visibility] duration-150 ease-out ${isTreeReady ? "opacity-100" : "pointer-events-none invisible opacity-0"} ${detailExpanded ? "right-[calc(var(--layout-center-right)-var(--layout-edge-tab-w))]" : isCollapsed ? "left-0" : "left-[calc(var(--layout-gutter)+var(--layout-left-w))]"}`,
2357
2439
  children: detailTab
2358
2440
  }
2359
2441
  ),
2360
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "tree-view-container flex-1 min-w-0 flex flex-col h-full", children: isLoading ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_genre_tree_view4.GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
2442
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "tree-view-container flex-1 min-w-0 flex flex-col h-full", children: isLoading ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_genre_tree_view4.GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
2361
2443
  // The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
2362
2444
  // and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
2363
2445
  // Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
2364
2446
  // The panel's own Zoom in button is hidden: the search bar carries it.
2365
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 [--gtv-info-panel-header-inset:calc(var(--layout-search-h,2.5rem)+var(--layout-panel-inset,0.5rem))] [&_.gtv-info-panel-header]:pb-2 [&_.gtv-info-panel-title--hidden]:h-0 [&_.gtv-toolbar]:hidden", children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
2447
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 [--gtv-info-panel-header-inset:calc(var(--layout-search-h,2.5rem)+var(--layout-panel-inset,0.5rem))] [&_.gtv-info-panel-header]:pb-2 [&_.gtv-info-panel-title--hidden]:h-0 [&_.gtv-toolbar]:hidden", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2366
2448
  GenrePlaylistTreeWheel,
2367
2449
  {
2368
2450
  scope,
@@ -2380,6 +2462,9 @@ function GenreTreeView({
2380
2462
  readOnly,
2381
2463
  allowWheelRotation,
2382
2464
  controlsOrientation,
2465
+ sizeMetric: filterArtist ? "songs" : deferredSizeMetric,
2466
+ busy,
2467
+ floatingControlsLeading: filterArtist ? void 0 : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric }),
2383
2468
  defaultViewport,
2384
2469
  onViewportChange,
2385
2470
  ...infoPanelCuration
@@ -2387,7 +2472,7 @@ function GenreTreeView({
2387
2472
  ) }) })
2388
2473
  ) : viewMode === "pop-core" ? (
2389
2474
  // Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
2390
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 [--gtv-info-panel-header-inset:calc(var(--layout-search-h,2.5rem)+var(--layout-panel-inset,0.5rem))] [&_.gtv-info-panel-header]:pb-2 [&_.gtv-info-panel-title--hidden]:h-0 [&_.gtv-toolbar]:hidden [&_.gtv-info-panel-zoom-in]:hidden", children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
2475
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 [--gtv-info-panel-header-inset:calc(var(--layout-search-h,2.5rem)+var(--layout-panel-inset,0.5rem))] [&_.gtv-info-panel-header]:pb-2 [&_.gtv-info-panel-title--hidden]:h-0 [&_.gtv-toolbar]:hidden [&_.gtv-info-panel-zoom-in]:hidden", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2391
2476
  GenrePlaylistTreeWheelRadialPopCore,
2392
2477
  {
2393
2478
  scope,
@@ -2405,13 +2490,16 @@ function GenreTreeView({
2405
2490
  readOnly,
2406
2491
  allowWheelRotation,
2407
2492
  controlsOrientation,
2493
+ sizeMetric: filterArtist ? "songs" : deferredSizeMetric,
2494
+ busy,
2495
+ floatingControlsLeading: filterArtist ? void 0 : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric }),
2408
2496
  defaultViewport,
2409
2497
  onViewportChange,
2410
2498
  ...infoPanelCuration,
2411
2499
  ...focus
2412
2500
  }
2413
2501
  ) }) })
2414
- ) : viewMode === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative [--gtv-info-panel-header-inset:calc(var(--layout-search-h,2.5rem)+var(--layout-panel-inset,0.5rem)+0.75rem)] [&_.gtv-toolbar]:hidden", children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
2502
+ ) : viewMode === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative [--gtv-info-panel-header-inset:calc(var(--layout-search-h,2.5rem)+var(--layout-panel-inset,0.5rem)+0.75rem)] [&_.gtv-toolbar]:hidden", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2415
2503
  GenrePlaylistTreeWheelRadialPopCore,
2416
2504
  {
2417
2505
  outline: true,
@@ -2430,13 +2518,13 @@ function GenreTreeView({
2430
2518
  readOnly,
2431
2519
  ...infoPanelCuration
2432
2520
  }
2433
- ) }) : /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "tree-container relative flex flex-col gap-4 w-full pt-14 overflow-x-auto overflow-y-auto text-[var(--color-text)] [&_.gtv-toolbar]:hidden", children: Object.entries(groupedGenrePlaylistsByRoot).map(
2521
+ ) }) : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "tree-container relative flex flex-col gap-4 w-full pt-14 overflow-x-auto overflow-y-auto text-[var(--color-text)] [&_.gtv-toolbar]:hidden", children: Object.entries(groupedGenrePlaylistsByRoot).map(
2434
2522
  ([uuid, genrePlaylistTreePerRoot], rootIndex) => {
2435
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
2523
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2436
2524
  "div",
2437
2525
  {
2438
2526
  className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
2439
- children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
2527
+ children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2440
2528
  GenrePlaylistTreePerRoot,
2441
2529
  {
2442
2530
  scope,
@@ -2480,7 +2568,7 @@ var import_genre_tree19 = require("@behindthemusictree/app-kit/genre-tree");
2480
2568
  var import_genre_tree20 = require("@behindthemusictree/app-kit/genre-tree");
2481
2569
  var import_genre_tree21 = require("@behindthemusictree/app-kit/genre-tree");
2482
2570
  var import_track_noun7 = require("@behindthemusictree/app-kit/track-noun");
2483
- var import_jsx_runtime34 = require("react/jsx-runtime");
2571
+ var import_jsx_runtime35 = require("react/jsx-runtime");
2484
2572
  function TrackListSidebar({
2485
2573
  className = "",
2486
2574
  renderDuration,
@@ -2505,7 +2593,7 @@ function TrackListSidebar({
2505
2593
  }, [hasMore, loadMore]);
2506
2594
  const { hideTrackListSidebar } = (0, import_genre_tree20.useTrackListSidebarVisibility)();
2507
2595
  const positionClasses = layout === "inline" ? "relative w-full flex h-full flex-col" : "fixed right-0 w-[50rem]";
2508
- return trackList ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
2596
+ return trackList ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
2509
2597
  "div",
2510
2598
  {
2511
2599
  "data-theme": "dark",
@@ -2515,16 +2603,16 @@ function TrackListSidebar({
2515
2603
  /* Match player height exactly */
2516
2604
  } : void 0,
2517
2605
  children: [
2518
- /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "header flex h-16 px-4 py-2 text-[var(--color-text)]", children: [
2519
- /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "origin flex text-xl ", children: [
2520
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
2521
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "name-container flex flex-col justify-center items-center text-[var(--color-text)] font-bold pr-2 max-w-[27.5rem]", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
2606
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "header flex h-16 px-4 py-2 text-[var(--color-text)]", children: [
2607
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "origin flex text-xl ", children: [
2608
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
2609
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "name-container flex flex-col justify-center items-center text-[var(--color-text)] font-bold pr-2 max-w-[27.5rem]", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
2522
2610
  ] }),
2523
- /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "info flex flex-col justify-center items-center pt-1 mr-2 text-[var(--color-text-muted)]", children: [
2611
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "info flex flex-col justify-center items-center pt-1 mr-2 text-[var(--color-text-muted)]", children: [
2524
2612
  trackList.origin.type === import_genre_tree21.TrackListOriginType.GENRE_PLAYLIST ? "\u2022 Genre playlist \u2022 " : trackList.origin.type === import_genre_tree21.TrackListOriginType.ARTIST ? "\u2022 Artist \u2022 " : `\u2022 ${trackNoun.singular} playlist \u2022 `,
2525
2613
  `${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular} \u2022`
2526
2614
  ] }),
2527
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2615
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2528
2616
  "button",
2529
2617
  {
2530
2618
  type: "button",
@@ -2535,12 +2623,12 @@ function TrackListSidebar({
2535
2623
  }
2536
2624
  )
2537
2625
  ] }),
2538
- /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
2626
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
2539
2627
  "ul",
2540
2628
  {
2541
2629
  className: layout === "inline" ? "track-list flex-1 min-h-0 overflow-auto list-none p-0 m-0" : "track-list overflow-auto max-h-[calc(100vh-63.5px-79px-56px-3.5px)] list-none p-0 m-0",
2542
2630
  children: [
2543
- trackList.tracks.map((track, index) => /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2631
+ trackList.tracks.map((track, index) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2544
2632
  TrackItem,
2545
2633
  {
2546
2634
  track,
@@ -2550,7 +2638,7 @@ function TrackListSidebar({
2550
2638
  renderArtistLink
2551
2639
  }
2552
2640
  ) }, track.uuid)),
2553
- hasMore && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
2641
+ hasMore && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
2554
2642
  ]
2555
2643
  }
2556
2644
  )
@@ -2565,7 +2653,7 @@ var import_lucide_react13 = require("lucide-react");
2565
2653
  var import_tailwind_merge2 = require("tailwind-merge");
2566
2654
  var import_player11 = require("@behindthemusictree/app-kit/player");
2567
2655
  var import_genre_tree22 = require("@behindthemusictree/app-kit/genre-tree");
2568
- var import_jsx_runtime35 = require("react/jsx-runtime");
2656
+ var import_jsx_runtime36 = require("react/jsx-runtime");
2569
2657
  var PANEL_ID = "player-panel";
2570
2658
  var iconButtonClassName = "flex h-6 w-6 shrink-0 items-center justify-center p-0 rounded-md hover:bg-white/10";
2571
2659
  var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
@@ -2595,7 +2683,7 @@ function PlayerPanel({
2595
2683
  label: "Extend song list panel",
2596
2684
  onClick: () => setIsExtendRequested(true)
2597
2685
  };
2598
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
2686
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
2599
2687
  "div",
2600
2688
  {
2601
2689
  id: PANEL_ID,
@@ -2608,7 +2696,7 @@ function PlayerPanel({
2608
2696
  ),
2609
2697
  style: isExtended ? { width: extendedWidth } : void 0,
2610
2698
  children: [
2611
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2699
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2612
2700
  PanelEdgeTab,
2613
2701
  {
2614
2702
  side: "right",
@@ -2618,7 +2706,7 @@ function PlayerPanel({
2618
2706
  className: "absolute right-full top-1/2 -translate-y-1/2"
2619
2707
  }
2620
2708
  ),
2621
- /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
2709
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
2622
2710
  "div",
2623
2711
  {
2624
2712
  className: (0, import_tailwind_merge2.twMerge)(
@@ -2626,26 +2714,26 @@ function PlayerPanel({
2626
2714
  isExtended ? "flex-row" : "flex-col"
2627
2715
  ),
2628
2716
  children: [
2629
- /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
2630
- /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: [
2631
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "min-w-0 truncate text-sm font-semibold", children: playerTrackObject?.track?.title }),
2632
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "flex shrink-0 items-center gap-1", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2717
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
2718
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: [
2719
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "min-w-0 truncate text-sm font-semibold", children: playerTrackObject?.track?.title }),
2720
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "flex shrink-0 items-center gap-1", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2633
2721
  "button",
2634
2722
  {
2635
2723
  type: "button",
2636
2724
  onClick: onClose,
2637
2725
  className: iconButtonClassName,
2638
2726
  "aria-label": "Close player",
2639
- children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react13.X, { size: 16 })
2727
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_lucide_react13.X, { size: 16 })
2640
2728
  }
2641
2729
  ) })
2642
2730
  ] }),
2643
- /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "relative w-full shrink-0", children: [
2644
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_player11.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
2645
- playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: playerTrackObject.loadError })
2731
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "relative w-full shrink-0", children: [
2732
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_player11.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
2733
+ playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: playerTrackObject.loadError })
2646
2734
  ] })
2647
2735
  ] }),
2648
- isTrackListSidebarVisible && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2736
+ isTrackListSidebarVisible && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2649
2737
  TrackListSidebar,
2650
2738
  {
2651
2739
  layout: "inline",
@@ -2668,11 +2756,11 @@ function PlayerPanel({
2668
2756
  var import_uuid = require("uuid");
2669
2757
  var import_ai = require("react-icons/ai");
2670
2758
  var import_genre_tree23 = require("@behindthemusictree/app-kit/genre-tree");
2671
- var import_jsx_runtime36 = require("react/jsx-runtime");
2759
+ var import_jsx_runtime37 = require("react/jsx-runtime");
2672
2760
  function Rating({ rating, handleChange }) {
2673
2761
  const instanceId = (0, import_uuid.v4)();
2674
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
2675
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2762
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
2763
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
2676
2764
  "input",
2677
2765
  {
2678
2766
  id: `star-${instanceId}-${i}`,
@@ -2684,16 +2772,16 @@ function Rating({ rating, handleChange }) {
2684
2772
  onChange: handleChange
2685
2773
  }
2686
2774
  ),
2687
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex", children: [
2688
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2775
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "flex", children: [
2776
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
2689
2777
  import_ai.AiOutlineStar,
2690
2778
  {
2691
2779
  className: `w-7 h-7 absolute top-0 left-0 ${rating === import_genre_tree23.FORM_RATING_NULL_VALUE ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
2692
2780
  }
2693
2781
  ),
2694
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "flex-grow w-8" }),
2782
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "flex-grow w-8" }),
2695
2783
  " "
2696
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2784
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
2697
2785
  import_ai.AiFillStar,
2698
2786
  {
2699
2787
  className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`