@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.
@@ -38,8 +38,9 @@ module.exports = __toCommonJS(genre_tree_exports);
38
38
 
39
39
  // src/genre-tree/GenreTreeView.tsx
40
40
  var import_react10 = require("react");
41
+ var import_react_query3 = require("@tanstack/react-query");
41
42
  var import_lucide_react8 = require("lucide-react");
42
- var import_ui13 = require("@behindthemusictree/ui");
43
+ var import_ui14 = require("@behindthemusictree/ui");
43
44
  var import_genre_tree_view4 = require("@behindthemusictree/genre-tree-view");
44
45
  var import_genre_tree17 = require("@behindthemusictree/app-kit/genre-tree");
45
46
  var import_genre_tree18 = require("@behindthemusictree/app-kit/genre-tree");
@@ -668,6 +669,11 @@ var import_genre_tree_view2 = require("@behindthemusictree/genre-tree-view");
668
669
  var import_player4 = require("@behindthemusictree/app-kit/player");
669
670
  var import_popup3 = require("@behindthemusictree/app-kit/popup");
670
671
  var import_transport6 = require("@behindthemusictree/app-kit/transport");
672
+
673
+ // src/genre-tree/playlist-tree/size-metric.ts
674
+ var genrePlaylistSize = (genrePlaylist, metric) => metric === "views" ? genrePlaylist.viewsCount : genrePlaylist.tracksCount;
675
+
676
+ // src/genre-tree/playlist-tree/TreeWheel.tsx
671
677
  var import_track_noun4 = require("@behindthemusictree/app-kit/track-noun");
672
678
  var import_jsx_runtime11 = require("react/jsx-runtime");
