@behindthemusictree/app-kit-ui 30.0.0 → 31.0.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
@@ -36,7 +36,6 @@ __export(src_exports, {
36
36
  GenreTreeWheelSkeleton: () => import_genre_tree_view6.GenreTreeWheelSkeleton,
37
37
  InternalErrorPopup: () => InternalErrorPopup,
38
38
  NetworkErrorPopup: () => NetworkErrorPopup,
39
- PanelEdgeTab: () => PanelEdgeTab,
40
39
  PlayerPanel: () => PlayerPanel,
41
40
  PopupButtons: () => PopupButtons,
42
41
  PopupTitle: () => PopupTitle,
@@ -47,8 +46,7 @@ __export(src_exports, {
47
46
  TrackListSidebar: () => TrackListSidebar,
48
47
  TrackPositionPlayPause: () => TrackPositionPlayPause,
49
48
  TrackUploadPopup: () => TrackUploadPopup,
50
- useGenrePlaylistPlayback: () => useGenrePlaylistPlayback,
51
- useLeftDetailPanelLayout: () => useLeftDetailPanelLayout
49
+ useGenrePlaylistPlayback: () => useGenrePlaylistPlayback
52
50
  });
53
51
  module.exports = __toCommonJS(src_exports);
54
52
 
@@ -580,8 +578,9 @@ function AuthCallbackHandler({ renderStatus, ...options }) {
580
578
  }
581
579
 
582
580
  // src/genre-tree/GenreTreeView.tsx
583
- var import_react17 = require("react");
581
+ var import_react16 = require("react");
584
582
  var import_react_query3 = require("@tanstack/react-query");
583
+ var import_lucide_react15 = require("lucide-react");
585
584
  var import_lucide_react16 = require("lucide-react");
586
585
  var import_ui18 = require("@behindthemusictree/ui");
587
586
  var import_genre_tree_view4 = require("@behindthemusictree/genre-tree-view");
@@ -589,6 +588,32 @@ var import_genre_tree18 = require("@behindthemusictree/app-kit/genre-tree");
589
588
  var import_genre_tree19 = require("@behindthemusictree/app-kit/genre-tree");
590
589
  var import_popup6 = require("@behindthemusictree/app-kit/popup");
591
590
 
591
+ // src/genre-tree/playlist-tree/size-metric.ts
592
+ var hasViewsCount = (genrePlaylists) => genrePlaylists.every((genrePlaylist) => genrePlaylist.viewsCount !== void 0);
593
+ var genrePlaylistSize = (genrePlaylist, metric) => {
594
+ if (metric === "songs") return genrePlaylist.tracksCount;
595
+ if (genrePlaylist.viewsCount === void 0) {
596
+ throw new Error(`Genre playlist ${genrePlaylist.uuid} has no viewsCount to size by views`);
597
+ }
598
+ return genrePlaylist.viewsCount;
599
+ };
600
+
601
+ // src/genre-tree/playlist-tree/toTreeNodes.ts
602
+ function toTreeNodes(genrePlaylists, sizeMetric) {
603
+ return genrePlaylists.map((genrePlaylist) => ({
604
+ id: genrePlaylist.uuid,
605
+ parentId: genrePlaylist.parent?.uuid ?? null,
606
+ name: genrePlaylist.name,
607
+ itemCount: genrePlaylistSize(genrePlaylist, sizeMetric),
608
+ actionable: Boolean(genrePlaylist.criteria),
609
+ detached: !genrePlaylist.criteria,
610
+ side: genrePlaylist.criteria?.side ?? void 0,
611
+ wikidataId: genrePlaylist.criteria?.wikidataId ?? null,
612
+ umbrella: genrePlaylist.isUmbrella,
613
+ scene: genrePlaylist.scene ?? void 0
614
+ }));
615
+ }
616
+
592
617
  // src/genre-tree/playlist-tree/TreePerRoot.tsx
593
618
  var import_react4 = require("react");
594
619
  var import_genre_tree_view = require("@behindthemusictree/genre-tree-view");
@@ -625,7 +650,6 @@ function useGenrePlaylistPlayback({
625
650
  }
626
651
 
627
652
  // src/genre-tree/playlist-tree/TreePerRoot.tsx
628
- var import_track_noun2 = require("@behindthemusictree/app-kit/track-noun");
629
653
  var import_jsx_runtime12 = require("react/jsx-runtime");
630
654
  function GenrePlaylistTreePerRoot({
631
655
  scope,
@@ -637,24 +661,19 @@ function GenrePlaylistTreePerRoot({
637
661
  additionalActions,
638
662
  onNodeClick,
639
663
  onNodeHover,
640
- renderInfoPanelBody,
641
664
  selectedNodeId,
642
665
  onSelectedNodeChange,
643
- hideInfoPanelClose,
644
- hideInfoPanelTitle,
645
666
  readOnly = false,
646
- showToolbar,
647
667
  wheelZoom,
648
668
  defaultViewport,
649
669
  viewportRestore,
650
670
  onViewportChange,
651
671
  playback,
652
- ...infoPanelCuration
672
+ ...hostPanel
653
673
  }) {
654
674
  const { isPlaying, setIsPlaying } = (0, import_player.usePlayer)();
655
675
  const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists: genrePlaylistTreePerRoot });
656
676
  const { playingNodeId, play } = playback ?? defaultPlayback;
657
- const trackNoun = (0, import_track_noun2.useTrackNoun)();
658
677
  const { showPopup } = (0, import_popup2.usePopup)();
659
678
  const nodes = (0, import_react4.useMemo)(
660
679
  () => genrePlaylistTreePerRoot.map((genrePlaylist) => ({
@@ -719,7 +738,6 @@ function GenrePlaylistTreePerRoot({
719
738
  {
720
739
  className: className2,
721
740
  nodes,
722
- itemCountLabel: `${(0, import_track_noun2.capitalize)(trackNoun.singular)} count`,
723
741
  rootColor: (0, import_genre_tree_view.getGenreTreeColor)(rootUuid),
724
742
  playingNodeId,
725
743
  playState: isPlaying ? "playing" : "paused",
@@ -730,18 +748,13 @@ function GenrePlaylistTreePerRoot({
730
748
  additionalActions,
731
749
  onNodeClick,
732
750
  onNodeHover,
733
- renderInfoPanelBody,
734
751
  selectedNodeId,
735
752
  onSelectedNodeChange,
736
- hideInfoPanelClose,
737
- hideInfoPanelTitle,
738
- showToolbar,
739
753
  wheelZoom,
740
754
  defaultViewport,
741
755
  viewportRestore,
742
756
  onViewportChange,
743
- ...infoPanelCuration,
744
- readOnly
757
+ ...hostPanel
745
758
  }
746
759
  );
747
760
  }
@@ -752,19 +765,6 @@ var import_genre_tree_view2 = require("@behindthemusictree/genre-tree-view");
752
765
  var import_player2 = require("@behindthemusictree/app-kit/player");
753
766
  var import_popup3 = require("@behindthemusictree/app-kit/popup");
754
767
  var import_transport3 = require("@behindthemusictree/app-kit/transport");
755
-
756
- // src/genre-tree/playlist-tree/size-metric.ts
757
- var hasViewsCount = (genrePlaylists) => genrePlaylists.every((genrePlaylist) => genrePlaylist.viewsCount !== void 0);
758
- var genrePlaylistSize = (genrePlaylist, metric) => {
759
- if (metric === "songs") return genrePlaylist.tracksCount;
760
- if (genrePlaylist.viewsCount === void 0) {
761
- throw new Error(`Genre playlist ${genrePlaylist.uuid} has no viewsCount to size by views`);
762
- }
763
- return genrePlaylist.viewsCount;
764
- };
765
-
766
- // src/genre-tree/playlist-tree/TreeWheel.tsx
767
- var import_track_noun3 = require("@behindthemusictree/app-kit/track-noun");
768
768
  var import_jsx_runtime13 = require("react/jsx-runtime");
769
769
  function GenrePlaylistTreeWheel({
770
770
  scope,
@@ -775,14 +775,10 @@ function GenrePlaylistTreeWheel({
775
775
  additionalActions,
776
776
  onNodeClick,
777
777
  onNodeHover,
778
- renderInfoPanelBody,
779
778
  selectedNodeId,
780
779
  onSelectedNodeChange,
781
- hideInfoPanelClose,
782
- hideInfoPanelTitle,
783
780
  readOnly = false,
784
781
  allowWheelRotation,
785
- showToolbar,
786
782
  sizeMetric = "songs",
787
783
  floatingControlsLeading,
788
784
  busy,
@@ -790,12 +786,11 @@ function GenrePlaylistTreeWheel({
790
786
  viewportRestore,
791
787
  onViewportChange,
792
788
  playback,
793
- ...infoPanelCuration
789
+ ...hostPanel
794
790
  }) {
795
791
  const { isPlaying, setIsPlaying } = (0, import_player2.usePlayer)();
796
792
  const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists });
797
793
  const { playingNodeId, play } = playback ?? defaultPlayback;
798
- const trackNoun = (0, import_track_noun3.useTrackNoun)();
799
794
  const { showPopup } = (0, import_popup3.usePopup)();
800
795
  const nodes = (0, import_react5.useMemo)(
801
796
  () => genrePlaylists.map((genrePlaylist) => ({
@@ -859,7 +854,6 @@ function GenrePlaylistTreeWheel({
859
854
  {
860
855
  className: className2,
861
856
  nodes,
862
- itemCountLabel: sizeMetric === "views" ? "Views" : `${(0, import_track_noun3.capitalize)(trackNoun.singular)} count`,
863
857
  playingNodeId,
864
858
  playState: isPlaying ? "playing" : "paused",
865
859
  onPlayPause: handlePlayPause,
@@ -869,20 +863,15 @@ function GenrePlaylistTreeWheel({
869
863
  additionalActions,
870
864
  onNodeClick,
871
865
  onNodeHover,
872
- renderInfoPanelBody,
873
866
  selectedNodeId,
874
867
  onSelectedNodeChange,
875
- hideInfoPanelClose,
876
- hideInfoPanelTitle,
877
868
  allowWheelRotation,
878
- showToolbar,
879
869
  floatingControlsLeading,
880
870
  busy,
881
871
  defaultViewport,
882
872
  viewportRestore,
883
873
  onViewportChange,
884
- ...infoPanelCuration,
885
- readOnly
874
+ ...hostPanel
886
875
  }
887
876
  );
888
877
  }
@@ -891,7 +880,6 @@ function GenrePlaylistTreeWheel({
891
880
  var import_react6 = require("react");
892
881
  var import_genre_tree_view3 = require("@behindthemusictree/genre-tree-view");
893
882
  var import_player3 = require("@behindthemusictree/app-kit/player");
894
- var import_track_noun4 = require("@behindthemusictree/app-kit/track-noun");
895
883
  var import_jsx_runtime14 = require("react/jsx-runtime");
896
884
  function GenrePlaylistTreeWheelRadialPopCore(props) {
897
885
  const {
@@ -903,14 +891,10 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
903
891
  additionalActions,
904
892
  onNodeClick,
905
893
  onNodeHover,
906
- renderInfoPanelBody,
907
894
  selectedNodeId,
908
895
  onSelectedNodeChange,
909
- hideInfoPanelClose,
910
- hideInfoPanelTitle,
911
896
  readOnly = false,
912
897
  allowWheelRotation,
913
- showToolbar,
914
898
  sizeMetric = "songs",
915
899
  floatingControlsLeading,
916
900
  busy,
@@ -921,33 +905,14 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
921
905
  playback
922
906
  } = props;
923
907
  const focus = props;
924
- const infoPanelCuration = {
925
- renderDangerZone: props.renderDangerZone,
926
- expanded: props.expanded,
927
- onExpandedChange: props.onExpandedChange,
928
- collapsed: props.collapsed,
929
- hideExpandButton: props.hideExpandButton,
930
- overlayPanel: props.overlayPanel
908
+ const hostPanel = {
909
+ obscuredInset: props.obscuredInset,
910
+ onNodeStyleResolverChange: props.onNodeStyleResolverChange
931
911
  };
932
912
  const { isPlaying, setIsPlaying } = (0, import_player3.usePlayer)();
933
913
  const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists });
934
914
  const { playingNodeId, play } = playback ?? defaultPlayback;
935
- const trackNoun = (0, import_track_noun4.useTrackNoun)();
936
- const nodes = (0, import_react6.useMemo)(
937
- () => genrePlaylists.map((genrePlaylist) => ({
938
- id: genrePlaylist.uuid,
939
- parentId: genrePlaylist.parent?.uuid ?? null,
940
- name: genrePlaylist.name,
941
- itemCount: genrePlaylistSize(genrePlaylist, sizeMetric),
942
- actionable: Boolean(genrePlaylist.criteria),
943
- detached: !genrePlaylist.criteria,
944
- side: genrePlaylist.criteria?.side ?? void 0,
945
- wikidataId: genrePlaylist.criteria?.wikidataId ?? null,
946
- umbrella: genrePlaylist.isUmbrella,
947
- scene: genrePlaylist.scene ?? void 0
948
- })),
949
- [genrePlaylists, sizeMetric]
950
- );
915
+ const nodes = (0, import_react6.useMemo)(() => toTreeNodes(genrePlaylists, sizeMetric), [genrePlaylists, sizeMetric]);
951
916
  const handlePlayPause = (0, import_react6.useCallback)(
952
917
  (nodeId) => {
953
918
  const genrePlaylist = genrePlaylists.find((g) => g.uuid === nodeId);
@@ -988,7 +953,6 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
988
953
  const treeProps = {
989
954
  className: className2,
990
955
  nodes,
991
- itemCountLabel: sizeMetric === "views" ? "Views" : `${(0, import_track_noun4.capitalize)(trackNoun.singular)} count`,
992
956
  playingNodeId,
993
957
  playState: isPlaying ? "playing" : "paused",
994
958
  onPlayPause: handlePlayPause,
@@ -998,14 +962,9 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
998
962
  additionalActions,
999
963
  onNodeClick,
1000
964
  onNodeHover,
1001
- renderInfoPanelBody,
1002
965
  selectedNodeId,
1003
966
  onSelectedNodeChange,
1004
- hideInfoPanelClose,
1005
- hideInfoPanelTitle,
1006
- showToolbar,
1007
- ...infoPanelCuration,
1008
- readOnly
967
+ ...hostPanel
1009
968
  };
1010
969
  return outline ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_genre_tree_view3.GenreTreeOutline, { ...treeProps }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1011
970
  import_genre_tree_view3.GenreTreeWheelRadialPopCore,
@@ -1125,7 +1084,7 @@ function GenreSearch({
1125
1084
  type: "button",
1126
1085
  "aria-label": "Previous",
1127
1086
  title: "Previous",
1128
- className: "genre-search-previous absolute left-2 top-1/2 z-10 hidden items-center justify-center p-1.5 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] [@media(pointer:coarse)]:min-w-[var(--layout-touch-target)] text-[var(--color-text-muted)] rounded-full -translate-y-1/2 hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-text)]",
1087
+ className: "genre-search-previous absolute left-2 top-1/2 z-10 flex items-center justify-center p-1.5 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] [@media(pointer:coarse)]:min-w-[var(--layout-touch-target)] text-[var(--color-text-muted)] rounded-full -translate-y-1/2 hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-text)]",
1129
1088
  onClick: onPrevious,
1130
1089
  children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react7.ArrowLeft, { "aria-hidden": "true", className: "w-4 h-4" })
1131
1090
  }
@@ -1147,7 +1106,7 @@ function GenreSearch({
1147
1106
  onPick: handlePick,
1148
1107
  status,
1149
1108
  loading: loading ? "Searching\u2026" : void 0,
1150
- inputClassName: "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",
1109
+ inputClassName: `h-[var(--layout-search-h,3rem)] ${onPrevious ? "pl-11" : "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`,
1151
1110
  className: "genre-search-results z-40 rounded-xl shadow-md",
1152
1111
  listClassName: "max-h-[min(20rem,50dvh)]",
1153
1112
  optionClassName: "[@media(pointer:coarse)]:min-h-[var(--layout-touch-target)]"
@@ -1584,7 +1543,7 @@ function MusicBrainzRecordingLink({
1584
1543
 
1585
1544
  // src/genre-tree/GenrePlaylistTracks.tsx
1586
1545
  var import_genre_tree8 = require("@behindthemusictree/app-kit/genre-tree");
1587
- var import_track_noun5 = require("@behindthemusictree/app-kit/track-noun");
1546
+ var import_track_noun2 = require("@behindthemusictree/app-kit/track-noun");
1588
1547
  var import_jsx_runtime24 = require("react/jsx-runtime");
1589
1548
  var CATALOGUE_PAGE_SIZE = 100;
1590
1549
  var SongPageSchema = (0, import_transport5.PaginatedResponseSchema)(import_genre_tree6.SongDetailedSchema).extend({
@@ -1618,7 +1577,7 @@ function GenrePlaylistTracks({
1618
1577
  const { session, sessionRestored } = (0, import_auth2.useSession)();
1619
1578
  const { playerTrackObject, playState, handlePlayPauseAction } = (0, import_player4.usePlayer)();
1620
1579
  const { showPopup } = (0, import_popup5.usePopup)();
1621
- const trackNoun = (0, import_track_noun5.useTrackNoun)();
1580
+ const trackNoun = (0, import_track_noun2.useTrackNoun)();
1622
1581
  const { uuid } = genrePlaylist;
1623
1582
  const { fetch } = (0, import_transport4.useFetchWrapper)(getBackendBaseUrl);
1624
1583
  const { data, isPending, isError, refetch, hasNextPage, isFetchingNextPage, fetchNextPage } = (0, import_react_query.useInfiniteQuery)({
@@ -1663,7 +1622,7 @@ function GenrePlaylistTracks({
1663
1622
  };
1664
1623
  return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "gtv-info-panel-children", children: [
1665
1624
  /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { className: "gtv-info-panel-children-title", children: [
1666
- (0, import_track_noun5.capitalize)(trackNoun.plural),
1625
+ (0, import_track_noun2.capitalize)(trackNoun.plural),
1667
1626
  total !== void 0 && ` (${total})`
1668
1627
  ] }),
1669
1628
  renderTracksToolbar?.(),
@@ -1734,7 +1693,7 @@ function GenrePlaylistTracks({
1734
1693
  }
1735
1694
 
1736
1695
  // src/genre-tree/GenreDetailExtras.tsx
1737
- var import_track_noun6 = require("@behindthemusictree/app-kit/track-noun");
1696
+ var import_track_noun3 = require("@behindthemusictree/app-kit/track-noun");
1738
1697
  var import_si2 = require("react-icons/si");
1739
1698
  var import_jsx_runtime25 = require("react/jsx-runtime");
1740
1699
  function GenreDetailExtras({
@@ -1752,7 +1711,7 @@ function GenreDetailExtras({
1752
1711
  family,
1753
1712
  ...trackSlots
1754
1713
  }) {
1755
- const trackNoun = (0, import_track_noun6.useTrackNoun)();
1714
+ const trackNoun = (0, import_track_noun3.useTrackNoun)();
1756
1715
  const { data: overview, isPending } = (0, import_genre_tree9.useFetchGenreOverview)(
1757
1716
  genreUuid,
1758
1717
  scope,
@@ -1886,66 +1845,10 @@ function GenreInfoPanelBody({
1886
1845
  ] });
1887
1846
  }
1888
1847
 
1889
- // src/genre-tree/PanelEdgeTab.tsx
1890
- var import_lucide_react12 = require("lucide-react");
1891
- var import_tailwind_merge = require("tailwind-merge");
1892
- var import_jsx_runtime27 = require("react/jsx-runtime");
1893
- var buttonClassName = "flex h-12 w-full items-center justify-center p-0 text-[var(--color-text-muted)] hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-text)] focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--color-primary)]";
1894
- function PanelEdgeTab({ side, edge, center, controls, className: className2 }) {
1895
- if (!edge && !center) return null;
1896
- const isLeft = side === "left";
1897
- const EdgeIcon = isLeft ? import_lucide_react12.ChevronLeft : import_lucide_react12.ChevronRight;
1898
- const CenterIcon = isLeft ? import_lucide_react12.ChevronRight : import_lucide_react12.ChevronLeft;
1899
- const renderButton = (action, icon) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1900
- "button",
1901
- {
1902
- type: "button",
1903
- "aria-label": action.label,
1904
- title: action.label,
1905
- "aria-controls": controls,
1906
- "aria-expanded": action.expanded,
1907
- className: buttonClassName,
1908
- onClick: action.onClick,
1909
- children: icon
1910
- }
1911
- );
1912
- const edgeButton = edge && renderButton(edge, /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(EdgeIcon, { "aria-hidden": "true", className: "h-5 w-5" }));
1913
- const centerButton = center && renderButton(center, /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(CenterIcon, { "aria-hidden": "true", className: "h-5 w-5" }));
1914
- const buttons = isLeft ? [edgeButton, centerButton] : [centerButton, edgeButton];
1915
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
1916
- "div",
1917
- {
1918
- className: (0, import_tailwind_merge.twMerge)(
1919
- "panel-edge-tab flex w-[var(--layout-edge-tab-w)] flex-col overflow-hidden bg-[var(--color-surface)] shadow-[0_1px_2px_rgba(60,64,67,0.3),0_2px_6px_2px_rgba(60,64,67,0.15)] [clip-path:inset(-0.5rem_-0.5rem_-0.5rem_0)]",
1920
- isLeft ? "rounded-r-lg" : "rounded-l-lg [clip-path:inset(-0.5rem_0_-0.5rem_-0.5rem)]",
1921
- className2
1922
- ),
1923
- children: [
1924
- buttons[0],
1925
- buttons[0] && buttons[1] && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { role: "presentation", className: "h-px shrink-0 bg-[var(--color-border)]" }),
1926
- buttons[1]
1927
- ]
1928
- }
1929
- );
1930
- }
1931
-
1932
- // src/genre-tree/useLeftDetailPanelLayout.ts
1933
- var import_react13 = require("react");
1934
- function useLeftDetailPanelLayout(isPanelShown, enabled = true) {
1935
- (0, import_react13.useEffect)(() => {
1936
- if (!enabled || isPanelShown) return void 0;
1937
- const root = document.documentElement;
1938
- root.style.setProperty("--layout-left-w", "0px");
1939
- return () => {
1940
- root.style.removeProperty("--layout-left-w");
1941
- };
1942
- }, [enabled, isPanelShown]);
1943
- }
1944
-
1945
1848
  // src/genre-tree/ChangeParentPopup.tsx
1946
- var import_react14 = require("react");
1849
+ var import_react13 = require("react");
1947
1850
  var import_genre_tree10 = require("@behindthemusictree/app-kit/genre-tree");
1948
- var import_jsx_runtime28 = require("react/jsx-runtime");
1851
+ var import_jsx_runtime27 = require("react/jsx-runtime");
1949
1852
  function ChangeParentPopup({
1950
1853
  genrePlaylist,
1951
1854
  genrePlaylists,
@@ -1955,7 +1858,7 @@ function ChangeParentPopup({
1955
1858
  onParentChanged
1956
1859
  }) {
1957
1860
  const { mutate, isPending, formErrors } = (0, import_genre_tree10.useUpdateGenre)(scope, getBackendBaseUrl);
1958
- const candidates = (0, import_react14.useMemo)(
1861
+ const candidates = (0, import_react13.useMemo)(
1959
1862
  () => (0, import_genre_tree10.getReparentCandidates)(genrePlaylists, genrePlaylist.uuid),
1960
1863
  [genrePlaylists, genrePlaylist.uuid]
1961
1864
  );
@@ -1973,19 +1876,19 @@ function ChangeParentPopup({
1973
1876
  }
1974
1877
  );
1975
1878
  };
1976
- (0, import_react14.useEffect)(() => {
1879
+ (0, import_react13.useEffect)(() => {
1977
1880
  const handleKeyDown = (event) => {
1978
1881
  if (event.key === "Escape") onClose();
1979
1882
  };
1980
1883
  window.addEventListener("keydown", handleKeyDown);
1981
1884
  return () => window.removeEventListener("keydown", handleKeyDown);
1982
1885
  }, [onClose]);
1983
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
1984
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("p", { className: "mb-3 text-sm text-[var(--color-text-muted)]", children: [
1886
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
1887
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("p", { className: "mb-3 text-sm text-[var(--color-text-muted)]", children: [
1985
1888
  "Current parent: ",
1986
1889
  genrePlaylist.parent?.name ?? "none"
1987
1890
  ] }),
1988
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1891
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1989
1892
  GenreSearch,
1990
1893
  {
1991
1894
  genrePlaylists: candidates,
@@ -1993,7 +1896,7 @@ function ChangeParentPopup({
1993
1896
  placeholder: "Search a new parent\u2026"
1994
1897
  }
1995
1898
  ) }),
1996
- formErrors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("p", { role: "alert", className: "mt-3 text-sm text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
1899
+ formErrors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { role: "alert", className: "mt-3 text-sm text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
1997
1900
  ] });
1998
1901
  }
1999
1902
 
@@ -2005,12 +1908,12 @@ var import_genre_tree15 = require("@behindthemusictree/app-kit/genre-tree");
2005
1908
  var import_ui10 = require("@behindthemusictree/ui");
2006
1909
 
2007
1910
  // src/genre-tree/artist/ArtistLinks.tsx
2008
- var import_lucide_react13 = require("lucide-react");
1911
+ var import_lucide_react12 = require("lucide-react");
2009
1912
  var import_si3 = require("react-icons/si");
2010
1913
  var import_ui9 = require("@behindthemusictree/ui");
2011
- var import_jsx_runtime29 = require("react/jsx-runtime");
1914
+ var import_jsx_runtime28 = require("react/jsx-runtime");
2012
1915
  var LINK_KINDS = {
2013
- official: { label: "Official website", Icon: import_lucide_react13.Globe },
1916
+ official: { label: "Official website", Icon: import_lucide_react12.Globe },
2014
1917
  wikipedia: { label: "Wikipedia", Icon: import_si3.SiWikipedia },
2015
1918
  wikidata: { label: "Wikidata", Icon: import_si3.SiWikidata },
2016
1919
  spotify: { label: "Spotify", Icon: import_si3.SiSpotify },
@@ -2018,7 +1921,7 @@ var LINK_KINDS = {
2018
1921
  bandcamp: { label: "Bandcamp", Icon: import_si3.SiBandcamp },
2019
1922
  soundcloud: { label: "SoundCloud", Icon: import_si3.SiSoundcloud },
2020
1923
  discogs: { label: "Discogs", Icon: import_si3.SiDiscogs },
2021
- social: { label: "Social", Icon: import_lucide_react13.Share2 }
1924
+ social: { label: "Social", Icon: import_lucide_react12.Share2 }
2022
1925
  };
2023
1926
  function socialLabel(url) {
2024
1927
  try {
@@ -2045,7 +1948,7 @@ function artistLinks(artist) {
2045
1948
  return links;
2046
1949
  }
2047
1950
  function ArtistLinks({ links }) {
2048
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui9.Chip, { href: url, icon: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
1951
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_ui9.Chip, { href: url, icon: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
2049
1952
  }
2050
1953
 
2051
1954
  // src/genre-tree/artist/formatYears.ts
@@ -2056,7 +1959,7 @@ function formatYears(beginYear, endYear, ended) {
2056
1959
  }
2057
1960
 
2058
1961
  // src/genre-tree/artist/ArtistHeader.tsx
2059
- var import_jsx_runtime30 = require("react/jsx-runtime");
1962
+ var import_jsx_runtime29 = require("react/jsx-runtime");
2060
1963
  var countryNames = new Intl.DisplayNames(["en"], { type: "region" });
2061
1964
  function ArtistHeader({ artist }) {
2062
1965
  const fields = [];
@@ -2066,35 +1969,35 @@ function ArtistHeader({ artist }) {
2066
1969
  const years = formatYears(artist.beginYear, artist.endYear, artist.ended);
2067
1970
  if (years) fields.push({ label: "Active", value: years });
2068
1971
  const links = artistLinks(artist);
2069
- if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ArtistLinks, { links }) });
1972
+ if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(ArtistLinks, { links }) });
2070
1973
  if (fields.length === 0) return null;
2071
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui10.FieldList, { items: fields });
1974
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui10.FieldList, { items: fields });
2072
1975
  }
2073
1976
 
2074
1977
  // src/genre-tree/artist/ArtistPhoto.tsx
2075
- var import_jsx_runtime31 = require("react/jsx-runtime");
1978
+ var import_jsx_runtime30 = require("react/jsx-runtime");
2076
1979
  function ExternalLink({ href, children }) {
2077
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
1980
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
2078
1981
  }
2079
1982
  function ArtistPhoto({ image, name }) {
2080
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
2081
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
2082
- /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("figcaption", { className: "text-xs text-[var(--color-text-muted)]", children: [
1983
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
1984
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
1985
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("figcaption", { className: "text-xs text-[var(--color-text-muted)]", children: [
2083
1986
  "Photo: ",
2084
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
1987
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
2085
1988
  ",",
2086
1989
  " ",
2087
- image.licenseUrl ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
1990
+ image.licenseUrl ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
2088
1991
  ] })
2089
1992
  ] });
2090
1993
  }
2091
1994
 
2092
1995
  // src/genre-tree/artist/ArtistMembers.tsx
2093
1996
  var import_ui11 = require("@behindthemusictree/ui");
2094
- var import_jsx_runtime32 = require("react/jsx-runtime");
1997
+ var import_jsx_runtime31 = require("react/jsx-runtime");
2095
1998
  function MembershipList({ title, memberships, renderArtistLink }) {
2096
1999
  if (memberships.length === 0) return null;
2097
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui11.PanelGroup, { title, children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("ul", { children: memberships.map((membership, index) => {
2000
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ui11.PanelGroup, { title, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("ul", { children: memberships.map((membership, index) => {
2098
2001
  const details = [
2099
2002
  formatYears(membership.beginYear, membership.endYear, membership.ended),
2100
2003
  ...membership.roles
@@ -2102,9 +2005,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
2102
2005
  const detailsText = details.filter(Boolean).join(" \xB7 ");
2103
2006
  return (
2104
2007
  // A member can have several periods in the same group.
2105
- /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
2106
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
2107
- detailsText && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
2008
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
2009
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
2010
+ detailsText && /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
2108
2011
  " \u2014 ",
2109
2012
  detailsText
2110
2013
  ] })
@@ -2115,9 +2018,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
2115
2018
  function ArtistMembers({ title, memberships, renderArtistLink }) {
2116
2019
  if (memberships.length === 0) return null;
2117
2020
  const isCurrent = (membership) => !membership.ended && membership.endYear === null;
2118
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_ui11.PanelSection, { title, children: [
2119
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
2120
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
2021
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_ui11.PanelSection, { title, children: [
2022
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
2023
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
2121
2024
  MembershipList,
2122
2025
  {
2123
2026
  title: "Past",
@@ -2130,7 +2033,7 @@ function ArtistMembers({ title, memberships, renderArtistLink }) {
2130
2033
 
2131
2034
  // src/genre-tree/artist/ArtistDiscography.tsx
2132
2035
  var import_ui12 = require("@behindthemusictree/ui");
2133
- var import_jsx_runtime33 = require("react/jsx-runtime");
2036
+ var import_jsx_runtime32 = require("react/jsx-runtime");
2134
2037
  var GROUPS = ["Albums", "EPs", "Singles", "Live", "Compilations", "Other"];
2135
2038
  var PRIMARY_GROUPS = { album: "Albums", ep: "EPs", single: "Singles" };
2136
2039
  function discographyGroup({ primaryType, secondaryTypes }) {
@@ -2150,20 +2053,20 @@ function byYear(a, b) {
2150
2053
  function ArtistDiscography({ albums }) {
2151
2054
  if (albums.length === 0) return null;
2152
2055
  const sorted = [...albums].sort(byYear);
2153
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ui12.PanelSection, { title: "Discography", children: GROUPS.map((group) => {
2056
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui12.PanelSection, { title: "Discography", children: GROUPS.map((group) => {
2154
2057
  const groupAlbums = sorted.filter((album) => discographyGroup(album) === group);
2155
2058
  if (groupAlbums.length === 0) return null;
2156
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ui12.PanelGroup, { title: group, children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
2157
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
2158
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
2059
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui12.PanelGroup, { title: group, children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
2060
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
2061
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
2159
2062
  ] }, album.uuid)) }) }, group);
2160
2063
  }) });
2161
2064
  }
2162
2065
 
2163
2066
  // src/genre-tree/artist/ArtistSongs.tsx
2164
- var import_react15 = require("react");
2067
+ var import_react14 = require("react");
2165
2068
  var import_react_query2 = require("@tanstack/react-query");
2166
- var import_lucide_react14 = require("lucide-react");
2069
+ var import_lucide_react13 = require("lucide-react");
2167
2070
  var import_ui13 = require("@behindthemusictree/ui");
2168
2071
  var import_player10 = require("@behindthemusictree/app-kit/player");
2169
2072
  var import_genre_tree14 = require("@behindthemusictree/app-kit/genre-tree");
@@ -2177,7 +2080,7 @@ var import_genre_tree11 = require("@behindthemusictree/app-kit/genre-tree");
2177
2080
  var import_fa5 = require("react-icons/fa");
2178
2081
  var import_player7 = require("@behindthemusictree/app-kit/player");
2179
2082
  var import_player8 = require("@behindthemusictree/app-kit/player");
2180
- var import_jsx_runtime34 = require("react/jsx-runtime");
2083
+ var import_jsx_runtime33 = require("react/jsx-runtime");
2181
2084
  function TrackPositionPlayPause({
2182
2085
  position,
2183
2086
  uuid,
@@ -2191,14 +2094,14 @@ function TrackPositionPlayPause({
2191
2094
  const isPlaying = isThisTrack && isConfirmedPlayable && playState === import_player8.PlayStates.PLAYING;
2192
2095
  const isPaused = isThisTrack && isConfirmedPlayable && playState === import_player8.PlayStates.PAUSED;
2193
2096
  const isCurrentTrackActive = isPlaying || isPaused;
2194
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
2097
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
2195
2098
  "div",
2196
2099
  {
2197
2100
  className: "track-position-play-pause relative flex items-center justify-center gap-2 text-lg w-16 cursor-pointer",
2198
2101
  style: { minWidth: "4rem", maxWidth: "4rem" },
2199
2102
  onClick: handlePlayPauseClick,
2200
2103
  children: [
2201
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
2104
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
2202
2105
  "span",
2203
2106
  {
2204
2107
  className: "track-position-play-pause__bars",
@@ -2206,18 +2109,18 @@ function TrackPositionPlayPause({
2206
2109
  role: "img",
2207
2110
  "aria-label": isPlaying ? "Playing" : "Paused",
2208
2111
  children: [
2209
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "track-position-play-pause__bar" }),
2210
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "track-position-play-pause__bar" }),
2211
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "track-position-play-pause__bar" })
2112
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "track-position-play-pause__bar" }),
2113
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "track-position-play-pause__bar" }),
2114
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "track-position-play-pause__bar" })
2212
2115
  ]
2213
2116
  }
2214
2117
  ) : position }),
2215
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2118
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
2216
2119
  "div",
2217
2120
  {
2218
2121
  className: `hidden group-hover:flex items-center justify-center ${isCurrentTrackActive ? "" : "absolute inset-0"}`,
2219
2122
  "aria-hidden": "true",
2220
- children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_fa5.FaPause, {}) : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_fa5.FaPlay, {})
2123
+ children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_fa5.FaPause, {}) : /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_fa5.FaPlay, {})
2221
2124
  }
2222
2125
  )
2223
2126
  ]
@@ -2228,7 +2131,7 @@ function TrackPositionPlayPause({
2228
2131
  // src/genre-tree/track-list-sidebar/TrackItem.tsx
2229
2132
  var import_genre_tree12 = require("@behindthemusictree/app-kit/genre-tree");
2230
2133
  var import_genre_tree13 = require("@behindthemusictree/app-kit/genre-tree");
2231
- var import_jsx_runtime35 = require("react/jsx-runtime");
2134
+ var import_jsx_runtime34 = require("react/jsx-runtime");
2232
2135
  function TrackItem({
2233
2136
  track,
2234
2137
  position,
@@ -2252,12 +2155,12 @@ function TrackItem({
2252
2155
  loadTrackForPlayer(track.uuid);
2253
2156
  }
2254
2157
  };
2255
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
2158
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
2256
2159
  "div",
2257
2160
  {
2258
2161
  className: `track-item flex h-14 text-[var(--color-text)] ${isUnplayable ? "opacity-50" : "hover:bg-[var(--color-surface-muted)] group"}`,
2259
2162
  children: [
2260
- isUnplayable ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2163
+ isUnplayable ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2261
2164
  "div",
2262
2165
  {
2263
2166
  className: "flex items-center justify-center text-lg w-16",
@@ -2265,7 +2168,7 @@ function TrackItem({
2265
2168
  "aria-disabled": "true",
2266
2169
  children: position
2267
2170
  }
2268
- ) : /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2171
+ ) : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2269
2172
  TrackPositionPlayPause,
2270
2173
  {
2271
2174
  position,
@@ -2274,12 +2177,12 @@ function TrackItem({
2274
2177
  isSelected: selectedTrack?.uuid === track.uuid
2275
2178
  }
2276
2179
  ),
2277
- /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
2278
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree12.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
2279
- track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_genre_tree13.ArtistNames, { artists: track.artists, renderArtistLink }) }) : ""
2180
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
2181
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree12.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
2182
+ track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_genre_tree13.ArtistNames, { artists: track.artists, renderArtistLink }) }) : ""
2280
2183
  ] }),
2281
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "genre-name-container flex items-center justify-start w-[21.6%] min-w-0 shrink-0", children: track.genre ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
2282
- renderDuration && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2184
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "genre-name-container flex items-center justify-start w-[21.6%] min-w-0 shrink-0", children: track.genre ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
2185
+ renderDuration && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2283
2186
  "div",
2284
2187
  {
2285
2188
  className: "duration flex text-base w-16 items-center justify-center",
@@ -2287,22 +2190,22 @@ function TrackItem({
2287
2190
  children: renderDuration(track)
2288
2191
  }
2289
2192
  ),
2290
- renderActions && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
2193
+ renderActions && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
2291
2194
  ]
2292
2195
  }
2293
2196
  );
2294
2197
  }
2295
2198
 
2296
2199
  // src/genre-tree/artist/ArtistSongs.tsx
2297
- var import_jsx_runtime36 = require("react/jsx-runtime");
2200
+ var import_jsx_runtime35 = require("react/jsx-runtime");
2298
2201
  var SongPageSchema2 = (0, import_transport8.PaginatedResponseSchema)(import_genre_tree14.SongDetailedSchema);
2299
2202
  var SORTS = [
2300
2203
  { ordering: "title", label: "Title" },
2301
2204
  { ordering: "-albumYear", label: "Year" }
2302
2205
  ];
2303
2206
  function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLink }) {
2304
- const [page, setPage] = (0, import_react15.useState)(1);
2305
- const [ordering, setOrdering] = (0, import_react15.useState)("title");
2207
+ const [page, setPage] = (0, import_react14.useState)(1);
2208
+ const [ordering, setOrdering] = (0, import_react14.useState)("title");
2306
2209
  const { fetch } = (0, import_transport8.useFetchWrapper)(getBackendBaseUrl);
2307
2210
  const { playNewTrackListFromArtist } = (0, import_genre_tree14.useTrackList)();
2308
2211
  const { playerTrackObject } = (0, import_player10.usePlayer)();
@@ -2327,21 +2230,21 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
2327
2230
  event.stopPropagation();
2328
2231
  void play(song.uuid);
2329
2232
  };
2330
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.PanelSection, { title: "Songs", children: isPending ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.Skeleton, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_jsx_runtime36.Fragment, { children: [
2331
- /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
2332
- /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
2233
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui13.PanelSection, { title: "Songs", children: isPending ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui13.Skeleton, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
2234
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
2235
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
2333
2236
  "button",
2334
2237
  {
2335
2238
  type: "button",
2336
2239
  onClick: () => void play(),
2337
2240
  className: "flex w-fit items-center gap-1.5 px-3 py-1 text-xs font-medium bg-gray-900 text-white rounded-full hover:bg-gray-700",
2338
2241
  children: [
2339
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_lucide_react14.Play, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
2242
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react13.Play, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
2340
2243
  "Play all"
2341
2244
  ]
2342
2245
  }
2343
2246
  ),
2344
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2247
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2345
2248
  "button",
2346
2249
  {
2347
2250
  type: "button",
@@ -2356,7 +2259,7 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
2356
2259
  sort.ordering
2357
2260
  )) })
2358
2261
  ] }),
2359
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2262
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2360
2263
  TrackItem,
2361
2264
  {
2362
2265
  track: song,
@@ -2365,12 +2268,12 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
2365
2268
  renderSongLink
2366
2269
  }
2367
2270
  ) }, song.uuid)) }),
2368
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
2271
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui13.Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
2369
2272
  ] }) });
2370
2273
  }
2371
2274
 
2372
2275
  // src/genre-tree/artist/useArtistPanel.tsx
2373
- var import_jsx_runtime37 = require("react/jsx-runtime");
2276
+ var import_jsx_runtime36 = require("react/jsx-runtime");
2374
2277
  function useArtistPanel({
2375
2278
  uuid,
2376
2279
  scope,
@@ -2391,26 +2294,26 @@ function useArtistPanel({
2391
2294
  return {
2392
2295
  ...panel,
2393
2296
  title: "Artist",
2394
- content: /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2395
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui14.Skeleton, { className: "h-3 w-16" }),
2396
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui14.Skeleton, { className: "h-4 w-32" }),
2397
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui14.Skeleton, { className: "h-3 w-16" }),
2398
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui14.Skeleton, { className: "h-4 w-24" }),
2399
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui14.Skeleton, { className: "h-16 mt-2.5" }),
2400
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui14.Skeleton, { className: "h-16" })
2297
+ content: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2298
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui14.Skeleton, { className: "h-3 w-16" }),
2299
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui14.Skeleton, { className: "h-4 w-32" }),
2300
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui14.Skeleton, { className: "h-3 w-16" }),
2301
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui14.Skeleton, { className: "h-4 w-24" }),
2302
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui14.Skeleton, { className: "h-16 mt-2.5" }),
2303
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui14.Skeleton, { className: "h-16" })
2401
2304
  ] })
2402
2305
  };
2403
2306
  case "not-found":
2404
2307
  return {
2405
2308
  ...panel,
2406
2309
  title: "Artist not found",
2407
- content: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This artist does not exist." })
2310
+ content: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This artist does not exist." })
2408
2311
  };
2409
2312
  case "error":
2410
2313
  return {
2411
2314
  ...panel,
2412
2315
  title: "Artist",
2413
- content: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
2316
+ content: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
2414
2317
  };
2415
2318
  case "success": {
2416
2319
  const { artist } = result;
@@ -2419,13 +2322,13 @@ function useArtistPanel({
2419
2322
  return {
2420
2323
  ...panel,
2421
2324
  title: artist.name,
2422
- content: /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(import_jsx_runtime37.Fragment, { children: [
2423
- shareUrl && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: shareTarget && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl }) }) }),
2424
- artist.image && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ArtistPhoto, { image: artist.image, name: artist.name }),
2425
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ArtistHeader, { artist }),
2426
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
2427
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
2428
- artist.tracksCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
2325
+ content: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_jsx_runtime36.Fragment, { children: [
2326
+ shareUrl && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: shareTarget && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl }) }) }),
2327
+ artist.image && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ArtistPhoto, { image: artist.image, name: artist.name }),
2328
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ArtistHeader, { artist }),
2329
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
2330
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
2331
+ artist.tracksCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2429
2332
  ArtistSongs,
2430
2333
  {
2431
2334
  artist: { uuid: artist.uuid, name: artist.name },
@@ -2435,7 +2338,7 @@ function useArtistPanel({
2435
2338
  }
2436
2339
  )
2437
2340
  ] }),
2438
- extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ArtistDiscography, { albums: artist.albums }) : void 0
2341
+ extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ArtistDiscography, { albums: artist.albums }) : void 0
2439
2342
  };
2440
2343
  }
2441
2344
  }
@@ -2446,45 +2349,45 @@ var import_ui16 = require("@behindthemusictree/ui");
2446
2349
  var import_genre_tree17 = require("@behindthemusictree/app-kit/genre-tree");
2447
2350
 
2448
2351
  // src/genre-tree/song/SongPanelContent.tsx
2449
- var import_react16 = require("react");
2450
- var import_lucide_react15 = require("lucide-react");
2352
+ var import_react15 = require("react");
2353
+ var import_lucide_react14 = require("lucide-react");
2451
2354
  var import_ui15 = require("@behindthemusictree/ui");
2452
2355
  var import_player11 = require("@behindthemusictree/app-kit/player");
2453
2356
  var import_genre_tree16 = require("@behindthemusictree/app-kit/genre-tree");
2454
- var import_jsx_runtime38 = require("react/jsx-runtime");
2357
+ var import_jsx_runtime37 = require("react/jsx-runtime");
2455
2358
  var numberFormat = new Intl.NumberFormat("en");
2456
2359
  var dateFormat = new Intl.DateTimeFormat("en", { dateStyle: "medium" });