673
679
  function GenrePlaylistTreeWheel({
@@ -688,6 +694,9 @@ function GenrePlaylistTreeWheel({
688
694
  allowWheelRotation,
689
695
  showToolbar,
690
696
  controlsOrientation,
697
+ sizeMetric = "songs",
698
+ floatingControlsLeading,
699
+ busy,
691
700
  defaultViewport,
692
701
  onViewportChange,
693
702
  playback,
@@ -703,12 +712,12 @@ function GenrePlaylistTreeWheel({
703
712
  id: genrePlaylist.uuid,
704
713
  parentId: genrePlaylist.parent?.uuid ?? null,
705
714
  name: genrePlaylist.name,
706
- itemCount: genrePlaylist.tracksCount,
715
+ itemCount: genrePlaylistSize(genrePlaylist, sizeMetric),
707
716
  actionable: Boolean(genrePlaylist.criteria),
708
717
  side: genrePlaylist.criteria?.side ?? void 0,
709
718
  wikidataId: genrePlaylist.criteria?.wikidataId ?? null
710
719
  })),
711
- [genrePlaylists]
720
+ [genrePlaylists, sizeMetric]
712
721
  );
713
722
  const handlePlayPause = (0, import_react5.useCallback)(
714
723
  (nodeId) => {
@@ -760,7 +769,7 @@ function GenrePlaylistTreeWheel({
760
769
  {
761
770
  className,
762
771
  nodes,
763
- itemCountLabel: `${(0, import_track_noun4.capitalize)(trackNoun.singular)} count`,
772
+ itemCountLabel: sizeMetric === "views" ? "Views" : `${(0, import_track_noun4.capitalize)(trackNoun.singular)} count`,
764
773
  playingNodeId,
765
774
  playState: isPlaying ? "playing" : "paused",
766
775
  onPlayPause: handlePlayPause,
@@ -778,6 +787,8 @@ function GenrePlaylistTreeWheel({
778
787
  allowWheelRotation,
779
788
  showToolbar,
780
789
  controlsOrientation,
790
+ floatingControlsLeading,
791
+ busy,
781
792
  defaultViewport,
782
793
  onViewportChange,
783
794
  ...infoPanelCuration,
@@ -811,6 +822,9 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
811
822
  allowWheelRotation,
812
823
  showToolbar,
813
824
  controlsOrientation,
825
+ sizeMetric = "songs",
826
+ floatingControlsLeading,
827
+ busy,
814
828
  outline = false,
815
829
  defaultViewport,
816
830
  onViewportChange,
@@ -818,10 +832,6 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
818
832
  } = props;
819
833
  const focus = props;
820
834
  const infoPanelCuration = {
821
- editMode: props.editMode,
822
- onEditModeChange: props.onEditModeChange,
823
- renderTitleEditor: props.renderTitleEditor,
824
- renderParentsEditor: props.renderParentsEditor,
825
835
  renderDangerZone: props.renderDangerZone,
826
836
  expanded: props.expanded,
827
837
  onExpandedChange: props.onExpandedChange,
@@ -838,14 +848,15 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
838
848
  id: genrePlaylist.uuid,
839
849
  parentId: genrePlaylist.parent?.uuid ?? null,
840
850
  name: genrePlaylist.name,
841
- itemCount: genrePlaylist.tracksCount,
851
+ itemCount: genrePlaylistSize(genrePlaylist, sizeMetric),
842
852
  actionable: Boolean(genrePlaylist.criteria),
843
853
  detached: !genrePlaylist.criteria,
844
854
  side: genrePlaylist.criteria?.side ?? void 0,
845
855
  wikidataId: genrePlaylist.criteria?.wikidataId ?? null,
846
- umbrella: genrePlaylist.isUmbrella
856
+ umbrella: genrePlaylist.isUmbrella,
857
+ scene: genrePlaylist.scene ?? void 0
847
858
  })),
848
- [genrePlaylists]
859
+ [genrePlaylists, sizeMetric]
849
860
  );
850
861
  const handlePlayPause = (0, import_react6.useCallback)(
851
862
  (nodeId) => {
@@ -887,7 +898,7 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
887
898
  const treeProps = {
888
899
  className,
889
900
  nodes,
890
- itemCountLabel: `${(0, import_track_noun5.capitalize)(trackNoun.singular)} count`,
901
+ itemCountLabel: sizeMetric === "views" ? "Views" : `${(0, import_track_noun5.capitalize)(trackNoun.singular)} count`,
891
902
  playingNodeId,
892
903
  playState: isPlaying ? "playing" : "paused",
893
904
  onPlayPause: handlePlayPause,
@@ -912,6 +923,8 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
912
923
  ...treeProps,
913
924
  allowWheelRotation,
914
925
  controlsOrientation,
926
+ floatingControlsLeading,
927
+ busy,
915
928
  defaultViewport,
916
929
  onViewportChange,
917
930
  ...focus.onFocusGenreChange ? { focusNodeId: focus.focusGenreUuid, onFocusChange: focus.onFocusGenreChange } : { focusNodeId: void 0, onFocusChange: void 0 }
@@ -977,6 +990,7 @@ function GenreSearch({
977
990
  });
978
991
  const extraItems = query !== "" && query !== selectedName ? extraResults?.items ?? [] : [];
979
992
  const label = extraResults ? "Search" : "Search a genre";
993
+ const loading = query !== "" && query !== selectedName && (extraResults?.loading ?? false);
980
994
  const showList = results.length > 0 || extraItems.length > 0;
981
995
  const heading = "px-3 pt-2 pb-1 text-xs font-medium uppercase text-[var(--color-text-muted)]";
982
996
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "genre-search relative w-[calc(var(--layout-detail-panel-w,25.5rem)-2*var(--layout-panel-inset,1rem))] shrink-0", children: [
@@ -1002,11 +1016,34 @@ function GenreSearch({
1002
1016
  },
1003
1017
  placeholder: placeholder ?? `${label}\u2026`,
1004
1018
  "aria-label": label,
1019
+ "aria-busy": loading,
1005
1020
  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"
1006
1021
  }
1007
1022
  ),
1008
1023
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "absolute right-3 top-1/2 flex items-center -translate-y-1/2", children: [
1009
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "p-2 text-[var(--color-text-muted)] pointer-events-none", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react3.Search, { "aria-hidden": "true", className: "w-6 h-6" }) }),
1024
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
1025
+ "span",
1026
+ {
1027
+ "data-loading": loading,
1028
+ className: "relative p-2 text-[var(--color-text-muted)] pointer-events-none",
1029
+ children: [
1030
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1031
+ import_lucide_react3.Search,
1032
+ {
1033
+ "aria-hidden": "true",
1034
+ className: `w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? "opacity-0" : ""}`
1035
+ }
1036
+ ),
1037
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1038
+ import_lucide_react3.LoaderCircle,
1039
+ {
1040
+ "aria-hidden": "true",
1041
+ 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"}`
1042
+ }
1043
+ )
1044
+ ]
1045
+ }
1046
+ ),
1010
1047
  canClear && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1011
1048
  "button",
1012
1049
  {
@@ -1189,13 +1226,28 @@ function ChangeParentPopup({
1189
1226
  genrePlaylists,
1190
1227
  scope,
1191
1228
  getBackendBaseUrl,
1192
- onClose
1229
+ onClose,
1230
+ onParentChanged
1193
1231
  }) {
1194
1232
  const { mutate, isPending, formErrors } = (0, import_genre_tree11.useUpdateGenre)(scope, getBackendBaseUrl);
1195
1233
  const candidates = (0, import_react8.useMemo)(
1196
1234
  () => (0, import_genre_tree11.getReparentCandidates)(genrePlaylists, genrePlaylist.uuid),
1197
1235
  [genrePlaylists, genrePlaylist.uuid]
1198
1236
  );
1237
+ const handleSelect = (newParent) => {
1238
+ const genre = genrePlaylist.criteria.uuid;
1239
+ const parentPlaylistUuid = genrePlaylist.parent?.uuid;
1240
+ const from = parentPlaylistUuid ? genrePlaylists.find(({ uuid }) => uuid === parentPlaylistUuid)?.criteria?.uuid ?? null : null;
1241
+ mutate(
1242
+ { uuid: genre, data: { parent: newParent.criteria.uuid } },
1243
+ {
1244
+ onSuccess: () => {
1245
+ onClose();
1246
+ onParentChanged?.({ genre, from, to: newParent.criteria.uuid });
1247
+ }
1248
+ }
1249
+ );
1250
+ };
1199
1251
  (0, import_react8.useEffect)(() => {
1200
1252
  const handleKeyDown = (event) => {
1201
1253
  if (event.key === "Escape") onClose();
@@ -1212,7 +1264,7 @@ function ChangeParentPopup({
1212
1264
  GenreSearch,
1213
1265
  {
1214
1266
  genrePlaylists: candidates,
1215
- onSelect: (newParent) => mutate({ uuid: genrePlaylist.criteria.uuid, data: { parent: newParent.criteria.uuid } }, { onSuccess: onClose }),
1267
+ onSelect: handleSelect,
1216
1268
  placeholder: "Search a new parent\u2026"
1217
1269
  }
1218
1270
  ) }),
@@ -1643,8 +1695,30 @@ function useArtistPanel({
1643
1695
  }
1644
1696
  }
1645
1697
 
1646
- // src/genre-tree/GenreTreeView.tsx
1698
+ // src/genre-tree/SizeMetricToggle.tsx
1699
+ var import_md = require("react-icons/md");
1700
+ var import_ui13 = require("@behindthemusictree/ui");
1647
1701
  var import_jsx_runtime27 = require("react/jsx-runtime");
1702
+ function SizeMetricToggle({
1703
+ value,
1704
+ onChange
1705
+ }) {
1706
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1707
+ import_ui13.SegmentedControl,
1708
+ {
1709
+ "aria-label": "Size nodes by",
1710
+ value,
1711
+ onChange,
1712
+ options: [
1713
+ { value: "songs", label: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_md.MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
1714
+ { value: "views", label: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_md.MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
1715
+ ]
1716
+ }
1717
+ );
1718
+ }
1719
+
1720
+ // src/genre-tree/GenreTreeView.tsx
1721
+ var import_jsx_runtime28 = require("react/jsx-runtime");
1648
1722
  var PREVIOUS_BUTTON_CSS = `
1649
1723
  .genre-tree-view .search-overlay .gtv-info-panel-previous { display: none; }
1650
1724
  .genre-tree-view:has(.search-overlay .gtv-info-panel-previous) .genre-search input { padding-left: 2.75rem; }
@@ -1666,7 +1740,7 @@ function GenreNodeActionBand({ genrePlaylist, scope, playback, ...bandProps }) {
1666
1740
  console.error("Failed to load genre playlist tracks:", error);
1667
1741
  });
1668
1742
  };
1669
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1743
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1670
1744
  GenreActionBand,
1671
1745
  {
1672
1746
  ...bandProps,
@@ -1681,6 +1755,7 @@ function GenreTreeView({
1681
1755
  scope,
1682
1756
  handleGenreCreationAction,
1683
1757
  handleGenreRenameAction,
1758
+ onParentChanged,
1684
1759
  getBackendBaseUrl,
1685
1760
  additionalActions,
1686
1761
  viewMode: controlledViewMode,
@@ -1696,6 +1771,8 @@ function GenreTreeView({
1696
1771
  criteriaOverviewSchema,
1697
1772
  renderGenreDetailExtras,
1698
1773
  controlsOrientation = "horizontal",
1774
+ sizeMetric: controlledSizeMetric,
1775
+ onSizeMetricChange,
1699
1776
  renderArtistLink,
1700
1777
  artistUuid: controlledArtistUuid,
1701
1778
  onArtistUuidChange,
@@ -1709,10 +1786,6 @@ function GenreTreeView({
1709
1786
  renderSongLeading,
1710
1787
  renderSongActions,
1711
1788
  renderTracksToolbar,
1712
- editMode,
1713
- onEditModeChange,
1714
- renderTitleEditor,
1715
- renderParentsEditor,
1716
1789
  renderDangerZone,
1717
1790
  expanded,
1718
1791
  onExpandedChange,
@@ -1725,12 +1798,20 @@ function GenreTreeView({
1725
1798
  const [internalArtistUuid, setInternalArtistUuid] = (0, import_react10.useState)(null);
1726
1799
  const isArtistControlled = controlledArtistUuid !== void 0;
1727
1800
  const artistUuid = isArtistControlled ? controlledArtistUuid : internalArtistUuid;
1801
+ const [internalSizeMetric, setInternalSizeMetric] = (0, import_react10.useState)("songs");
1802
+ const sizeMetric = controlledSizeMetric ?? internalSizeMetric;
1803
+ const deferredSizeMetric = (0, import_react10.useDeferredValue)(sizeMetric);
1804
+ const changeSizeMetric = (metric) => {
1805
+ if (controlledSizeMetric === void 0) setInternalSizeMetric(metric);
1806
+ onSizeMetricChange?.(metric);
1807
+ };
1728
1808
  const changeArtist = (uuid) => {
1729
1809
  if (!isArtistControlled) setInternalArtistUuid(uuid);
1730
1810
  onArtistUuidChange?.(uuid);
1731
1811
  };
1732
1812
  const {
1733
1813
  isLoading,
1814
+ isFetching: isTreeFetching,
1734
1815
  genrePlaylistByNodeId,
1735
1816
  visibleGenrePlaylists,
1736
1817
  groupedGenrePlaylistsByRoot,
@@ -1755,6 +1836,8 @@ function GenreTreeView({
1755
1836
  criteriaOverviewSchema,
1756
1837
  artistUuid
1757
1838
  });
1839
+ const isGenreUpdating = (0, import_react_query3.useIsMutating)({ mutationKey: import_genre_tree18.genreUpdateMutationKey }) > 0;
1840
+ const busy = deferredSizeMetric !== sizeMetric || isTreeFetching || isGenreUpdating;
1758
1841
  const artistResult = (0, import_genre_tree18.useArtist)(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
1759
1842
  const filterArtistName = artistResult.status === "success" ? artistResult.artist.name : null;
1760
1843
  const filterArtist = (0, import_react10.useMemo)(
@@ -1771,7 +1854,7 @@ function GenreTreeView({
1771
1854
  const artistPanel = useArtistPanel({
1772
1855
  uuid: artistUuid,
1773
1856
  getBackendBaseUrl: artistBaseUrl,
1774
- renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
1857
+ renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
1775
1858
  expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
1776
1859
  onExpandedChange: (on) => {
1777
1860
  if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
@@ -1809,10 +1892,6 @@ function GenreTreeView({
1809
1892
  const detailExpanded = detailOverlay ? !!detailOverlay.expanded : isExpanded;
1810
1893
  const onDetailExpandedChange = detailOverlay ? detailOverlay.onExpandedChange : changeExpanded;
1811
1894
  const infoPanelCuration = {
1812
- editMode,
1813
- onEditModeChange,
1814
- renderTitleEditor,
1815
- renderParentsEditor,
1816
1895
  renderDangerZone,
1817
1896
  expanded: placement === "regions" ? isExpanded : expanded,
1818
1897
  onExpandedChange: placement === "regions" ? changeExpanded : onExpandedChange,
@@ -1834,15 +1913,15 @@ function GenreTreeView({
1834
1913
  label: "Artists",
1835
1914
  items: artistSearchResult.artists.map((artist) => ({
1836
1915
  key: artist.uuid,
1837
- content: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
1838
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { className: "block truncate font-medium", children: [
1916
+ content: /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_jsx_runtime28.Fragment, { children: [
1917
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { className: "block truncate font-medium", children: [
1839
1918
  artist.name,
1840
- artist.disambiguation && /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { className: "font-normal text-[var(--color-neutral-500)]", children: [
1919
+ artist.disambiguation && /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { className: "font-normal text-[var(--color-neutral-500)]", children: [
1841
1920
  " \xB7 ",
1842
1921
  artist.disambiguation
1843
1922
  ] })
1844
1923
  ] }),
1845
- artist.matchedAlias && /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [
1924
+ artist.matchedAlias && /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [
1846
1925
  "also known as ",
1847
1926
  artist.matchedAlias
1848
1927
  ] })
@@ -1852,7 +1931,8 @@ function GenreTreeView({
1852
1931
  onArtistSelect?.(artist);
1853
1932
  }
1854
1933
  })),
1855
- status: artistSearchResult.isError ? "Search failed" : artistSearchResult.settled && artistSearchResult.artists.length === 0 ? "No artists found" : ""
1934
+ status: artistSearchResult.isError ? "Search failed" : artistSearchResult.settled && artistSearchResult.artists.length === 0 ? "No artists found" : "",
1935
+ loading: artistSearchResult.pending
1856
1936
  } : void 0;
1857
1937
  const handleSelectedNodeChange = (node) => {
1858
1938
  selectNode(node);
@@ -1873,15 +1953,15 @@ function GenreTreeView({
1873
1953
  (node, { toolbar, ancestors, family }) => {
1874
1954
  const genrePlaylist = genrePlaylistByNodeId.get(node.id);
1875
1955
  const criteria = genrePlaylist?.criteria;
1876
- if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
1956
+ if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_jsx_runtime28.Fragment, { children: [
1877
1957
  toolbar,
1878
1958
  family
1879
1959
  ] }), aside: ancestors };
1880
1960
  const genreUuid = criteria.uuid;
1881
1961
  return {
1882
- primary: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
1962
+ primary: /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_jsx_runtime28.Fragment, { children: [
1883
1963
  toolbar,
1884
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1964
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1885
1965
  GenreNodeActionBand,
1886
1966
  {
1887
1967
  node,
@@ -1891,14 +1971,15 @@ function GenreTreeView({
1891
1971
  onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
1892
1972
  onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
1893
1973
  onChangeParent: readOnly ? void 0 : () => showPopup(
1894
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1974
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1895
1975
  ChangeParentPopup,
1896
1976
  {
1897
1977
  genrePlaylist: { ...genrePlaylist, criteria },
1898
1978
  genrePlaylists: visibleGenrePlaylists,
1899
1979
  scope,
1900
1980
  getBackendBaseUrl,
1901
- onClose: hidePopup
1981
+ onClose: hidePopup,
1982
+ onParentChanged
1902
1983
  }
1903
1984
  )
1904
1985
  ),
@@ -1906,7 +1987,7 @@ function GenreTreeView({
1906
1987
  playback
1907
1988
  }
1908
1989
  ),
1909
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1990
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1910
1991
  GenreDetailExtras,
1911
1992
  {
1912
1993
  family,
@@ -1938,6 +2019,7 @@ function GenreTreeView({
1938
2019
  handleGenreRenameAction,
1939
2020
  showPopup,
1940
2021
  hidePopup,
2022
+ onParentChanged,
1941
2023
  visibleGenrePlaylists,
1942
2024
  playback,
1943
2025
  filterArtist,
@@ -1955,7 +2037,7 @@ function GenreTreeView({
1955
2037
  );
1956
2038
  const isRotationControlled = controlledAllowWheelRotation !== void 0;
1957
2039
  const allowWheelRotation = controlledAllowWheelRotation ?? internalAllowWheelRotation;
1958
- const detailTab = placement === "regions" && (selectedNodeId || detailOverlay) ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2040
+ const detailTab = placement === "regions" && (selectedNodeId || detailOverlay) ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1959
2041
  PanelEdgeTab,
1960
2042
  {
1961
2043
  side: "left",
@@ -1963,8 +2045,8 @@ function GenreTreeView({
1963
2045
  center: detailExpanded ? null : isCollapsed ? { label: "Show panel", onClick: () => changeCollapsed(false) } : onDetailExpandedChange ? { label: "Extend panel", onClick: () => onDetailExpandedChange(true) } : null
1964
2046
  }
1965
2047
  ) : null;
1966
- const actions = /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
1967
- !isLoading && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2048
+ const actions = /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_jsx_runtime28.Fragment, { children: [
2049
+ !isLoading && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1968
2050
  GenreSearch,
1969
2051
  {
1970
2052
  genrePlaylists: visibleGenrePlaylists,
@@ -1976,8 +2058,8 @@ function GenreTreeView({
1976
2058
  extraResults: artistExtraResults
1977
2059
  }
1978
2060
  ),
1979
- !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
1980
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2061
+ !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
2062
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1981
2063
  "button",
1982
2064
  {
1983
2065
  type: "button",
@@ -1989,7 +2071,7 @@ function GenreTreeView({
1989
2071
  children: filterArtist.name
1990
2072
  }
1991
2073
  ),
1992
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2074
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1993
2075
  "button",
1994
2076
  {
1995
2077
  type: "button",
@@ -2000,8 +2082,8 @@ function GenreTreeView({
2000
2082
  }
2001
2083
  )
2002
2084
  ] }),
2003
- !isLoading && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2004
- import_ui13.SegmentedControl,
2085
+ !isLoading && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2086
+ import_ui14.SegmentedControl,
2005
2087
  {
2006
2088
  "aria-label": "Tree view mode",
2007
2089
  value: viewMode,
@@ -2014,14 +2096,14 @@ function GenreTreeView({
2014
2096
  ]
2015
2097
  }
2016
2098
  ),
2017
- !isLoading && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2099
+ !isLoading && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2018
2100
  "div",
2019
2101
  {
2020
2102
  className: "flex items-center gap-1",
2021
2103
  role: "group",
2022
2104
  "aria-label": "Tree display options",
2023
- children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2024
- import_ui13.Button,
2105
+ children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2106
+ import_ui14.Button,
2025
2107
  {
2026
2108
  variant: allowWheelRotation ? "default" : "outline",
2027
2109
  size: "sm",
@@ -2031,8 +2113,8 @@ function GenreTreeView({
2031
2113
  )
2032
2114
  }
2033
2115
  ),
2034
- !isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2035
- import_ui13.IconTextButton,
2116
+ !isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2117
+ import_ui14.IconTextButton,
2036
2118
  {
2037
2119
  icon: import_lucide_react8.Plus,
2038
2120
  text: "Add root",
@@ -2040,33 +2122,33 @@ function GenreTreeView({
2040
2122
  }
2041
2123
  )
2042
2124
  ] });
2043
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
2125
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
2044
2126
  "div",
2045
2127
  {
2046
2128
  ref: rootRef,
2047
2129
  className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className].filter(Boolean).join(" "),
2048
2130
  children: [
2049
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("style", { children: PREVIOUS_BUTTON_CSS }),
2050
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2131
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("style", { children: PREVIOUS_BUTTON_CSS }),
2132
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2051
2133
  "div",
2052
2134
  {
2053
2135
  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"}`,
2054
2136
  children: actions
2055
2137
  }
2056
2138
  ),
2057
- detailTab && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2139
+ detailTab && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2058
2140
  "div",
2059
2141
  {
2060
2142
  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))]"}`,
2061
2143
  children: detailTab
2062
2144
  }
2063
2145
  ),
2064
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "tree-view-container flex-1 min-w-0 flex flex-col h-full", children: isLoading ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_genre_tree_view4.GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
2146
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "tree-view-container flex-1 min-w-0 flex flex-col h-full", children: isLoading ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_genre_tree_view4.GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
2065
2147
  // The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
2066
2148
  // and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
2067
2149
  // Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
2068
2150
  // The panel's own Zoom in button is hidden: the search bar carries it.
2069
- /* @__PURE__ */ (0, import_jsx_runtime27.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_runtime27.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2151
+ /* @__PURE__ */ (0, import_jsx_runtime28.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_runtime28.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2070
2152
  GenrePlaylistTreeWheel,
2071
2153
  {
2072
2154
  scope,
@@ -2084,6 +2166,9 @@ function GenreTreeView({
2084
2166
  readOnly,
2085
2167
  allowWheelRotation,
2086
2168
  controlsOrientation,
2169
+ sizeMetric: filterArtist ? "songs" : deferredSizeMetric,
2170
+ busy,
2171
+ floatingControlsLeading: filterArtist ? void 0 : /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric }),
2087
2172
  defaultViewport,
2088
2173
  onViewportChange,
2089
2174
  ...infoPanelCuration
@@ -2091,7 +2176,7 @@ function GenreTreeView({
2091
2176
  ) }) })
2092
2177
  ) : viewMode === "pop-core" ? (
2093
2178
  // Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
2094
- /* @__PURE__ */ (0, import_jsx_runtime27.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_runtime27.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2179
+ /* @__PURE__ */ (0, import_jsx_runtime28.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_runtime28.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2095
2180
  GenrePlaylistTreeWheelRadialPopCore,
2096
2181
  {
2097
2182
  scope,
@@ -2109,13 +2194,16 @@ function GenreTreeView({
2109
2194
  readOnly,
2110
2195
  allowWheelRotation,
2111
2196
  controlsOrientation,
2197
+ sizeMetric: filterArtist ? "songs" : deferredSizeMetric,
2198
+ busy,
2199
+ floatingControlsLeading: filterArtist ? void 0 : /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric }),
2112
2200
  defaultViewport,
2113
2201
  onViewportChange,
2114
2202
  ...infoPanelCuration,
2115
2203
  ...focus
2116
2204
  }
2117
2205
  ) }) })