2457
2360
  var externalLink = "text-[var(--color-primary)] hover:underline focus-visible:underline";
2458
2361
  function SongHistory({ uuid, getBackendBaseUrl }) {
2459
- const [open, setOpen] = (0, import_react16.useState)(false);
2362
+ const [open, setOpen] = (0, import_react15.useState)(false);
2460
2363
  const history = (0, import_genre_tree16.useSongHistory)(uuid, getBackendBaseUrl, open);
2461
2364
  let body = null;
2462
- if (history.status === "loading") body = /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_ui15.Skeleton, { className: "h-10", "aria-busy": "true" });
2463
- else if (history.status === "error") body = /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("p", { className: "text-red-600", children: "Could not load the history." });
2365
+ if (history.status === "loading") body = /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui15.Skeleton, { className: "h-10", "aria-busy": "true" });
2366
+ else if (history.status === "error") body = /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "text-red-600", children: "Could not load the history." });
2464
2367
  else if (history.status === "success")
2465
- body = history.entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("li", { className: "py-1.5 text-sm", children: [
2466
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "font-medium", children: entry.action }),
2467
- entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
2368
+ body = history.entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("li", { className: "py-1.5 text-sm", children: [
2369
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "font-medium", children: entry.action }),
2370
+ entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
2468
2371
  " ",
2469
2372
  "\u2014 ",
2470
2373
  entry.oldValue ?? "none",
2471
2374
  " \u2192 ",
2472
2375
  entry.newValue ?? "none"
2473
2376
  ] }) : null,
2474
- /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("span", { className: "block text-xs text-[var(--color-text-muted)]", children: [
2377
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { className: "block text-xs text-[var(--color-text-muted)]", children: [
2475
2378
  entry.actorPseudo ?? "Anonymous",
2476
2379
  " \xB7 ",
2477
2380
  dateFormat.format(new Date(entry.createdOn))
2478
2381
  ] })
2479
2382
  ] }, entry.uuid)) });
2480
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
2383
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
2481
2384
  "details",
2482
2385
  {
2483
2386
  onToggle: (event) => setOpen(event.currentTarget.open),
2484
2387
  className: "px-6 pb-2.5 mt-2.5 border-t border-[var(--color-border)]",
2485
2388
  children: [
2486
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("summary", { className: "mt-2.5 cursor-pointer text-[0.6875rem] font-semibold uppercase text-[var(--color-text-muted)]", children: "History" }),
2487
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "mt-1.5", children: body })
2389
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("summary", { className: "mt-2.5 cursor-pointer text-[0.6875rem] font-semibold uppercase text-[var(--color-text-muted)]", children: "History" }),
2390
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "mt-1.5", children: body })
2488
2391
  ]
2489
2392
  }
2490
2393
  );
@@ -2510,13 +2413,13 @@ function SongPanelContent({
2510
2413
  const path = genrePath.length > 0 ? genrePath : null;
2511
2414
  fields.push({
2512
2415
  label: "Genre",
2513
- value: path ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("span", { children: [
2514
- index > 0 && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { "aria-hidden": "true", children: "\u203A " }),
2515
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("button", { type: "button", onClick: () => onSelectGenre(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
2416
+ value: path ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { children: [
2417
+ index > 0 && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { "aria-hidden": "true", children: "\u203A " }),
2418
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("button", { type: "button", onClick: () => onSelectGenre(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
2516
2419
  ] }, genrePlaylist.uuid)) }) : song.genre.name
2517
2420
  });
2518
2421
  } else {
2519
- fields.push({ label: "Genre", value: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
2422
+ fields.push({ label: "Genre", value: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
2520
2423
  }
2521
2424
  if (song.firstReleaseYear) fields.push({ label: "First released", value: song.firstReleaseYear });
2522
2425
  const viewCount = song.youtubeVideo?.viewCount;
@@ -2526,11 +2429,11 @@ function SongPanelContent({
2526
2429
  const links = [];
2527
2430
  if (musicBrainzUrl)
2528
2431
  links.push(
2529
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
2432
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
2530
2433
  );
2531
2434
  if (song.youtubeVideo)
2532
2435
  links.push(
2533
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2436
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
2534
2437
  "a",
2535
2438
  {
2536
2439
  href: `https://www.youtube.com/watch?v=${encodeURIComponent(song.youtubeVideo.id)}`,
@@ -2545,33 +2448,33 @@ function SongPanelContent({
2545
2448
  if (links.length > 0)
2546
2449
  fields.push({
2547
2450
  label: "Links",
2548
- value: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "flex gap-3", children: links })
2451
+ value: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "flex gap-3", children: links })
2549
2452
  });
2550
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_jsx_runtime38.Fragment, { children: [
2551
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
2552
- unplayableReason ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("p", { className: "flex items-center px-2.5 text-sm text-[var(--color-text-muted)]", children: unplayableReason }) : /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
2453
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(import_jsx_runtime37.Fragment, { children: [
2454
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
2455
+ unplayableReason ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "flex items-center px-2.5 text-sm text-[var(--color-text-muted)]", children: unplayableReason }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
2553
2456
  "button",
2554
2457
  {
2555
2458
  type: "button",
2556
2459
  onClick: () => isCurrent ? handlePlayPauseAction() : playNewTrackListFromTrackUuid(song, scope),
2557
2460
  className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none",
2558
2461
  children: [
2559
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "flex items-center justify-center w-10 h-10 rounded-full group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)] bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]", children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_lucide_react15.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_lucide_react15.Play, { "aria-hidden": "true", className: "w-5 h-5" }) }),
2462
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "flex items-center justify-center w-10 h-10 rounded-full group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)] bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]", children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react14.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react14.Play, { "aria-hidden": "true", className: "w-5 h-5" }) }),
2560
2463
  isPlaying ? "Pause" : "Play"
2561
2464
  ]
2562
2465
  }
2563
2466
  ),
2564
- shareUrl && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
2467
+ shareUrl && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
2565
2468
  ] }) }),
2566
- song.artists && song.artists.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("p", { className: "px-6 pt-2.5 text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_genre_tree16.ArtistNames, { artists: song.artists, renderArtistLink }) }),
2567
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_ui15.FieldList, { items: fields }),
2568
- song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_ui15.PanelSection, { title: "Essential in", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
2569
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
2469
+ song.artists && song.artists.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "px-6 pt-2.5 text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_genre_tree16.ArtistNames, { artists: song.artists, renderArtistLink }) }),
2470
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui15.FieldList, { items: fields }),
2471
+ song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui15.PanelSection, { title: "Essential in", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
2472
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
2570
2473
  ] });
2571
2474
  }
2572
2475
 
2573
2476
  // src/genre-tree/song/useSongPanel.tsx
2574
- var import_jsx_runtime39 = require("react/jsx-runtime");
2477
+ var import_jsx_runtime38 = require("react/jsx-runtime");
2575
2478
  function useSongPanel({
2576
2479
  uuid,
2577
2480
  scope,
@@ -2593,24 +2496,24 @@ function useSongPanel({
2593
2496
  return {
2594
2497
  ...panel,
2595
2498
  title: "Song",
2596
- content: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2597
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_ui16.Skeleton, { className: "h-3 w-16" }),
2598
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_ui16.Skeleton, { className: "h-4 w-32" }),
2599
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_ui16.Skeleton, { className: "h-3 w-16" }),
2600
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_ui16.Skeleton, { className: "h-4 w-24" })
2499
+ content: /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2500
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_ui16.Skeleton, { className: "h-3 w-16" }),
2501
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_ui16.Skeleton, { className: "h-4 w-32" }),
2502
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_ui16.Skeleton, { className: "h-3 w-16" }),
2503
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_ui16.Skeleton, { className: "h-4 w-24" })
2601
2504
  ] })
2602
2505
  };
2603
2506
  case "not-found":
2604
2507
  return {
2605
2508
  ...panel,
2606
2509
  title: "Song not found",
2607
- content: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This song does not exist." })
2510
+ content: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This song does not exist." })
2608
2511
  };
2609
2512
  case "error":
2610
2513
  return {
2611
2514
  ...panel,
2612
2515
  title: "Song",
2613
- content: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this song." })
2516
+ content: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this song." })
2614
2517
  };