2118
- ) : viewMode === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime27.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_runtime27.jsx)(
2206
+ ) : viewMode === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime28.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_runtime28.jsx)(
2119
2207
  GenrePlaylistTreeWheelRadialPopCore,
2120
2208
  {
2121
2209
  outline: true,
@@ -2134,13 +2222,13 @@ function GenreTreeView({
2134
2222
  readOnly,
2135
2223
  ...infoPanelCuration
2136
2224
  }
2137
- ) }) : /* @__PURE__ */ (0, import_jsx_runtime27.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(
2225
+ ) }) : /* @__PURE__ */ (0, import_jsx_runtime28.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(
2138
2226
  ([uuid, genrePlaylistTreePerRoot], rootIndex) => {
2139
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2227
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2140
2228
  "div",
2141
2229
  {
2142
2230
  className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
2143
- children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2231
+ children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2144
2232
  GenrePlaylistTreePerRoot,
2145
2233
  {
2146
2234
  scope,
@@ -2184,7 +2272,7 @@ var import_genre_tree19 = require("@behindthemusictree/app-kit/genre-tree");
2184
2272
  var import_genre_tree20 = require("@behindthemusictree/app-kit/genre-tree");
2185
2273
  var import_genre_tree21 = require("@behindthemusictree/app-kit/genre-tree");
2186
2274
  var import_track_noun6 = require("@behindthemusictree/app-kit/track-noun");
2187
- var import_jsx_runtime28 = require("react/jsx-runtime");
2275
+ var import_jsx_runtime29 = require("react/jsx-runtime");
2188
2276
  function TrackListSidebar({
2189
2277
  className = "",
2190
2278
  renderDuration,
@@ -2209,7 +2297,7 @@ function TrackListSidebar({
2209
2297
  }, [hasMore, loadMore]);
2210
2298
  const { hideTrackListSidebar } = (0, import_genre_tree20.useTrackListSidebarVisibility)();
2211
2299
  const positionClasses = layout === "inline" ? "relative w-full flex h-full flex-col" : "fixed right-0 w-[50rem]";
2212
- return trackList ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
2300
+ return trackList ? /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
2213
2301
  "div",
2214
2302
  {
2215
2303
  "data-theme": "dark",
@@ -2219,16 +2307,16 @@ function TrackListSidebar({
2219
2307
  /* Match player height exactly */
2220
2308
  } : void 0,
2221
2309
  children: [
2222
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "header flex h-16 px-4 py-2 text-[var(--color-text)]", children: [
2223
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "origin flex text-xl ", children: [
2224
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
2225
- /* @__PURE__ */ (0, import_jsx_runtime28.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_runtime28.jsx)("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
2310
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "header flex h-16 px-4 py-2 text-[var(--color-text)]", children: [
2311
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "origin flex text-xl ", children: [
2312
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
2313
+ /* @__PURE__ */ (0, import_jsx_runtime29.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_runtime29.jsx)("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
2226
2314
  ] }),
2227
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "info flex flex-col justify-center items-center pt-1 mr-2 text-[var(--color-text-muted)]", children: [
2315
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "info flex flex-col justify-center items-center pt-1 mr-2 text-[var(--color-text-muted)]", children: [
2228
2316
  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 `,
2229
2317
  `${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular} \u2022`
2230
2318
  ] }),
2231
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2319
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
2232
2320
  "button",
2233
2321
  {
2234
2322
  type: "button",
@@ -2239,12 +2327,12 @@ function TrackListSidebar({
2239
2327
  }
2240
2328
  )
2241
2329
  ] }),
2242
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
2330
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
2243
2331
  "ul",
2244
2332
  {
2245
2333
  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",
2246
2334
  children: [
2247
- trackList.tracks.map((track, index) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2335
+ trackList.tracks.map((track, index) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
2248
2336
  TrackItem,
2249
2337
  {
2250
2338
  track,
@@ -2254,7 +2342,7 @@ function TrackListSidebar({
2254
2342
  renderArtistLink
2255
2343
  }
2256
2344
  ) }, track.uuid)),
2257
- hasMore && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
2345
+ hasMore && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
2258
2346
  ]
2259
2347
  }
2260
2348
  )
@@ -2269,7 +2357,7 @@ var import_lucide_react9 = require("lucide-react");
2269
2357
  var import_tailwind_merge2 = require("tailwind-merge");
2270
2358
  var import_player11 = require("@behindthemusictree/app-kit/player");
2271
2359
  var import_genre_tree22 = require("@behindthemusictree/app-kit/genre-tree");
2272
- var import_jsx_runtime29 = require("react/jsx-runtime");
2360
+ var import_jsx_runtime30 = require("react/jsx-runtime");
2273
2361
  var PANEL_ID = "player-panel";
2274
2362
  var iconButtonClassName = "flex h-6 w-6 shrink-0 items-center justify-center p-0 rounded-md hover:bg-white/10";
2275
2363
  var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
@@ -2299,7 +2387,7 @@ function PlayerPanel({
2299
2387
  label: "Extend song list panel",
2300
2388
  onClick: () => setIsExtendRequested(true)
2301
2389
  };
2302
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
2390
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
2303
2391
  "div",
2304
2392
  {
2305
2393
  id: PANEL_ID,
@@ -2312,7 +2400,7 @@ function PlayerPanel({
2312
2400
  ),
2313
2401
  style: isExtended ? { width: extendedWidth } : void 0,
2314
2402
  children: [
2315
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
2403
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
2316
2404
  PanelEdgeTab,
2317
2405
  {
2318
2406
  side: "right",
@@ -2322,7 +2410,7 @@ function PlayerPanel({
2322
2410
  className: "absolute right-full top-1/2 -translate-y-1/2"
2323
2411
  }
2324
2412
  ),
2325
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
2413
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
2326
2414
  "div",
2327
2415
  {
2328
2416
  className: (0, import_tailwind_merge2.twMerge)(
@@ -2330,26 +2418,26 @@ function PlayerPanel({
2330
2418
  isExtended ? "flex-row" : "flex-col"
2331
2419
  ),
2332
2420
  children: [
2333
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
2334
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: [
2335
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "min-w-0 truncate text-sm font-semibold", children: playerTrackObject?.track?.title }),
2336
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "flex shrink-0 items-center gap-1", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
2421
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
2422
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: [
2423
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "min-w-0 truncate text-sm font-semibold", children: playerTrackObject?.track?.title }),
2424
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "flex shrink-0 items-center gap-1", children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
2337
2425
  "button",
2338
2426
  {
2339
2427
  type: "button",
2340
2428
  onClick: onClose,
2341
2429
  className: iconButtonClassName,
2342
2430
  "aria-label": "Close player",
2343
- children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_lucide_react9.X, { size: 16 })
2431
+ children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_lucide_react9.X, { size: 16 })
2344
2432
  }
2345
2433
  ) })
2346
2434
  ] }),
2347
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "relative w-full shrink-0", children: [
2348
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_player11.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
2349
- playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: playerTrackObject.loadError })
2435
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "relative w-full shrink-0", children: [
2436
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_player11.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
2437
+ playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: playerTrackObject.loadError })
2350
2438
  ] })
2351
2439
  ] }),
2352
- isTrackListSidebarVisible && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
2440
+ isTrackListSidebarVisible && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
2353
2441
  TrackListSidebar,
2354
2442
  {
2355
2443
  layout: "inline",
@@ -2372,11 +2460,11 @@ function PlayerPanel({
2372
2460
  var import_uuid = require("uuid");
2373
2461
  var import_ai = require("react-icons/ai");
2374
2462
  var import_genre_tree23 = require("@behindthemusictree/app-kit/genre-tree");
2375
- var import_jsx_runtime30 = require("react/jsx-runtime");
2463
+ var import_jsx_runtime31 = require("react/jsx-runtime");
2376
2464
  function Rating({ rating, handleChange }) {
2377
2465
  const instanceId = (0, import_uuid.v4)();
2378
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
2379
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
2466
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
2467
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
2380
2468
  "input",
2381
2469
  {
2382
2470
  id: `star-${instanceId}-${i}`,
@@ -2388,16 +2476,16 @@ function Rating({ rating, handleChange }) {
2388
2476
  onChange: handleChange
2389
2477
  }
2390
2478
  ),
2391
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "flex", children: [
2392
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
2479
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "flex", children: [
2480
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
2393
2481
  import_ai.AiOutlineStar,
2394
2482
  {
2395
2483
  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)]"}`
2396
2484
  }
2397
2485
  ),
2398
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "flex-grow w-8" }),
2486
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "flex-grow w-8" }),
2399
2487
  " "
2400
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
2488
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
2401
2489
  import_ai.AiFillStar,
2402
2490
  {
2403
2491
  className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`