2615
2518
  case "success": {
2616
2519
  const { song } = result;
@@ -2618,7 +2521,7 @@ function useSongPanel({
2618
2521
  return {
2619
2522
  ...panel,
2620
2523
  title: song.title,
2621
- content: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2524
+ content: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2622
2525
  SongPanelContent,
2623
2526
  {
2624
2527
  song,
@@ -2638,12 +2541,12 @@ function useSongPanel({
2638
2541
  // src/genre-tree/SizeMetricToggle.tsx
2639
2542
  var import_md2 = require("react-icons/md");
2640
2543
  var import_ui17 = require("@behindthemusictree/ui");
2641
- var import_jsx_runtime40 = require("react/jsx-runtime");
2544
+ var import_jsx_runtime39 = require("react/jsx-runtime");
2642
2545
  function SizeMetricToggle({
2643
2546
  value,
2644
2547
  onChange
2645
2548
  }) {
2646
- return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
2549
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2647
2550
  import_ui17.SegmentedControl,
2648
2551
  {
2649
2552
  variant: "floating",
@@ -2651,26 +2554,19 @@ function SizeMetricToggle({
2651
2554
  value,
2652
2555
  onChange,
2653
2556
  options: [
2654
- { value: "songs", label: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_md2.MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
2655
- { value: "views", label: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_md2.MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
2557
+ { value: "songs", label: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_md2.MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
2558
+ { value: "views", label: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_md2.MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
2656
2559
  ]
2657
2560
  }
2658
2561
  );
2659
2562
  }
2660
2563
 
2661
2564
  // src/genre-tree/GenreTreeView.tsx
2662
- var import_jsx_runtime41 = require("react/jsx-runtime");
2663
- var PREVIOUS_BUTTON_CSS = `
2664
- .genre-tree-view .search-overlay .gtv-info-panel-previous { display: none; }
2665
- .genre-tree-view:has(.search-overlay .gtv-info-panel-previous) .genre-search input { padding-left: 2.75rem; }
2666
- .genre-tree-view:has(.search-overlay .gtv-info-panel-previous) .genre-search-previous { display: flex; }
2667
- @keyframes genre-tree-view-previous-in { from { opacity: 0; transform: translate(-0.5rem, -50%); } to { opacity: 1; transform: translate(0, -50%); } }
2668
- @media (prefers-reduced-motion: no-preference) {
2669
- .genre-search-previous { animation: genre-tree-view-previous-in 200ms ease-out; }
2670
- }
2671
- `;
2565
+ var import_jsx_runtime40 = require("react/jsx-runtime");
2672
2566
  var NO_POP_ROOT_TITLE = "This genre tree has no 'Mainstream Pop' root yet";
2673
- var REGIONS_CLASS = "[--gtv-center-left:var(--layout-center-left)] [--gtv-center-right:var(--layout-center-right)] [--gtv-center-top:var(--layout-band-top)] [&_.gtv-info-panel--overlay]:[--gtv-info-panel-header-inset:0px]";
2567
+ var REGIONS_CLASS = "[--gtv-center-left:var(--layout-center-left)] [--gtv-center-right:var(--layout-center-right)] [--gtv-center-top:var(--layout-band-top)]";
2568
+ var noop = () => {
2569
+ };
2674
2570
  function GenreTreeView({
2675
2571
  scope,
2676
2572
  handleGenreCreationAction,
@@ -2724,21 +2620,21 @@ function GenreTreeView({
2724
2620
  placement = "default",
2725
2621
  ...focus
2726
2622
  }) {
2727
- const getShareUrlRef = (0, import_react17.useRef)(getShareUrl);
2623
+ const getShareUrlRef = (0, import_react16.useRef)(getShareUrl);
2728
2624
  getShareUrlRef.current = getShareUrl;
2729
- const [internalArtistUuid, setInternalArtistUuid] = (0, import_react17.useState)(null);
2625
+ const [internalArtistUuid, setInternalArtistUuid] = (0, import_react16.useState)(null);
2730
2626
  const isArtistControlled = controlledArtistUuid !== void 0;
2731
2627
  const artistUuid = isArtistControlled ? controlledArtistUuid : internalArtistUuid;
2732
- const [internalSongUuid, setInternalSongUuid] = (0, import_react17.useState)(null);
2628
+ const [internalSongUuid, setInternalSongUuid] = (0, import_react16.useState)(null);
2733
2629
  const isSongControlled = controlledSongUuid !== void 0;
2734
2630
  const songUuid = isSongControlled ? controlledSongUuid : internalSongUuid;
2735
2631
  const changeSong = (uuid) => {
2736
2632
  if (!isSongControlled) setInternalSongUuid(uuid);
2737
2633
  onSongUuidChange?.(uuid);
2738
2634
  };
2739
- const [internalSizeMetric, setInternalSizeMetric] = (0, import_react17.useState)("songs");
2635
+ const [internalSizeMetric, setInternalSizeMetric] = (0, import_react16.useState)("songs");
2740
2636
  const sizeMetric = controlledSizeMetric ?? internalSizeMetric;
2741
- const deferredSizeMetric = (0, import_react17.useDeferredValue)(sizeMetric);
2637
+ const deferredSizeMetric = (0, import_react16.useDeferredValue)(sizeMetric);
2742
2638
  const changeSizeMetric = (metric) => {
2743
2639
  if (controlledSizeMetric === void 0) setInternalSizeMetric(metric);
2744
2640
  onSizeMetricChange?.(metric);
@@ -2775,7 +2671,7 @@ function GenreTreeView({
2775
2671
  artistUuid
2776
2672
  });
2777
2673
  const restoreRevision = viewportRestore?.revision;
2778
- const [stackedRestoreOwner, setStackedRestoreOwner] = (0, import_react17.useState)({ revision: restoreRevision, rootUuid: selectedGenrePlaylist?.root.uuid });
2674
+ const [stackedRestoreOwner, setStackedRestoreOwner] = (0, import_react16.useState)({ revision: restoreRevision, rootUuid: selectedGenrePlaylist?.root.uuid });
2779
2675
  let restoreRootUuid = stackedRestoreOwner.rootUuid;
2780
2676
  if (stackedRestoreOwner.revision !== restoreRevision) {
2781
2677
  restoreRootUuid = selectedGenrePlaylist?.root.uuid;
@@ -2791,7 +2687,7 @@ function GenreTreeView({
2791
2687
  const canSizeByViews = !isArtistFiltered && hasViewsCount(visibleGenrePlaylists);
2792
2688
  const artistResult = (0, import_genre_tree19.useArtist)(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
2793
2689
  const filterArtistName = artistResult.status === "success" ? artistResult.artist.name : null;
2794
- const filterArtist = (0, import_react17.useMemo)(
2690
+ const filterArtist = (0, import_react16.useMemo)(
2795
2691
  () => artistUuid !== null && filterArtistName !== null ? { uuid: artistUuid, name: filterArtistName } : null,
2796
2692
  [artistUuid, filterArtistName]
2797
2693
  );
@@ -2806,14 +2702,14 @@ function GenreTreeView({
2806
2702
  if (selectedNodeId !== null) clearSelection();
2807
2703
  changeSong(uuid);
2808
2704
  };
2809
- const openSongRef = (0, import_react17.useRef)(openSong);
2705
+ const openSongRef = (0, import_react16.useRef)(openSong);
2810
2706
  openSongRef.current = openSong;
2811
- const resolvedRenderSongLink = (0, import_react17.useCallback)(
2812
- (song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
2707
+ const resolvedRenderSongLink = (0, import_react16.useCallback)(
2708
+ (song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
2813
2709
  [renderSongLink]
2814
2710
  );
2815
- const [expandedSongUuid, setExpandedSongUuid] = (0, import_react17.useState)(null);
2816
- const [expandedArtistUuid, setExpandedArtistUuid] = (0, import_react17.useState)(null);
2711
+ const [expandedSongUuid, setExpandedSongUuid] = (0, import_react16.useState)(null);
2712
+ const [expandedArtistUuid, setExpandedArtistUuid] = (0, import_react16.useState)(null);
2817
2713
  const artistBaseUrl = artistSearchBaseUrl ?? getBackendBaseUrl;
2818
2714
  const songBaseUrl = getBackendBaseUrl;
2819
2715
  const artistPanel = useArtistPanel({
@@ -2821,7 +2717,7 @@ function GenreTreeView({
2821
2717
  scope,
2822
2718
  getBackendBaseUrl: artistBaseUrl,
2823
2719
  getShareUrl: getShareUrlRef.current,
2824
- renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
2720
+ renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
2825
2721
  expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
2826
2722
  onExpandedChange: (on) => {
2827
2723
  if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
@@ -2830,7 +2726,7 @@ function GenreTreeView({
2830
2726
  renderSongLink: resolvedRenderSongLink,
2831
2727
  onClose: () => changeArtist(null)
2832
2728
  });
2833
- const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
2729
+ const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
2834
2730
  const getGenrePath = (genre) => {
2835
2731
  const path = [];
2836
2732
  let current = visibleGenrePlaylists.find((playlist) => playlist.criteria?.uuid === genre.uuid);
@@ -2862,9 +2758,8 @@ function GenreTreeView({
2862
2758
  const builtInSongPanel = overlayPanel || hasSelectedGenreNode ? void 0 : songPanel;
2863
2759
  const builtInArtistPanel = overlayPanel || hasSelectedGenreNode || builtInSongPanel ? void 0 : artistPanel;
2864
2760
  const builtInPanel = builtInSongPanel ?? builtInArtistPanel;
2865
- const activeOverlayPanel = overlayPanel ?? (builtInPanel && { ...builtInPanel, hideTitle: true, hideClose: true });
2866
- const [uncontrolledCollapsed, setUncontrolledCollapsed] = (0, import_react17.useState)(false);
2867
- const [uncontrolledExpanded, setUncontrolledExpanded] = (0, import_react17.useState)(false);
2761
+ const [uncontrolledCollapsed, setUncontrolledCollapsed] = (0, import_react16.useState)(false);
2762
+ const [uncontrolledExpanded, setUncontrolledExpanded] = (0, import_react16.useState)(false);
2868
2763
  const isCollapsed = collapsed ?? uncontrolledCollapsed;
2869
2764
  const isExpanded = expanded ?? uncontrolledExpanded;
2870
2765
  const changeCollapsed = (next) => {
@@ -2875,55 +2770,56 @@ function GenreTreeView({
2875
2770
  setUncontrolledExpanded(next);
2876
2771
  onExpandedChange?.(next);
2877
2772
  };
2878
- (0, import_react17.useEffect)(() => {
2773
+ (0, import_react16.useEffect)(() => {
2879
2774
  setUncontrolledCollapsed(false);
2880
2775
  }, [selectedNodeId, artistUuid, songUuid]);
2881
- const isDetailHidden = isCollapsed || !hasSelectedGenreNode && !activeOverlayPanel;
2882
- useLeftDetailPanelLayout(!isDetailHidden, placement === "regions");
2883
- const detailOverlay = placement === "regions" && activeOverlayPanel?.onExpandedChange ? activeOverlayPanel : void 0;
2884
- const detailExpanded = detailOverlay ? !!detailOverlay.expanded : isExpanded;
2885
- const onDetailExpandedChange = detailOverlay ? detailOverlay.onExpandedChange : changeExpanded;
2886
- const infoPanelCuration = {
2887
- renderDangerZone,
2888
- expanded: placement === "regions" ? isExpanded : expanded,
2889
- onExpandedChange: placement === "regions" ? changeExpanded : onExpandedChange,
2890
- collapsed: placement === "regions" ? isCollapsed : void 0,
2891
- hideExpandButton: placement === "regions" ? true : void 0,
2892
- overlayPanel: detailOverlay ? { ...detailOverlay, collapsed: isCollapsed, hideExpandButton: true } : activeOverlayPanel
2893
- };
2894
- const [isWheelRevealed, setIsWheelRevealed] = (0, import_react17.useState)(false);
2776
+ const [isWheelRevealed, setIsWheelRevealed] = (0, import_react16.useState)(false);
2895
2777
  const usesHandoff = viewMode === "wheel" || viewMode === "pop-core";
2896
2778
  const isTreeReady = !isLoading && (!usesHandoff || isWheelRevealed);
2897
- const rootRef = (0, import_react17.useRef)(null);
2898
- const handlePrevious = () => {
2899
- rootRef.current?.querySelector(".search-overlay .gtv-info-panel-previous")?.click();
2900
- };
2901
- (0, import_react17.useEffect)(() => {
2902
- if (!isTreeReady || viewMode !== "pop-core" || !hasSelectedGenreNode || !selectedGenrePlaylist) return;
2903
- const root = rootRef.current;
2904
- const centerChip = root?.querySelector(".gtv-wheel-center-node .gtv-wheel-chip");
2905
- if (!centerChip || centerChip.getAttribute("aria-label") !== selectedGenrePlaylist.name) return;
2906
- const panelTitle = root?.querySelector(".gtv-info-panel-title");
2907
- if (panelTitle?.textContent?.trim().startsWith(selectedGenrePlaylist.name)) return;
2908
- centerChip.click();
2909
- }, [hasSelectedGenreNode, isTreeReady, selectedGenrePlaylist, viewMode]);
2779
+ const [resolveStyle, setResolveStyle] = (0, import_react16.useState)(null);
2780
+ const handleNodeStyleResolverChange = (0, import_react16.useCallback)(
2781
+ (resolver) => setResolveStyle(() => resolver),
2782
+ []
2783
+ );
2784
+ const panelRef = (0, import_react16.useRef)(null);
2785
+ const obscuredInset = (0, import_ui18.useObscuredInset)(panelRef);
2786
+ const hostPanel = { obscuredInset, onNodeStyleResolverChange: handleNodeStyleResolverChange };
2787
+ const nodeMetric = isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric;
2788
+ const treeNodes = (0, import_react16.useMemo)(() => toTreeNodes(visibleGenrePlaylists, nodeMetric), [visibleGenrePlaylists, nodeMetric]);
2789
+ const relations = (0, import_genre_tree_view4.useGenreNodeRelations)(
2790
+ treeNodes,
2791
+ !overlayPanel && hasSelectedGenreNode ? selectedNodeId : null,
2792
+ resolveStyle
2793
+ );
2794
+ const [selectionHistory, setSelectionHistory] = (0, import_react16.useState)({
2795
+ current: selectedNodeId,
2796
+ previous: null
2797
+ });
2798
+ if (selectionHistory.current !== selectedNodeId) {
2799
+ setSelectionHistory({
2800
+ current: selectedNodeId,
2801
+ previous: selectionHistory.current ?? selectionHistory.previous
2802
+ });
2803
+ }
2804
+ const previousPlaylist = selectionHistory.previous !== null && selectionHistory.previous !== selectedNodeId ? genrePlaylistByNodeId.get(selectionHistory.previous) : void 0;
2805
+ const handlePrevious = previousPlaylist && visibleGenrePlaylists.includes(previousPlaylist) ? () => handleGenreSearchSelect(previousPlaylist) : void 0;
2910
2806
  const { showPopup, hidePopup } = (0, import_popup6.usePopup)();
2911
- const [searchText, setSearchText] = (0, import_react17.useState)("");
2807
+ const [searchText, setSearchText] = (0, import_react16.useState)("");
2912
2808
  const artistSearchResult = (0, import_genre_tree18.useArtistSearch)(artistBaseUrl, artistSearch ? searchText : "");
2913
2809
  const artistGroup = artistSearch ? {
2914
2810
  key: "artists",
2915
2811
  label: "Artists",
2916
2812
  items: artistSearchResult.artists.map((artist) => ({
2917
2813
  key: artist.uuid,
2918
- content: /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(import_jsx_runtime41.Fragment, { children: [
2919
- /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { className: "block truncate font-medium", children: [
2814
+ content: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
2815
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "block truncate font-medium", children: [
2920
2816
  artist.name,
2921
- artist.disambiguation && /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { className: "font-normal text-[var(--color-neutral-500)]", children: [
2817
+ artist.disambiguation && /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "font-normal text-[var(--color-neutral-500)]", children: [
2922
2818
  " \xB7 ",
2923
2819
  artist.disambiguation
2924
2820
  ] })
2925
2821
  ] }),
2926
- artist.matchedAlias && /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [
2822
+ artist.matchedAlias && /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [
2927
2823
  "also known as ",
2928
2824
  artist.matchedAlias
2929
2825
  ] })
@@ -2942,9 +2838,9 @@ function GenreTreeView({
2942
2838
  label: "Songs",
2943
2839
  items: songSearchResult.songs.map((song) => ({
2944
2840
  key: song.uuid,
2945
- content: /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { className: (0, import_genre_tree18.isSongSearchResultPlayable)(song) ? void 0 : "opacity-60", children: [
2946
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "block truncate font-medium", children: song.title }),
2947
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [song.artists.map((artist) => artist.name).join(", "), song.genre?.name ?? "No genre"].filter(Boolean).join(" \xB7 ") })
2841
+ content: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: (0, import_genre_tree18.isSongSearchResultPlayable)(song) ? void 0 : "opacity-60", children: [
2842
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "block truncate font-medium", children: song.title }),
2843
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [song.artists.map((artist) => artist.name).join(", "), song.genre?.name ?? "No genre"].filter(Boolean).join(" \xB7 ") })
2948
2844
  ] }),
2949
2845
  onSelect: () => {
2950
2846
  openSong(song.uuid);
@@ -2970,36 +2866,49 @@ function GenreTreeView({
2970
2866
  const handleGenreSearchZoomIn = () => {
2971
2867
  if (selectedUuid) focus.onFocusGenreChange?.(selectedUuid, "zoom-in");
2972
2868
  };
2973
- const zoomInRef = (0, import_react17.useRef)(void 0);
2869
+ const zoomInRef = (0, import_react16.useRef)(void 0);
2974
2870
  zoomInRef.current = canZoomIn ? handleGenreSearchZoomIn : void 0;
2975
- const renderInfoPanelBody = (0, import_react17.useCallback)(
2976
- (node, { toolbar, ancestors, family }) => {
2977
- const genrePlaylist = genrePlaylistByNodeId.get(node.id);
2978
- const criteria = genrePlaylist?.criteria;
2979
- if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(import_jsx_runtime41.Fragment, { children: [
2980
- toolbar,
2981
- family
2982
- ] }), aside: ancestors };
2983
- const genreUuid = criteria.uuid;
2984
- const parentPlaylist = visibleGenrePlaylists.find(({ uuid }) => uuid === genrePlaylist.parent?.uuid);
2985
- const canToggleSide = !readOnly && parentPlaylist !== void 0 && parentPlaylist.parent === null;
2986
- const side = criteria.side ?? "core";
2987
- const shareTarget = {
2988
- kind: "genre",
2989
- genrePlaylistUuid: genrePlaylist.uuid,
2990
- name: criteria.name,
2991
- scope,
2992
- artistUuid
2993
- };
2994
- const shareUrl = getShareUrlRef.current?.(shareTarget) ?? void 0;
2995
- const popSibling = canToggleSide && side === "core" ? visibleGenrePlaylists.find(
2996
- (sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist.uuid && sibling.criteria?.side === "pop"
2997
- ) : void 0;
2998
- return {
2999
- primary: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
2871
+ const selectGenreFromPanel = (nodeId) => {
2872
+ const genrePlaylist = genrePlaylistByNodeId.get(nodeId);
2873
+ if (genrePlaylist) handleGenreSearchSelect(genrePlaylist);
2874
+ };
2875
+ const renderGenreBody = (relation) => {
2876
+ const { node } = relation;
2877
+ const family = /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
2878
+ import_genre_tree_view4.GenreNodeFamily,
2879
+ {
2880
+ node,
2881
+ fill: relation.fill,
2882
+ textColor: relation.textColor,
2883
+ parentNode: relation.parentNode,
2884
+ childNodes: relation.childNodes,
2885
+ onSelectNode: selectGenreFromPanel
2886
+ }
2887
+ );
2888
+ const ancestors = /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_genre_tree_view4.GenreNodeAncestors, { ancestorNodes: relation.ancestorNodes, onSelectNode: selectGenreFromPanel });
2889
+ const genrePlaylist = genrePlaylistByNodeId.get(node.id);
2890
+ const criteria = genrePlaylist?.criteria;
2891
+ if (!genrePlaylist || !criteria) return { primary: family, aside: ancestors };
2892
+ const genreUuid = criteria.uuid;
2893
+ const parentPlaylist = visibleGenrePlaylists.find(({ uuid }) => uuid === genrePlaylist.parent?.uuid);
2894
+ const canToggleSide = !readOnly && parentPlaylist !== void 0 && parentPlaylist.parent === null;
2895
+ const side = criteria.side ?? "core";
2896
+ const shareTarget = {
2897
+ kind: "genre",
2898
+ genrePlaylistUuid: genrePlaylist.uuid,
2899
+ name: criteria.name,
2900
+ scope,
2901
+ artistUuid
2902
+ };
2903
+ const shareUrl = getShareUrlRef.current?.(shareTarget) ?? void 0;
2904
+ const popSibling = canToggleSide && side === "core" ? visibleGenrePlaylists.find(
2905
+ (sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist.uuid && sibling.criteria?.side === "pop"
2906
+ ) : void 0;
2907
+ return {
2908
+ primary: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
2909
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3000
2910
  GenreInfoPanelBody,
3001
2911
  {
3002
- toolbar,
3003
2912
  family,
3004
2913
  genreUuid,
3005
2914
  genrePlaylist,
@@ -3015,7 +2924,7 @@ function GenreTreeView({
3015
2924
  onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
3016
2925
  onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
3017
2926
  onChangeParent: readOnly ? void 0 : () => showPopup(
3018
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
2927
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3019
2928
  ChangeParentPopup,
3020
2929
  {
3021
2930
  genrePlaylist: { ...genrePlaylist, criteria },
@@ -3046,49 +2955,65 @@ function GenreTreeView({
3046
2955
  }
3047
2956
  }
3048
2957
  ),
3049
- aside: ancestors
3050
- };
3051
- },
3052
- [
3053
- genrePlaylistByNodeId,
3054
- additionalActions,
3055
- readOnly,
3056
- handleGenreCreationAction,
3057
- handleGenreRenameAction,
3058
- showPopup,
3059
- hidePopup,
3060
- onParentChanged,
3061
- visibleGenrePlaylists,
3062
- updateGenre,
3063
- playback,
3064
- filterArtist,
3065
- scope,
3066
- getBackendBaseUrl,
3067
- criteriaOverviewSchema,
3068
- renderGenreDetailExtras,
3069
- renderArtistLink,
3070
- resolvedRenderSongLink,
3071
- renderSummary,
3072
- renderEssentialTracks,
3073
- renderSongLeading,
3074
- renderSongActions,
3075
- renderTracksToolbar
3076
- ]
3077
- );
2958
+ renderDangerZone?.(node)
2959
+ ] }),
2960
+ aside: ancestors
2961
+ };
2962
+ };
3078
2963
  const isRotationControlled = controlledAllowWheelRotation !== void 0;
3079
2964
  const allowWheelRotation = controlledAllowWheelRotation ?? internalAllowWheelRotation;
3080
- const detailTab = placement === "regions" && (hasSelectedGenreNode || detailOverlay) ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3081
- PanelEdgeTab,
2965
+ let activePanel = null;
2966
+ if (overlayPanel) {
2967
+ activePanel = {
2968
+ title: overlayPanel.title,
2969
+ content: overlayPanel.content,
2970
+ aside: overlayPanel.extraContent,
2971
+ expanded: overlayPanel.expanded ?? false,
2972
+ onExpandedChange: overlayPanel.onExpandedChange,
2973
+ onClose: overlayPanel.onClose,
2974
+ compact: false
2975
+ };
2976
+ } else if (relations) {
2977
+ const { primary, aside } = renderGenreBody(relations);
2978
+ activePanel = {
2979
+ title: relations.node.name,
2980
+ content: primary,
2981
+ aside,
2982
+ expanded: isExpanded,
2983
+ onExpandedChange: changeExpanded,
2984
+ onClose: clearSelection,
2985
+ compact: true,
2986
+ headerAccent: { background: relations.fill, color: relations.textColor }
2987
+ };
2988
+ } else if (builtInPanel) {
2989
+ activePanel = {
2990
+ title: builtInPanel.title,
2991
+ content: builtInPanel.content,
2992
+ aside: builtInPanel.extraContent,
2993
+ expanded: builtInPanel.expanded,
2994
+ onExpandedChange: builtInPanel.onExpandedChange,
2995
+ onClose: builtInPanel.onClose,
2996
+ compact: true
2997
+ };
2998
+ }
2999
+ const isPanelShown = isTreeReady && activePanel !== null;
3000
+ const detailTab = placement === "regions" && activePanel?.onExpandedChange ? {
3001
+ edge: activePanel.expanded ? { label: "Shrink panel", onClick: () => activePanel.onExpandedChange?.(false) } : isCollapsed ? null : { label: "Hide panel", onClick: () => changeCollapsed(true) },
3002
+ center: activePanel.expanded ? null : isCollapsed ? { label: "Show panel", onClick: () => changeCollapsed(false) } : activePanel.onExpandedChange ? { label: "Extend panel", onClick: () => activePanel.onExpandedChange?.(true) } : null
3003
+ } : void 0;
3004
+ const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3005
+ import_ui18.IconButton,
3082
3006
  {
3083
- side: "left",
3084
- edge: detailExpanded ? { label: "Shrink panel", onClick: () => onDetailExpandedChange?.(false) } : isCollapsed ? null : { label: "Hide panel", onClick: () => changeCollapsed(true) },
3085
- center: detailExpanded ? null : isCollapsed ? { label: "Show panel", onClick: () => changeCollapsed(false) } : onDetailExpandedChange ? { label: "Extend panel", onClick: () => onDetailExpandedChange(true) } : null
3007
+ "aria-label": activePanel.expanded ? "Shrink panel" : "Extend panel",
3008
+ style: activePanel.headerAccent && { color: "inherit" },
3009
+ onClick: () => activePanel.onExpandedChange?.(!activePanel.expanded),
3010
+ children: activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react16.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react16.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
3086
3011
  }
3087
3012
  ) : null;
3088
3013
  const renderArtistFilter = () => {
3089
3014
  if (isLoading || !filterArtist) return null;
3090
- return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
3091
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3015
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
3016
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3092
3017
  "button",
3093
3018
  {
3094
3019
  type: "button",
@@ -3100,7 +3025,7 @@ function GenreTreeView({
3100
3025
  children: filterArtist.name
3101
3026
  }
3102
3027
  ),
3103
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3028
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3104
3029
  "button",
3105
3030
  {
3106
3031
  type: "button",
@@ -3112,22 +3037,22 @@ function GenreTreeView({
3112
3037
  )
3113
3038
  ] });
3114
3039
  };
3115
- const actions = /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(import_jsx_runtime41.Fragment, { children: [
3116
- !isLoading && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3040
+ const actions = /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
3041
+ !isLoading && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3117
3042
  GenreSearch,
3118
3043
  {
3119
3044
  genrePlaylists: visibleGenrePlaylists,
3120
3045
  onSelect: handleGenreSearchSelect,
3121
- selectedName: selectedGenrePlaylist?.name ?? builtInPanel?.title ?? null,
3046
+ selectedName: selectedGenrePlaylist?.name ?? (typeof builtInPanel?.title === "string" ? builtInPanel.title : null),
3122
3047
  onClear: builtInSongPanel ? () => changeSong(null) : builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
3123
3048
  onPrevious: handlePrevious,
3124
3049
  onQueryChange: artistSearch || songSearch ? setSearchText : void 0,
3125
3050
  extraGroups
3126
3051
  }
3127
3052
  ),
3128
- !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
3129
- /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "contents max-md:flex max-md:w-full max-md:items-center max-md:gap-2 max-md:overflow-x-auto max-md:[scrollbar-width:none] max-md:[&>*]:shrink-0", children: [
3130
- !isLoading && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3053
+ !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
3054
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "contents max-md:flex max-md:w-full max-md:items-center max-md:gap-2 max-md:overflow-x-auto max-md:[scrollbar-width:none] max-md:[&>*]:shrink-0", children: [
3055
+ !isLoading && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3131
3056
  import_ui18.SegmentedControl,
3132
3057
  {
3133
3058
  "aria-label": "Tree view mode",
@@ -3141,13 +3066,13 @@ function GenreTreeView({
3141
3066
  ]
3142
3067
  }
3143
3068
  ),
3144
- !isLoading && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3069
+ !isLoading && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3145
3070
  "div",
3146
3071
  {
3147
3072
  className: "flex items-center gap-1",
3148
3073
  role: "group",
3149
3074
  "aria-label": "Tree display options",
3150
- children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3075
+ children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3151
3076
  import_ui18.Button,
3152
3077
  {
3153
3078
  variant: allowWheelRotation ? "default" : "outline",
@@ -3158,52 +3083,44 @@ function GenreTreeView({
3158
3083
  )
3159
3084
  }
3160
3085
  ),
3161
- !isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3086
+ !isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3162
3087
  import_ui18.IconTextButton,
3163
3088
  {
3164
- icon: import_lucide_react16.Plus,
3089
+ icon: import_lucide_react15.Plus,
3165
3090
  text: "Add root",
3166
3091
  onClick: () => handleGenreCreationAction(null)
3167
3092
  }
3168
3093
  )
3169
3094
  ] })
3170
3095
  ] });
3171
- return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
3096
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
3172
3097
  "div",
3173
3098
  {
3174
- ref: rootRef,
3099
+ "data-left-panel": isPanelShown && !isCollapsed ? void 0 : "hidden",
3175
3100
  className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
3176
3101
  children: [
3177
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("style", { children: PREVIOUS_BUTTON_CSS }),
3178
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3102
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3179
3103
  "div",
3180
3104
  {
3181
3105
  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))] max-md:right-[calc(var(--layout-gutter)+var(--layout-panel-inset))] max-md:top-[var(--layout-band-top)] max-md:flex-col max-md:items-stretch" : "left-5 top-5"}`,
3182
3106
  children: actions
3183
3107
  }
3184
3108
  ),
3185
- !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3109
+ !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3186
3110
  "div",
3187
3111
  {
3188
3112
  className: `absolute left-1/2 z-30 hidden -translate-x-1/2 md:block ${placement === "regions" ? "top-[calc(var(--layout-gutter)+var(--layout-panel-inset))]" : "top-5"}`,
3189
3113
  children: renderArtistFilter()
3190
3114
  }
3191
3115
  ),
3192
- detailTab && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3193
- "div",
3194
- {
3195
- className: `absolute top-1/2 z-30 -translate-y-1/2 max-md:hidden 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))]"}`,
3196
- children: detailTab
3197
- }
3198
- ),
3199
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
3200
- !isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
3201
- isLoading ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_genre_tree_view4.GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
3116
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
3117
+ !isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
3118
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_genre_tree_view4.GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
3202
3119
  // The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
3203
3120
  // and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
3204
3121
  // Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
3205
3122
  // The panel's own Zoom in button is hidden: the search bar carries it.
3206
- /* @__PURE__ */ (0, import_jsx_runtime41.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_runtime41.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3123
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3207
3124
  GenrePlaylistTreeWheel,
3208
3125
  {
3209
3126
  scope,
@@ -3212,29 +3129,26 @@ function GenreTreeView({
3212
3129
  handleGenreRenameAction,
3213
3130
  additionalActions,
3214
3131
  onSelectedNodeChange: handleSelectedNodeChange,
3215
- hideInfoPanelClose: true,
3216
- hideInfoPanelTitle: true,
3217
3132
  onNodeHover: handleNodeHover,
3218
- renderInfoPanelBody,
3133
+ ...hostPanel,
3219
3134
  playback,
3220
3135
  selectedNodeId,
3221
3136
  readOnly,
3222
3137
  allowWheelRotation,
3223
3138
  sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
3224
3139
  busy,
3225
- floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "flex items-center gap-2", children: [
3140
+ floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "flex items-center gap-2", children: [
3226
3141
  floatingControlsLeading,
3227
- canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
3142
+ canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
3228
3143
  ] }) : void 0,
3229
3144
  defaultViewport,
3230
3145
  viewportRestore,
3231
- onViewportChange,
3232
- ...infoPanelCuration
3146
+ onViewportChange
3233
3147
  }
3234
3148
  ) }) })
3235
3149
  ) : viewMode === "pop-core" ? (
3236
3150
  // Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
3237
- /* @__PURE__ */ (0, import_jsx_runtime41.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_runtime41.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3151
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3238
3152
  GenrePlaylistTreeWheelRadialPopCore,
3239
3153
  {
3240
3154
  scope,
@@ -3243,28 +3157,25 @@ function GenreTreeView({
3243
3157
  handleGenreRenameAction,
3244
3158
  additionalActions,
3245
3159
  onSelectedNodeChange: handleSelectedNodeChange,
3246
- hideInfoPanelClose: true,
3247
- hideInfoPanelTitle: true,
3248
3160
  onNodeHover: handleNodeHover,
3249
- renderInfoPanelBody,
3161
+ ...hostPanel,
3250
3162
  playback,
3251
3163
  selectedNodeId,
3252
3164
  readOnly,
3253
3165
  allowWheelRotation,
3254
3166
  sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
3255
3167
  busy,
3256
- floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "flex items-center gap-2", children: [
3168
+ floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "flex items-center gap-2", children: [
3257
3169
  floatingControlsLeading,
3258
- canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
3170
+ canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
3259
3171
  ] }) : void 0,
3260
3172
  defaultViewport,
3261
3173
  viewportRestore,
3262
3174
  onViewportChange,
3263
- ...infoPanelCuration,
3264
3175
  ...focus
3265
3176
  }
3266
3177
  ) }) })
3267
- ) : viewMode === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime41.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_runtime41.jsx)(
3178
+ ) : viewMode === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3268
3179
  GenrePlaylistTreeWheelRadialPopCore,
3269
3180
  {
3270
3181
  outline: true,
@@ -3274,22 +3185,19 @@ function GenreTreeView({
3274
3185
  handleGenreRenameAction,
3275
3186
  additionalActions,
3276
3187
  onSelectedNodeChange: handleSelectedNodeChange,
3277
- hideInfoPanelClose: true,
3278
- hideInfoPanelTitle: true,
3279
3188
  onNodeHover: handleNodeHover,
3280
- renderInfoPanelBody,
3189
+ ...hostPanel,
3281
3190
  playback,
3282
3191
  selectedNodeId,
3283
- readOnly,
3284
- ...infoPanelCuration
3192
+ readOnly
3285
3193
  }
3286
- ) }) : /* @__PURE__ */ (0, import_jsx_runtime41.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(
3287
- ([uuid, genrePlaylistTreePerRoot], rootIndex) => {
3288
- return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3194
+ ) }) : /* @__PURE__ */ (0, import_jsx_runtime40.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)]", children: Object.entries(groupedGenrePlaylistsByRoot).map(
3195
+ ([uuid, genrePlaylistTreePerRoot]) => {
3196
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3289
3197
  "div",
3290
3198
  {
3291
3199
  className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
3292
- children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3200
+ children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3293
3201
  GenrePlaylistTreePerRoot,
3294
3202
  {
3295
3203
  scope,
@@ -3299,26 +3207,45 @@ function GenreTreeView({
3299
3207
  handleGenreRenameAction,
3300
3208
  additionalActions,
3301
3209
  onSelectedNodeChange: handleSelectedNodeChange,
3302
- hideInfoPanelClose: true,
3303
- hideInfoPanelTitle: true,
3304
3210
  onNodeHover: handleNodeHover,
3305
- renderInfoPanelBody,
3211
+ ...hostPanel,
3306
3212
  playback,
3307
3213
  selectedNodeId: uuid === selectedGenrePlaylist?.root.uuid ? selectedNodeId : null,
3308
3214
  readOnly,
3309
3215
  wheelZoom: "modifier",
3310
3216
  defaultViewport: uuid === selectedGenrePlaylist?.root.uuid ? defaultViewport : void 0,
3311
3217
  viewportRestore: uuid === selectedGenrePlaylist?.root.uuid && uuid === restoreRootUuid ? viewportRestore : void 0,
3312
- onViewportChange: uuid === selectedGenrePlaylist?.root.uuid ? onViewportChange : void 0,
3313
- ...infoPanelCuration,
3314
- overlayPanel: rootIndex === 0 ? activeOverlayPanel : void 0
3218
+ onViewportChange: uuid === selectedGenrePlaylist?.root.uuid ? onViewportChange : void 0
3315
3219
  }
3316
3220
  ) })
3317
3221
  },
3318
3222
  uuid
3319
3223
  );
3320
3224
  }
3321
- ) })
3225
+ ) }),
3226
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3227
+ import_ui18.DetailPanel,
3228
+ {
3229
+ ref: panelRef,
3230
+ side: "left",
3231
+ title: activePanel?.title ?? "",
3232
+ hideTitle: activePanel?.compact,
3233
+ hideClose: activePanel?.compact,
3234
+ headerAccent: activePanel?.headerAccent,
3235
+ headerActions: expandButton,
3236
+ onClose: activePanel?.onClose ?? noop,
3237
+ collapsed: isCollapsed || !isTreeReady || !activePanel,
3238
+ expanded: activePanel?.expanded,
3239
+ onExpandedChange: activePanel?.onExpandedChange,
3240
+ aside: activePanel?.aside,
3241
+ edgeTab: detailTab,
3242
+ headerClassName: activePanel?.compact ? "pt-[calc(var(--layout-search-h)+var(--layout-panel-inset)+0.5rem)]" : void 0,
3243
+ children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
3244
+ activePanel.content,
3245
+ activePanel.aside
3246
+ ] }) : activePanel?.content
3247
+ }
3248
+ )
3322
3249
  ] }) })
3323
3250
  ]
3324
3251
  }
@@ -3326,12 +3253,12 @@ function GenreTreeView({
3326
3253
  }
3327
3254
 
3328
3255
  // src/genre-tree/GenreDetailPanel.tsx
3329
- var import_react18 = require("react");
3256
+ var import_react17 = require("react");
3330
3257
  var import_genre_tree_view5 = require("@behindthemusictree/genre-tree-view");
3331
3258
  var import_ui19 = require("@behindthemusictree/ui");
3259
+ var import_lucide_react17 = require("lucide-react");
3332
3260
  var import_genre_tree20 = require("@behindthemusictree/app-kit/genre-tree");
3333
- var import_jsx_runtime42 = require("react/jsx-runtime");
3334
- var ITEM_COUNT_LABEL = "Songs";
3261
+ var import_jsx_runtime41 = require("react/jsx-runtime");
3335
3262
  var RELATIVE_ITEM_COUNT = 1;
3336
3263
  function toNode(genre, parentId) {
3337
3264
  return {
@@ -3355,11 +3282,12 @@ function GenreDetailPanel({
3355
3282
  collapsed,
3356
3283
  expanded,
3357
3284
  onExpandedChange,
3285
+ edgeTab,
3358
3286
  extras
3359
3287
  }) {
3360
3288
  const { data: detail, isPending, isError } = (0, import_genre_tree20.useFetchGenreDetail)(genreUuid, scope, getBackendBaseUrl);
3361
3289
  const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: [] });
3362
- const view = (0, import_react18.useMemo)(() => {
3290
+ const view = (0, import_react17.useMemo)(() => {
3363
3291
  if (!detail) return null;
3364
3292
  const rootColor = (0, import_genre_tree_view5.getGenreTreeColor)(detail.root.wikidataId);
3365
3293
  const relative = (genre, parentId) => ({
@@ -3367,7 +3295,7 @@ function GenreDetailPanel({
3367
3295
  fill: (0, import_genre_tree_view5.tintSurface)(rootColor),
3368
3296
  textColor: import_genre_tree_view5.TEXT_COLOR
3369
3297
  });
3370
- const ancestors = [...detail.ascendants].sort((a, b) => b.degree - a.degree);
3298
+ const ancestors2 = [...detail.ascendants].sort((a, b) => b.degree - a.degree);
3371
3299
  return {
3372
3300
  node: {
3373
3301
  id: detail.uuid,
@@ -3379,57 +3307,72 @@ function GenreDetailPanel({
3379
3307
  fill: rootColor,
3380
3308
  parentNode: detail.parent ? relative(detail.parent, null) : null,
3381
3309
  childNodes: detail.children.map((child) => relative(child, detail.uuid)),
3382
- ancestorNodes: ancestors.map(
3383
- ({ ascendant }, index) => relative(ascendant, index === 0 ? null : ancestors[index - 1].ascendant.uuid)
3310
+ ancestorNodes: ancestors2.map(
3311
+ ({ ascendant }, index) => relative(ascendant, index === 0 ? null : ancestors2[index - 1].ascendant.uuid)
3384
3312
  )
3385
3313
  };
3386
3314
  }, [detail]);
3387
3315
  if (isError) {
3388
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
3316
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
3389
3317
  }
3390
3318
  if (isPending || !detail || !view) {
3391
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
3392
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui19.Skeleton, { className: "h-6 w-2/3" }),
3393
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui19.Skeleton, { className: "h-4 w-full" }),
3394
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui19.Skeleton, { className: "h-4 w-3/4" })
3319
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
3320
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui19.Skeleton, { className: "h-6 w-2/3" }),
3321
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui19.Skeleton, { className: "h-4 w-full" }),
3322
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui19.Skeleton, { className: "h-4 w-3/4" })
3395
3323
  ] });
3396
3324
  }
3397
- const renderBody = (node, sections) => ({
3398
- primary: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
3399
- GenreInfoPanelBody,
3400
- {
3401
- toolbar: sections.toolbar,
3402
- family: sections.family,
3403
- genreUuid: detail.uuid,
3404
- genrePlaylist: { ...detail.criteriaPlaylist, tracksCount: detail.tracksCount },
3405
- wikidataId: detail.wikidataId,
3406
- scope,
3407
- getBackendBaseUrl,
3408
- playback,
3409
- band: { node, onZoomIn, shareUrl, shareTarget: shareUrl ? shareTarget : void 0 },
3410
- extras
3411
- }
3412
- ),
3413
- aside: null
3414
- });
3415
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
3416
- import_genre_tree_view5.InfoPanel,
3325
+ const selectGenre = (nodeId) => onSelectGenre(nodeId);
3326
+ const family = /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3327
+ import_genre_tree_view5.GenreNodeFamily,
3417
3328
  {
3418
3329
  node: view.node,
3419
3330
  fill: view.fill,
3420
3331
  textColor: import_genre_tree_view5.ACCENT_TEXT_COLOR,
3421
3332
  parentNode: view.parentNode,
3422
3333
  childNodes: view.childNodes,
3423
- ancestorNodes: view.ancestorNodes,
3334
+ onSelectNode: selectGenre
3335
+ }
3336
+ );
3337
+ const ancestors = /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_genre_tree_view5.GenreNodeAncestors, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
3338
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
3339
+ import_ui19.DetailPanel,
3340
+ {
3424
3341
  side: "left",
3342
+ title: view.node.name,
3343
+ headerAccent: { background: view.fill, color: import_genre_tree_view5.ACCENT_TEXT_COLOR },
3344
+ headerActions: onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3345
+ import_ui19.IconButton,
3346
+ {
3347
+ "aria-label": expanded ? "Shrink panel" : "Extend panel",
3348
+ style: { color: "inherit" },
3349
+ onClick: () => onExpandedChange(!expanded),
3350
+ children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_lucide_react17.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_lucide_react17.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
3351
+ }
3352
+ ) : void 0,
3425
3353
  onClose,
3426
- onSelectNode: (nodeId) => onSelectGenre(nodeId),
3427
- itemCountLabel: ITEM_COUNT_LABEL,
3428
- renderInfoPanelBody: renderBody,
3354
+ collapsed,
3429
3355
  expanded,
3430
3356
  onExpandedChange,
3431
- collapsed,
3432
- hideExpandButton: onExpandedChange === void 0
3357
+ edgeTab,
3358
+ aside: ancestors,
3359
+ children: [
3360
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3361
+ GenreInfoPanelBody,
3362
+ {
3363
+ family,
3364
+ genreUuid: detail.uuid,
3365
+ genrePlaylist: { ...detail.criteriaPlaylist, tracksCount: detail.tracksCount },
3366
+ wikidataId: detail.wikidataId,
3367
+ scope,
3368
+ getBackendBaseUrl,
3369
+ playback,
3370
+ band: { node: view.node, onZoomIn, shareUrl, shareTarget: shareUrl ? shareTarget : void 0 },
3371
+ extras
3372
+ }
3373
+ ),
3374
+ !expanded && ancestors
3375
+ ]
3433
3376
  }
3434
3377
  );
3435
3378
  }
@@ -3438,13 +3381,13 @@ function GenreDetailPanel({
3438
3381
  var import_genre_tree_view6 = require("@behindthemusictree/genre-tree-view");
3439
3382
 
3440
3383
  // src/genre-tree/track-list-sidebar/TrackListSidebar.tsx
3441
- var import_react19 = require("react");
3442
- var import_lucide_react17 = require("lucide-react");
3384
+ var import_react18 = require("react");
3385
+ var import_lucide_react18 = require("lucide-react");
3443
3386
  var import_genre_tree21 = require("@behindthemusictree/app-kit/genre-tree");
3444
3387
  var import_genre_tree22 = require("@behindthemusictree/app-kit/genre-tree");
3445
- var import_track_noun7 = require("@behindthemusictree/app-kit/track-noun");
3446
- var import_jsx_runtime43 = require("react/jsx-runtime");
3447
- var useBrowserLayoutEffect = typeof window === "undefined" ? import_react19.useEffect : import_react19.useLayoutEffect;
3388
+ var import_track_noun4 = require("@behindthemusictree/app-kit/track-noun");
3389
+ var import_jsx_runtime42 = require("react/jsx-runtime");
3390
+ var useBrowserLayoutEffect = typeof window === "undefined" ? import_react18.useEffect : import_react18.useLayoutEffect;
3448
3391
  function TrackListSidebar({
3449
3392
  className: className2 = "",
3450
3393
  renderDuration,
@@ -3457,17 +3400,17 @@ function TrackListSidebar({
3457
3400
  renderSongLink
3458
3401
  }) {
3459
3402
  const { trackList, loadMore, autoAdvanceScrollRequest } = (0, import_genre_tree21.useTrackList)();
3460
- const trackNoun = (0, import_track_noun7.useTrackNoun)();
3461
- const trackListRef = (0, import_react19.useRef)(null);
3462
- const previousOriginRef = (0, import_react19.useRef)(trackList?.origin ?? null);
3463
- const handledAutoAdvanceRequestIdRef = (0, import_react19.useRef)(null);
3464
- const shouldFollowPlaybackRef = (0, import_react19.useRef)(true);
3465
- const programmaticScrollTopRef = (0, import_react19.useRef)(null);
3466
- const userScrollIntentRef = (0, import_react19.useRef)(false);
3467
- const userScrollIntentTimerRef = (0, import_react19.useRef)(null);
3468
- const loadMoreSentinelRef = (0, import_react19.useRef)(null);
3403
+ const trackNoun = (0, import_track_noun4.useTrackNoun)();
3404
+ const trackListRef = (0, import_react18.useRef)(null);
3405
+ const previousOriginRef = (0, import_react18.useRef)(trackList?.origin ?? null);
3406
+ const handledAutoAdvanceRequestIdRef = (0, import_react18.useRef)(null);
3407
+ const shouldFollowPlaybackRef = (0, import_react18.useRef)(true);
3408
+ const programmaticScrollTopRef = (0, import_react18.useRef)(null);
3409
+ const userScrollIntentRef = (0, import_react18.useRef)(false);
3410
+ const userScrollIntentTimerRef = (0, import_react18.useRef)(null);
3411
+ const loadMoreSentinelRef = (0, import_react18.useRef)(null);
3469
3412
  const hasMore = !!trackList && trackList.nextPage !== null;
3470
- (0, import_react19.useEffect)(() => {
3413
+ (0, import_react18.useEffect)(() => {
3471
3414
  const sentinel = loadMoreSentinelRef.current;
3472
3415
  if (!hasMore || !sentinel) return;
3473
3416
  const observer = new IntersectionObserver((entries) => {
@@ -3487,11 +3430,11 @@ function TrackListSidebar({
3487
3430
  programmaticScrollTopRef.current = null;
3488
3431
  if (trackListRef.current) trackListRef.current.scrollTop = 0;
3489
3432
  }, [trackList?.origin]);
3490
- (0, import_react19.useEffect)(() => {
3433
+ (0, import_react18.useEffect)(() => {
3491
3434
  const list = trackListRef.current;
3492
3435
  if (!isCollapsed && list?.scrollTop === 0) shouldFollowPlaybackRef.current = true;
3493
3436
  }, [isCollapsed]);
3494
- (0, import_react19.useEffect)(
3437
+ (0, import_react18.useEffect)(
3495
3438
  () => () => {
3496
3439
  if (userScrollIntentTimerRef.current !== null) clearTimeout(userScrollIntentTimerRef.current);
3497
3440
  },
@@ -3545,25 +3488,25 @@ function TrackListSidebar({
3545
3488
  }
3546
3489
  };
3547
3490
  const positionClasses = layout === "inline" ? "relative w-full flex h-full flex-col" : "fixed right-0 w-[50rem] flex flex-col";
3548
- return trackList ? /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(
3491
+ return trackList ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
3549
3492
  "div",
3550
3493
  {
3551
3494
  "data-theme": "dark",
3552
3495
  className: `track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${className2}`,
3553
3496
  style: layout === "fixed" ? { top: "63.5px", maxHeight: "calc(100vh - 63.5px - 79px)" } : void 0,
3554
3497
  children: [
3555
- /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
3556
- /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
3557
- /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "origin flex text-xl", children: [
3558
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
3559
- /* @__PURE__ */ (0, import_jsx_runtime43.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_runtime43.jsx)("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
3498
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
3499
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
3500
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "origin flex text-xl", children: [
3501
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
3502
+ /* @__PURE__ */ (0, import_jsx_runtime42.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_runtime42.jsx)("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
3560
3503
  ] }),
3561
- /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
3504
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
3562
3505
  trackList.origin.type === import_genre_tree22.TrackListOriginType.GENRE_PLAYLIST ? "Genre \u2022 " : trackList.origin.type === import_genre_tree22.TrackListOriginType.ARTIST ? "Artist \u2022 " : `${trackNoun.singular} playlist \u2022 `,
3563
3506
  `${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`
3564
3507
  ] })
3565
3508
  ] }),
3566
- (isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
3509
+ (isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
3567
3510
  "button",
3568
3511
  {
3569
3512
  type: "button",
@@ -3572,11 +3515,11 @@ function TrackListSidebar({
3572
3515
  "aria-expanded": !isCollapsed,
3573
3516
  className: `ml-2 flex h-8 w-8 shrink-0 items-center justify-center rounded-md hover:bg-[var(--color-surface-muted)] ${isCollapsed ? "text-[var(--color-text-muted)]" : "text-white"}`,
3574
3517
  onClick: isCollapsed ? onExpand : onMinimize,
3575
- children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react17.ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
3518
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react18.ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
3576
3519
  }
3577
3520
  )
3578
3521
  ] }),
3579
- !isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(
3522
+ !isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
3580
3523
  "ul",
3581
3524
  {
3582
3525
  ref: trackListRef,
@@ -3586,7 +3529,7 @@ function TrackListSidebar({
3586
3529
  onPointerDown: markUserScrollIntent,
3587
3530
  className: layout === "inline" ? "track-list flex-1 min-h-0 overflow-auto list-none p-0 m-0" : "track-list flex-1 min-h-0 overflow-auto list-none p-0 m-0",
3588
3531
  children: [
3589
- trackList.tracks.map((track, index) => /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
3532
+ trackList.tracks.map((track, index) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
3590
3533
  TrackItem,
3591
3534
  {
3592
3535
  track,
@@ -3597,7 +3540,7 @@ function TrackListSidebar({
3597
3540
  renderSongLink
3598
3541
  }
3599
3542
  ) }, track.uuid)),
3600
- hasMore && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
3543
+ hasMore && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
3601
3544
  ]
3602
3545
  }
3603
3546
  )
@@ -3607,16 +3550,17 @@ function TrackListSidebar({
3607
3550
  }
3608
3551
 
3609
3552
  // src/genre-tree/PlayerPanel.tsx
3610
- var import_react20 = require("react");
3611
- var import_tailwind_merge2 = require("tailwind-merge");
3553
+ var import_react19 = require("react");
3554
+ var import_tailwind_merge = require("tailwind-merge");
3612
3555
  var import_player13 = require("@behindthemusictree/app-kit/player");
3613
3556
  var import_genre_tree24 = require("@behindthemusictree/app-kit/genre-tree");
3557
+ var import_ui20 = require("@behindthemusictree/ui");
3614
3558
 
3615
3559
  // src/genre-tree/PlayerControls.tsx
3616
- var import_lucide_react18 = require("lucide-react");
3560
+ var import_lucide_react19 = require("lucide-react");
3617
3561
  var import_genre_tree23 = require("@behindthemusictree/app-kit/genre-tree");
3618
3562
  var import_player12 = require("@behindthemusictree/app-kit/player");
3619
- var import_jsx_runtime44 = require("react/jsx-runtime");
3563
+ var import_jsx_runtime43 = require("react/jsx-runtime");
3620
3564
  function PlayerControls() {
3621
3565
  const {
3622
3566
  duration,
@@ -3635,15 +3579,15 @@ function PlayerControls() {
3635
3579
  const safeDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;
3636
3580
  const safeCurrentTime = Number.isFinite(currentTime) ? Math.max(0, Math.min(currentTime, safeDuration)) : 0;
3637
3581
  const isPlaying = playState === import_player12.PlayStates.PLAYING;
3638
- return /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(
3582
+ return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(
3639
3583
  "section",
3640
3584
  {
3641
3585
  "aria-label": "Player controls",
3642
3586
  className: "player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200",
3643
3587
  children: [
3644
- /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
3645
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { "aria-label": "Elapsed time", children: (0, import_genre_tree23.formatTime)(safeCurrentTime) }),
3646
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3588
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
3589
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { "aria-label": "Elapsed time", children: (0, import_genre_tree23.formatTime)(safeCurrentTime) }),
3590
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
3647
3591
  "input",
3648
3592
  {
3649
3593
  "aria-label": "Seek",
@@ -3657,11 +3601,11 @@ function PlayerControls() {
3657
3601
  onChange: (event) => seekTo(Number(event.currentTarget.value))
3658
3602
  }
3659
3603
  ),
3660
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { "aria-label": "Duration", children: (0, import_genre_tree23.formatTime)(safeDuration) })
3604
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { "aria-label": "Duration", children: (0, import_genre_tree23.formatTime)(safeDuration) })
3661
3605
  ] }),
3662
- /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
3663
- /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
3664
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3606
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
3607
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
3608
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
3665
3609
  "button",
3666
3610
  {
3667
3611
  type: "button",
@@ -3670,10 +3614,10 @@ function PlayerControls() {
3670
3614
  disabled: !isReady || !hasPreviousTrack,
3671
3615
  onClick: playPreviousTrack,
3672
3616
  className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-zinc-300 hover:bg-zinc-800 hover:text-white disabled:cursor-not-allowed disabled:opacity-40",
3673
- children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_lucide_react18.SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
3617
+ children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react19.SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
3674
3618
  }
3675
3619
  ),
3676
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3620
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
3677
3621
  "button",
3678
3622
  {
3679
3623
  type: "button",
@@ -3682,10 +3626,10 @@ function PlayerControls() {
3682
3626
  disabled: !isReady,
3683
3627
  onClick: handlePlayPauseAction,
3684
3628
  className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-white text-zinc-950 hover:bg-zinc-200 disabled:cursor-not-allowed disabled:opacity-40",
3685
- children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_lucide_react18.Pause, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_lucide_react18.Play, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
3629
+ children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react19.Pause, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react19.Play, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
3686
3630
  }
3687
3631
  ),
3688
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3632
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
3689
3633
  "button",
3690
3634
  {
3691
3635
  type: "button",
@@ -3694,14 +3638,14 @@ function PlayerControls() {
3694
3638
  disabled: !isReady || !hasNextTrack,
3695
3639
  onClick: playNextTrack,
3696
3640
  className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-zinc-300 hover:bg-zinc-800 hover:text-white disabled:cursor-not-allowed disabled:opacity-40",
3697
- children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_lucide_react18.SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
3641
+ children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react19.SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
3698
3642
  }
3699
3643
  )
3700
3644
  ] }),
3701
- /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("label", { className: "col-start-3 flex w-[4.2rem] shrink-0 items-center gap-[0.35rem] justify-self-end text-zinc-400 sm:w-[4.9rem]", children: [
3702
- volume === 0 ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_lucide_react18.VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_lucide_react18.Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
3703
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { className: "sr-only", children: "Volume" }),
3704
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3645
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("label", { className: "col-start-3 flex w-[4.2rem] shrink-0 items-center gap-[0.35rem] justify-self-end text-zinc-400 sm:w-[4.9rem]", children: [
3646
+ volume === 0 ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react19.VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react19.Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
3647
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "sr-only", children: "Volume" }),
3648
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
3705
3649
  "input",
3706
3650
  {
3707
3651
  "aria-label": "Volume",
@@ -3723,7 +3667,7 @@ function PlayerControls() {
3723
3667
  }
3724
3668
 
3725
3669
  // src/genre-tree/PlayerPanel.tsx
3726
- var import_jsx_runtime45 = require("react/jsx-runtime");
3670
+ var import_jsx_runtime44 = require("react/jsx-runtime");
3727
3671
  var PANEL_ID = "player-panel";
3728
3672
  var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
3729
3673
  function PlayerPanel({
@@ -3737,12 +3681,12 @@ function PlayerPanel({
3737
3681
  }) {
3738
3682
  const { playerTrackObject } = (0, import_player13.usePlayer)();
3739
3683
  const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0, import_genre_tree24.useTrackListSidebarVisibility)();
3740
- const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = (0, import_react20.useState)("");
3684
+ const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = (0, import_react19.useState)("");
3741
3685
  const loadedTrackTitle = playerTrackObject?.track.title;
3742
- (0, import_react20.useEffect)(() => {
3686
+ (0, import_react19.useEffect)(() => {
3743
3687
  if (loadedTrackTitle) setLastLoadedTrackTitle(loadedTrackTitle);
3744
3688
  }, [loadedTrackTitle]);
3745
- const [uncontrolledExtended, setUncontrolledExtended] = (0, import_react20.useState)(false);
3689
+ const [uncontrolledExtended, setUncontrolledExtended] = (0, import_react19.useState)(false);
3746
3690
  const isExtendRequested = expanded ?? uncontrolledExtended;
3747
3691
  const setIsExtendRequested = (next) => {
3748
3692
  setUncontrolledExtended(next);
@@ -3763,11 +3707,11 @@ function PlayerPanel({
3763
3707
  onClick: () => setIsExtendRequested(true),
3764
3708
  expanded: false
3765
3709
  } : null;
3766
- return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(
3710
+ return /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(
3767
3711
  "div",
3768
3712
  {
3769
3713
  id: PANEL_ID,
3770
- className: (0, import_tailwind_merge2.twMerge)(
3714
+ className: (0, import_tailwind_merge.twMerge)(
3771
3715
  "fixed right-[var(--layout-gutter)] top-[var(--layout-band-top)] z-40 max-h-[calc(100vh-var(--layout-band-top)-3.5rem)]",
3772
3716
  !isExtended && "w-[var(--layout-right-w)]",
3773
3717
  isTrackListSidebarVisible && "h-[calc(100vh-var(--layout-band-top)-3.5rem)]",
@@ -3776,8 +3720,8 @@ function PlayerPanel({
3776
3720
  ),
3777
3721
  style: isExtended ? { width: extendedWidth } : void 0,
3778
3722
  children: [
3779
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3780
- PanelEdgeTab,
3723
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3724
+ import_ui20.PanelEdgeTab,
3781
3725
  {
3782
3726
  side: "right",
3783
3727
  edge: tabEdge,
@@ -3786,30 +3730,30 @@ function PlayerPanel({
3786
3730
  className: "absolute right-full top-1/2 -translate-y-1/2"
3787
3731
  }
3788
3732
  ),
3789
- /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(
3733
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(
3790
3734
  "div",
3791
3735
  {
3792
- className: (0, import_tailwind_merge2.twMerge)(
3736
+ className: (0, import_tailwind_merge.twMerge)(
3793
3737
  "flex h-full max-h-[inherit] overflow-hidden rounded-[var(--radius-panel,1rem)] bg-zinc-950 text-zinc-100 shadow-[0_1px_2px_rgba(0,0,0,0.5),0_2px_6px_2px_rgba(0,0,0,0.3)]",
3794
3738
  isExtended ? "flex-row" : "flex-col"
3795
3739
  ),
3796
3740
  children: [
3797
- /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
3798
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
3799
- /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("div", { className: "relative w-full shrink-0", children: [
3800
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_player13.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
3801
- playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: playerTrackObject.loadError })
3741
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
3742
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
3743
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "relative w-full shrink-0", children: [
3744
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_player13.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
3745
+ playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: playerTrackObject.loadError })
3802
3746
  ] }),
3803
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(PlayerControls, {})
3747
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(PlayerControls, {})
3804
3748
  ] }),
3805
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3749
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3806
3750
  TrackListSidebar,
3807
3751
  {
3808
3752
  layout: "inline",
3809
3753
  isCollapsed: !isTrackListSidebarVisible,
3810
3754
  onExpand: toggleTrackListSidebar,
3811
3755
  onMinimize: hideSongList,
3812
- className: (0, import_tailwind_merge2.twMerge)(
3756
+ className: (0, import_tailwind_merge.twMerge)(
3813
3757
  "min-h-0 min-w-0 flex-1",
3814
3758
  isExtended && "border-l border-zinc-800"
3815
3759
  ),
@@ -3829,11 +3773,11 @@ function PlayerPanel({
3829
3773
  var import_uuid = require("uuid");
3830
3774
  var import_ai = require("react-icons/ai");
3831
3775
  var import_genre_tree25 = require("@behindthemusictree/app-kit/genre-tree");
3832
- var import_jsx_runtime46 = require("react/jsx-runtime");
3776
+ var import_jsx_runtime45 = require("react/jsx-runtime");
3833
3777
  function Rating({ rating, handleChange }) {
3834
3778
  const instanceId = (0, import_uuid.v4)();
3835
- return /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
3836
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
3779
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
3780
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3837
3781
  "input",
3838
3782
  {
3839
3783
  id: `star-${instanceId}-${i}`,
@@ -3845,16 +3789,16 @@ function Rating({ rating, handleChange }) {
3845
3789
  onChange: handleChange
3846
3790
  }
3847
3791
  ),
3848
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "flex", children: [
3849
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
3792
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("div", { className: "flex", children: [
3793
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3850
3794
  import_ai.AiOutlineStar,
3851
3795
  {
3852
3796
  className: `w-7 h-7 absolute top-0 left-0 ${rating === import_genre_tree25.FORM_RATING_NULL_VALUE ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
3853
3797
  }
3854
3798
  ),
3855
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { className: "flex-grow w-8" }),
3799
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "flex-grow w-8" }),
3856
3800
  " "
3857
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
3801
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3858
3802
  import_ai.AiFillStar,
3859
3803
  {
3860
3804
  className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
@@ -3880,7 +3824,6 @@ function Rating({ rating, handleChange }) {
3880
3824
  GenreTreeWheelSkeleton,
3881
3825
  InternalErrorPopup,
3882
3826
  NetworkErrorPopup,
3883
- PanelEdgeTab,
3884
3827
  PlayerPanel,
3885
3828
  PopupButtons,
3886
3829
  PopupTitle,
@@ -3891,7 +3834,6 @@ function Rating({ rating, handleChange }) {
3891
3834
  TrackListSidebar,
3892
3835
  TrackPositionPlayPause,
3893
3836
  TrackUploadPopup,
3894
- useGenrePlaylistPlayback,
3895
- useLeftDetailPanelLayout
3837
+ useGenrePlaylistPlayback
3896
3838
  });
3897
3839
  //# sourceMappingURL=index.cjs.map