@behindthemusictree/app-kit-ui 33.1.0 → 33.3.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.
@@ -32,10 +32,16 @@ __export(genre_tree_exports, {
32
32
  GenreTreeWheelSkeleton: () => import_genre_tree_view6.GenreTreeWheelSkeleton,
33
33
  PlayerPanel: () => PlayerPanel,
34
34
  Rating: () => Rating,
35
+ SEARCH_FAILED_STATUS: () => SEARCH_FAILED_STATUS,
36
+ SearchBox: () => SearchBox,
35
37
  TrackItem: () => TrackItem,
36
38
  TrackListSidebar: () => TrackListSidebar,
37
39
  TrackPositionPlayPause: () => TrackPositionPlayPause,
38
- useGenrePlaylistPlayback: () => useGenrePlaylistPlayback
40
+ searchStatus: () => searchStatus,
41
+ useArtistSearchGroup: () => useArtistSearchGroup,
42
+ useGenrePlaylistPlayback: () => useGenrePlaylistPlayback,
43
+ useGenreRemoteSearchGroup: () => useGenreRemoteSearchGroup,
44
+ useSongSearchGroup: () => useSongSearchGroup
39
45
  });
40
46
  module.exports = __toCommonJS(genre_tree_exports);
41
47
 
@@ -44,10 +50,9 @@ var import_react16 = require("react");
44
50
  var import_react_query3 = require("@tanstack/react-query");
45
51
  var import_lucide_react11 = require("lucide-react");
46
52
  var import_lucide_react12 = require("lucide-react");
47
- var import_ui15 = require("@behindthemusictree/ui");
53
+ var import_ui16 = require("@behindthemusictree/ui");
48
54
  var import_genre_tree_view4 = require("@behindthemusictree/genre-tree-view");
49
- var import_genre_tree18 = require("@behindthemusictree/app-kit/genre-tree");
50
- var import_genre_tree19 = require("@behindthemusictree/app-kit/genre-tree");
55
+ var import_genre_tree21 = require("@behindthemusictree/app-kit/genre-tree");
51
56
  var import_popup5 = require("@behindthemusictree/app-kit/popup");
52
57
 
53
58
  // src/genre-tree/playlist-tree/size-metric.ts
@@ -695,11 +700,143 @@ function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
695
700
  }
696
701
 
697
702
  // src/genre-tree/GenreSearch.tsx
698
- var import_react7 = require("react");
699
703
  var import_lucide_react3 = require("lucide-react");
704
+ var import_ui4 = require("@behindthemusictree/ui");
705
+ var import_genre_tree5 = require("@behindthemusictree/app-kit/genre-tree");
706
+
707
+ // src/genre-tree/search/SearchBox.tsx
708
+ var import_react7 = require("react");
700
709
  var import_ui3 = require("@behindthemusictree/ui");
701
- var import_genre_tree2 = require("@behindthemusictree/app-kit/genre-tree");
702
710
  var import_jsx_runtime9 = require("react/jsx-runtime");
711
+ function SearchBox({
712
+ label,
713
+ placeholder,
714
+ value,
715
+ onValueChange,
716
+ groups,
717
+ onPicked,
718
+ onClear,
719
+ leading,
720
+ className: className2,
721
+ onFocus
722
+ }) {
723
+ const [open, setOpen] = (0, import_react7.useState)(false);
724
+ const loading = groups.some((group) => group.loading);
725
+ const status = groups.map((group) => group.status).filter(Boolean).join(". ");
726
+ const handlePick = (groupKey, itemKey) => {
727
+ setOpen(false);
728
+ groups.find((group) => group.key === groupKey)?.items.find((item) => item.key === itemKey)?.onSelect();
729
+ onPicked?.(groupKey, itemKey);
730
+ };
731
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: className2, onFocusCapture: onFocus, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
732
+ import_ui3.SearchCombobox,
733
+ {
734
+ ariaLabel: label,
735
+ listLabel: `${label} results`,
736
+ value,
737
+ placeholder: placeholder ?? `${label}\u2026`,
738
+ groups: groups.map((group) => ({
739
+ key: group.key,
740
+ label: group.label,
741
+ options: group.items.map((item) => ({ key: item.key, content: item.content }))
742
+ })),
743
+ open,
744
+ onOpenChange: setOpen,
745
+ onValueChange,
746
+ onPick: handlePick,
747
+ status,
748
+ loading,
749
+ onClear,
750
+ leading,
751
+ inputClassName: "focus-visible:ring-0"
752
+ }
753
+ ) });
754
+ }
755
+
756
+ // src/genre-tree/search/SearchGroup.ts
757
+ var SEARCH_FAILED_STATUS = "Search failed";
758
+ var searchStatus = (state, count, noun) => state.isError ? SEARCH_FAILED_STATUS : state.settled && count === 0 ? `No ${noun} found` : "";
759
+
760
+ // src/genre-tree/search/useArtistSearchGroup.tsx
761
+ var import_genre_tree2 = require("@behindthemusictree/app-kit/genre-tree");
762
+ var import_jsx_runtime10 = require("react/jsx-runtime");
763
+ function useArtistSearchGroup(getBaseUrl, text, onSelect, enabled = true) {
764
+ const search = (0, import_genre_tree2.useArtistSearch)(getBaseUrl, enabled ? text : "");
765
+ if (!enabled) return void 0;
766
+ return {
767
+ key: "artists",
768
+ label: "Artists",
769
+ items: search.artists.map((artist) => ({
770
+ key: artist.uuid,
771
+ content: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
772
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "block truncate font-medium", children: [
773
+ artist.name,
774
+ artist.disambiguation && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "font-normal text-[var(--color-neutral-500)]", children: [
775
+ " \xB7 ",
776
+ artist.disambiguation
777
+ ] })
778
+ ] }),
779
+ artist.matchedAlias && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [
780
+ "also known as ",
781
+ artist.matchedAlias
782
+ ] })
783
+ ] }),
784
+ onSelect: () => onSelect(artist)
785
+ })),
786
+ status: searchStatus(search, search.artists.length, "artists"),
787
+ loading: search.pending
788
+ };
789
+ }
790
+
791
+ // src/genre-tree/search/useSongSearchGroup.tsx
792
+ var import_genre_tree3 = require("@behindthemusictree/app-kit/genre-tree");
793
+ var import_jsx_runtime11 = require("react/jsx-runtime");
794
+ function useSongSearchGroup(getBaseUrl, text, onSelect, enabled = true) {
795
+ const search = (0, import_genre_tree3.useSongSearch)(getBaseUrl, enabled ? text : "");
796
+ if (!enabled) return void 0;
797
+ return {
798
+ key: "songs",
799
+ label: "Songs",
800
+ items: search.songs.map((song) => ({
801
+ key: song.uuid,
802
+ content: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: (0, import_genre_tree3.isSongSearchResultPlayable)(song) ? void 0 : "opacity-60", children: [
803
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "block truncate font-medium", children: song.title }),
804
+ /* @__PURE__ */ (0, import_jsx_runtime11.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 ") })
805
+ ] }),
806
+ onSelect: () => onSelect(song)
807
+ })),
808
+ status: searchStatus(search, search.songs.length, "songs"),
809
+ loading: search.pending
810
+ };
811
+ }
812
+
813
+ // src/genre-tree/search/useGenreRemoteSearchGroup.tsx
814
+ var import_genre_tree4 = require("@behindthemusictree/app-kit/genre-tree");
815
+ var import_jsx_runtime12 = require("react/jsx-runtime");
816
+ function useGenreRemoteSearchGroup(getBaseUrl, text, onSelect, { key = "genres", label = "Genres", filter, describe, ...options } = {}) {
817
+ const search = (0, import_genre_tree4.useGenreRemoteSearch)(getBaseUrl, text, options);
818
+ const genres = filter ? search.genres.filter(filter) : search.genres;
819
+ return {
820
+ key,
821
+ label,
822
+ items: genres.map((genre) => ({
823
+ key: genre.uuid,
824
+ content: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
825
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "block truncate font-medium", children: [
826
+ genre.name,
827
+ genre.isUmbrella && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "ml-2 text-xs font-normal italic text-[var(--color-text-muted)]", children: "umbrella" })
828
+ ] }),
829
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: describe?.(genre) ?? `${genre.treeName} \xB7 ${genre.root.name}` })
830
+ ] }),
831
+ onSelect: () => onSelect(genre)
832
+ })),
833
+ status: searchStatus(search, genres.length, "genres"),
834
+ loading: search.pending
835
+ };
836
+ }
837
+
838
+ // src/genre-tree/GenreSearch.tsx
839
+ var import_jsx_runtime13 = require("react/jsx-runtime");
703
840
  function GenreSearch({
704
841
  genrePlaylists,
705
842
  onSelect,
@@ -708,120 +845,58 @@ function GenreSearch({
708
845
  onPrevious,
709
846
  placeholder,
710
847
  onQueryChange,
711
- extraGroups = []
848
+ extraGroups = [],
849
+ onFocus
712
850
  }) {
713
- const { query, setQuery, results, canClear, clear, select } = (0, import_genre_tree2.useGenreSearch)({
851
+ const { query, setQuery, results, canClear, clear, select } = (0, import_genre_tree5.useGenreSearch)({
714
852
  genrePlaylists,
715
853
  onSelect,
716
854
  selectedName,
717
855
  onClear
718
856
  });
719
- const [open, setOpen] = (0, import_react7.useState)(false);
720
857
  const active = query !== "" && query !== selectedName;
721
858
  const hasExtras = extraGroups.length > 0;
722
859
  const label = hasExtras ? "Search" : "Search a genre";
723
- const loading = active && extraGroups.some((group) => group.loading);
724
- const status = active ? extraGroups.map((group) => group.status).filter(Boolean).join(". ") : "";
725
860
  const groups = [
726
861
  {
727
862
  key: "genres",
728
863
  label: hasExtras ? "Genres" : void 0,
729
- options: results.map((genrePlaylist) => ({
864
+ items: results.map((genrePlaylist) => ({
730
865
  key: genrePlaylist.uuid,
731
- content: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
866
+ content: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
732
867
  genrePlaylist.name,
733
- genrePlaylist.isUmbrella && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ml-2 text-xs italic text-[var(--color-text-muted)]", children: "umbrella" })
734
- ] })
868
+ genrePlaylist.isUmbrella && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "ml-2 text-xs italic text-[var(--color-text-muted)]", children: "umbrella" })
869
+ ] }),
870
+ onSelect: () => select(genrePlaylist)
735
871
  }))
736
872
  },
737
- ...extraGroups.map((group) => ({
738
- key: group.key,
739
- label: group.label,
740
- options: active ? group.items.map((item) => ({ key: item.key, content: item.content })) : []
741
- }))
873
+ ...extraGroups.map(
874
+ (group) => active ? group : { ...group, items: [], status: "", loading: false }
875
+ )
742
876
  ];
743
- const handlePick = (groupKey, optionKey) => {
744
- setOpen(false);
745
- if (groupKey === "genres") {
746
- const genrePlaylist = results.find((result) => result.uuid === optionKey);
747
- if (!genrePlaylist) return;
748
- select(genrePlaylist);
749
- onQueryChange?.("");
750
- return;
877
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "genre-search w-[calc(var(--layout-detail-panel-w,25.5rem)-2*var(--layout-panel-inset,1rem))] shrink-0 max-md:w-full", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
878
+ SearchBox,
879
+ {
880
+ label,
881
+ placeholder,
882
+ value: query,
883
+ onValueChange: (value) => {
884
+ setQuery(value);
885
+ onQueryChange?.(value);
886
+ },
887
+ groups,
888
+ onPicked: (groupKey) => {
889
+ if (groupKey !== "genres") setQuery("");
890
+ onQueryChange?.("");
891
+ },
892
+ onClear: canClear ? () => {
893
+ clear();
894
+ onQueryChange?.("");
895
+ } : void 0,
896
+ leading: onPrevious && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { type: "button", "aria-label": "Previous", title: "Previous", className: import_ui4.SEARCH_LEADING_BUTTON_CLASS, onClick: onPrevious, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react3.ArrowLeft, { "aria-hidden": "true", className: "h-4 w-4" }) }),
897
+ onFocus
751
898
  }
752
- const item = extraGroups.find((group) => group.key === groupKey)?.items.find((entry) => entry.key === optionKey);
753
- if (!item) return;
754
- setQuery("");
755
- onQueryChange?.("");
756
- item.onSelect();
757
- };
758
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "genre-search relative w-[calc(var(--layout-detail-panel-w,25.5rem)-2*var(--layout-panel-inset,1rem))] shrink-0 max-md:w-full", children: [
759
- onPrevious && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
760
- "button",
761
- {
762
- type: "button",
763
- "aria-label": "Previous",
764
- title: "Previous",
765
- 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)]",
766
- onClick: onPrevious,
767
- children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react3.ArrowLeft, { "aria-hidden": "true", className: "w-4 h-4" })
768
- }
769
- ),
770
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
771
- import_ui3.Combobox,
772
- {
773
- ariaLabel: label,
774
- listLabel: `${label} results`,
775
- value: query,
776
- placeholder: placeholder ?? `${label}\u2026`,
777
- groups,
778
- open,
779
- onOpenChange: setOpen,
780
- onValueChange: (value) => {
781
- setQuery(value);
782
- onQueryChange?.(value);
783
- },
784
- onPick: handlePick,
785
- status,
786
- loading: loading ? "Searching\u2026" : void 0,
787
- 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 focus-visible:ring-0`,
788
- className: "genre-search-results z-40 rounded-xl shadow-md",
789
- listClassName: "max-h-[min(20rem,50dvh)]",
790
- optionClassName: "[@media(pointer:coarse)]:min-h-[var(--layout-touch-target)]"
791
- }
792
- ),
793
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "absolute right-3 top-1/2 flex items-center -translate-y-1/2", children: [
794
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { "data-loading": loading, className: "relative p-2 text-[var(--color-text-muted)] pointer-events-none", children: [
795
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
796
- import_lucide_react3.Search,
797
- {
798
- "aria-hidden": "true",
799
- className: `w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? "opacity-0" : ""}`
800
- }
801
- ),
802
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
803
- import_lucide_react3.LoaderCircle,
804
- {
805
- "aria-hidden": "true",
806
- className: `absolute inset-2 w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? "opacity-100 delay-300 animate-spin motion-reduce:animate-none" : "opacity-0"}`
807
- }
808
- )
809
- ] }),
810
- canClear && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
811
- "button",
812
- {
813
- type: "button",
814
- "aria-label": "Clear search",
815
- className: "flex items-center justify-center p-2 [@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 hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-text)]",
816
- onClick: () => {
817
- clear();
818
- onQueryChange?.("");
819
- },
820
- children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react3.X, { "aria-hidden": "true", className: "w-6 h-6" })
821
- }
822
- )
823
- ] })
824
- ] });
899
+ ) });
825
900
  }
826
901
 
827
902
  // src/genre-tree/GenreInfoPanelBody.tsx
@@ -843,7 +918,7 @@ var import_si = require("react-icons/si");
843
918
  // src/popup/Toast.tsx
844
919
  var import_react8 = require("react");
845
920
  var import_react_dom2 = require("react-dom");
846
- var import_jsx_runtime10 = require("react/jsx-runtime");
921
+ var import_jsx_runtime14 = require("react/jsx-runtime");
847
922
  function Toast({ message, onClose, durationMs = 2200 }) {
848
923
  const [mounted, setMounted] = (0, import_react8.useState)(false);
849
924
  (0, import_react8.useEffect)(() => {
@@ -853,7 +928,7 @@ function Toast({ message, onClose, durationMs = 2200 }) {
853
928
  }, [durationMs, onClose]);
854
929
  if (!mounted) return null;
855
930
  return (0, import_react_dom2.createPortal)(
856
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "pointer-events-none fixed inset-x-0 bottom-8 z-[60] flex justify-center px-4", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
931
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "pointer-events-none fixed inset-x-0 bottom-8 z-[60] flex justify-center px-4", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
857
932
  "div",
858
933
  {
859
934
  role: "status",
@@ -868,12 +943,12 @@ function Toast({ message, onClose, durationMs = 2200 }) {
868
943
  }
869
944
 
870
945
  // src/genre-tree/ShareDestinationButton.tsx
871
- var import_jsx_runtime11 = require("react/jsx-runtime");
946
+ var import_jsx_runtime15 = require("react/jsx-runtime");
872
947
  var className = "group flex min-w-0 flex-1 flex-col items-center justify-center gap-1.5 rounded-xl px-2 py-2 text-xs text-[var(--color-text)] transition-colors hover:bg-[var(--color-surface-muted)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] disabled:cursor-not-allowed disabled:opacity-50";
873
948
  function Contents({ icon, label }) {
874
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
875
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]", children: icon }),
876
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "max-w-full truncate", children: label })
949
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
950
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]", children: icon }),
951
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "max-w-full truncate", children: label })
877
952
  ] });
878
953
  }
879
954
  function ShareDestinationButton({
@@ -885,29 +960,29 @@ function ShareDestinationButton({
885
960
  ariaLabel
886
961
  }) {
887
962
  if (href) {
888
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", "aria-label": ariaLabel, className, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Contents, { icon, label }) });
963
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", "aria-label": ariaLabel, className, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Contents, { icon, label }) });
889
964
  }
890
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", onClick, disabled, "aria-label": ariaLabel, className, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Contents, { icon, label }) });
965
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { type: "button", onClick, disabled, "aria-label": ariaLabel, className, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Contents, { icon, label }) });
891
966
  }
892
967
 
893
968
  // src/genre-tree/SharePreviewCard.tsx
894
969
  var import_lucide_react4 = require("lucide-react");
895
- var import_jsx_runtime12 = require("react/jsx-runtime");
970
+ var import_jsx_runtime16 = require("react/jsx-runtime");
896
971
  function SharePreviewCard({ target, url }) {
897
972
  const Icon = { genre: import_lucide_react4.Music2, artist: import_lucide_react4.UserRound, song: import_lucide_react4.Music }[target.kind];
898
973
  const typeLabel = { genre: "Genre", artist: "Artist", song: "Song" }[target.kind];
899
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "rounded-3xl bg-[var(--color-surface-muted)] p-4", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex min-h-32 overflow-hidden rounded-2xl bg-[var(--color-surface)] shadow-lg", children: [
900
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "flex w-28 shrink-0 items-center justify-center bg-[var(--color-primary)]/15 text-[var(--color-primary)]", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Icon, { "aria-hidden": "true", className: "h-10 w-10" }) }),
901
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col justify-center gap-1 p-4", children: [
902
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-xs font-medium uppercase tracking-wide text-[var(--color-text-muted)]", children: typeLabel }),
903
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "truncate text-base font-semibold text-[var(--color-text)]", title: target.name, children: target.name }),
904
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "truncate text-sm text-[var(--color-primary)]", title: url, children: url })
974
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "rounded-3xl bg-[var(--color-surface-muted)] p-4", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex min-h-32 overflow-hidden rounded-2xl bg-[var(--color-surface)] shadow-lg", children: [
975
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "flex w-28 shrink-0 items-center justify-center bg-[var(--color-primary)]/15 text-[var(--color-primary)]", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { "aria-hidden": "true", className: "h-10 w-10" }) }),
976
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col justify-center gap-1 p-4", children: [
977
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-xs font-medium uppercase tracking-wide text-[var(--color-text-muted)]", children: typeLabel }),
978
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "truncate text-base font-semibold text-[var(--color-text)]", title: target.name, children: target.name }),
979
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "truncate text-sm text-[var(--color-primary)]", title: url, children: url })
905
980
  ] })
906
981
  ] }) });
907
982
  }
908
983
 
909
984
  // src/genre-tree/SharePopup.tsx
910
- var import_jsx_runtime13 = require("react/jsx-runtime");
985
+ var import_jsx_runtime17 = require("react/jsx-runtime");
911
986
  function resolveAbsoluteUrl(url) {
912
987
  try {
913
988
  const parsed = new URL(url, window.location.href);
@@ -982,8 +1057,8 @@ function SharePopup({ target, url, onClose }) {
982
1057
  setShareError("Could not open the share options.");
983
1058
  }
984
1059
  };
985
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
986
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1060
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
1061
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
987
1062
  BasePopup_default,
988
1063
  {
989
1064
  title: "Share",
@@ -998,67 +1073,67 @@ function SharePopup({ target, url, onClose }) {
998
1073
  titleTextClassName: "!text-xl !font-semibold !text-[var(--color-text)]",
999
1074
  closeButtonClassName: "!h-10 !w-10 !rounded-none !border-0 !bg-transparent !p-0 !text-[var(--color-text)] hover:!bg-transparent hover:!text-[var(--color-text)]",
1000
1075
  contentClassName: "!rounded-none !bg-transparent !px-6 !pb-6 !pt-2",
1001
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex flex-col gap-4", children: [
1002
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SharePreviewCard, { target, url: absoluteUrl || url }),
1003
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "grid grid-cols-3 gap-1 rounded-3xl bg-[var(--color-surface)] p-3 sm:grid-cols-4", children: [
1004
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1076
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex flex-col gap-4", children: [
1077
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SharePreviewCard, { target, url: absoluteUrl || url }),
1078
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "grid grid-cols-3 gap-1 rounded-3xl bg-[var(--color-surface)] p-3 sm:grid-cols-4", children: [
1079
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1005
1080
  ShareDestinationButton,
1006
1081
  {
1007
1082
  label: "Copy Link",
1008
- icon: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react5.Link2, { "aria-hidden": "true", className: "h-6 w-6" }),
1083
+ icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.Link2, { "aria-hidden": "true", className: "h-6 w-6" }),
1009
1084
  onClick: () => void handleCopy(),
1010
1085
  ariaLabel: "Copy link",
1011
1086
  disabled: !absoluteUrl
1012
1087
  }
1013
1088
  ),
1014
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1089
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1015
1090
  ShareDestinationButton,
1016
1091
  {
1017
1092
  label: "WhatsApp",
1018
- icon: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_fa.FaWhatsapp, { "aria-hidden": "true", className: "h-9 w-9 text-[#25D366]" }),
1093
+ icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_fa.FaWhatsapp, { "aria-hidden": "true", className: "h-9 w-9 text-[#25D366]" }),
1019
1094
  href: destinationUrls?.whatsapp,
1020
1095
  ariaLabel: `Share ${target.name} via WhatsApp`,
1021
1096
  disabled: !absoluteUrl
1022
1097
  }
1023
1098
  ),
1024
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1099
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1025
1100
  ShareDestinationButton,
1026
1101
  {
1027
1102
  label: "Gmail",
1028
- icon: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_si.SiGmail, { "aria-hidden": "true", className: "h-8 w-8 text-[#EA4335]" }),
1103
+ icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_si.SiGmail, { "aria-hidden": "true", className: "h-8 w-8 text-[#EA4335]" }),
1029
1104
  href: destinationUrls?.gmail,
1030
1105
  ariaLabel: `Email ${target.name} with Gmail`,
1031
1106
  disabled: !absoluteUrl
1032
1107
  }
1033
1108
  ),
1034
- canUseNativeShare && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1109
+ canUseNativeShare && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1035
1110
  ShareDestinationButton,
1036
1111
  {
1037
1112
  label: "More",
1038
- icon: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react5.Share2, { "aria-hidden": "true", className: "h-6 w-6" }),
1113
+ icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.Share2, { "aria-hidden": "true", className: "h-6 w-6" }),
1039
1114
  onClick: () => void handleNativeShare(),
1040
1115
  ariaLabel: "More sharing options",
1041
1116
  disabled: !absoluteUrl
1042
1117
  }
1043
1118
  )
1044
1119
  ] }),
1045
- copyStatus === "failed" && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: "Could not copy the link." }),
1046
- shareError && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: shareError })
1120
+ copyStatus === "failed" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: "Could not copy the link." }),
1121
+ shareError && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: shareError })
1047
1122
  ] })
1048
1123
  }
1049
1124
  ),
1050
- toastId > 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Toast, { message: "Link copied", onClose: dismissToast }, toastId)
1125
+ toastId > 0 && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Toast, { message: "Link copied", onClose: dismissToast }, toastId)
1051
1126
  ] });
1052
1127
  }
1053
1128
 
1054
1129
  // src/genre-tree/ShareActionButton.tsx
1055
- var import_jsx_runtime14 = require("react/jsx-runtime");
1130
+ var import_jsx_runtime18 = require("react/jsx-runtime");
1056
1131
  function ShareActionButton({ target, url }) {
1057
1132
  const { showPopup, hidePopup } = (0, import_popup3.usePopup)();
1058
1133
  const openSharePopup = () => {
1059
- showPopup(/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SharePopup, { target, url, onClose: hidePopup }));
1134
+ showPopup(/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(SharePopup, { target, url, onClose: hidePopup }));
1060
1135
  };
1061
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
1136
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
1062
1137
  "button",
1063
1138
  {
1064
1139
  type: "button",
@@ -1067,7 +1142,7 @@ function ShareActionButton({ target, url }) {
1067
1142
  "aria-label": `Share ${target.name}`,
1068
1143
  title: `Share ${target.name}`,
1069
1144
  children: [
1070
- /* @__PURE__ */ (0, import_jsx_runtime14.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: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react6.Share2, { "aria-hidden": "true", className: "w-5 h-5" }) }),
1145
+ /* @__PURE__ */ (0, import_jsx_runtime18.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: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react6.Share2, { "aria-hidden": "true", className: "w-5 h-5" }) }),
1071
1146
  "Share"
1072
1147
  ]
1073
1148
  }
@@ -1075,7 +1150,7 @@ function ShareActionButton({ target, url }) {
1075
1150
  }
1076
1151
 
1077
1152
  // src/genre-tree/GenreActionBand.tsx
1078
- var import_jsx_runtime15 = require("react/jsx-runtime");
1153
+ var import_jsx_runtime19 = require("react/jsx-runtime");
1079
1154
  function GenreActionBand({
1080
1155
  node,
1081
1156
  onPlayPause,
@@ -1097,7 +1172,7 @@ function GenreActionBand({
1097
1172
  items.push({
1098
1173
  key: "play-pause",
1099
1174
  label: isPlaying ? "Pause" : "Play",
1100
- icon: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react7.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react7.Play, { "aria-hidden": "true", className: "w-5 h-5" }),
1175
+ icon: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react7.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react7.Play, { "aria-hidden": "true", className: "w-5 h-5" }),
1101
1176
  onClick: onPlayPause
1102
1177
  });
1103
1178
  }
@@ -1105,24 +1180,24 @@ function GenreActionBand({
1105
1180
  items.push({
1106
1181
  key: "zoom-in",
1107
1182
  label: "Focus",
1108
- icon: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react7.ZoomIn, { "aria-hidden": "true", className: "w-5 h-5" }),
1183
+ icon: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react7.ZoomIn, { "aria-hidden": "true", className: "w-5 h-5" }),
1109
1184
  onClick: onZoomIn
1110
1185
  });
1111
1186
  }
1112
1187
  if (onAddChild) {
1113
- adminItems.push({ key: "add-child", label: "Add", icon: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react7.Plus, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onAddChild });
1188
+ adminItems.push({ key: "add-child", label: "Add", icon: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react7.Plus, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onAddChild });
1114
1189
  }
1115
1190
  if (onRename) {
1116
- adminItems.push({ key: "rename", label: "Rename", icon: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react7.Pencil, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onRename });
1191
+ adminItems.push({ key: "rename", label: "Rename", icon: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react7.Pencil, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onRename });
1117
1192
  }
1118
1193
  if (onChangeParent) {
1119
- adminItems.push({ key: "change-parent", label: "Parent", icon: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react7.MoveUpRight, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onChangeParent });
1194
+ adminItems.push({ key: "change-parent", label: "Parent", icon: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react7.MoveUpRight, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onChangeParent });
1120
1195
  }
1121
1196
  if (onToggleSide) {
1122
1197
  adminItems.push({
1123
1198
  key: "toggle-side",
1124
1199
  label: side === "pop" ? "Make core" : "Make pop",
1125
- icon: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react7.ArrowLeftRight, { "aria-hidden": "true", className: "w-5 h-5" }),
1200
+ icon: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react7.ArrowLeftRight, { "aria-hidden": "true", className: "w-5 h-5" }),
1126
1201
  onClick: onToggleSide,
1127
1202
  disabled: !!toggleSideDisabledReason,
1128
1203
  title: toggleSideDisabledReason
@@ -1138,7 +1213,7 @@ function GenreActionBand({
1138
1213
  });
1139
1214
  }
1140
1215
  if (items.length === 0 && (!shareTarget || !shareUrl) && adminItems.length === 0) return null;
1141
- const renderItems = (list) => list.map((item) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
1216
+ const renderItems = (list) => list.map((item) => /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
1142
1217
  "button",
1143
1218
  {
1144
1219
  type: "button",
@@ -1147,25 +1222,25 @@ function GenreActionBand({
1147
1222
  onClick: item.onClick,
1148
1223
  className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none disabled:opacity-50",
1149
1224
  children: [
1150
- /* @__PURE__ */ (0, import_jsx_runtime15.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)] group-disabled:group-hover:bg-[var(--color-surface-muted)]", children: item.icon }),
1225
+ /* @__PURE__ */ (0, import_jsx_runtime19.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)] group-disabled:group-hover:bg-[var(--color-surface-muted)]", children: item.icon }),
1151
1226
  item.label
1152
1227
  ]
1153
1228
  },
1154
1229
  item.key
1155
1230
  ));
1156
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: [
1157
- (items.length > 0 || shareTarget && shareUrl) && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
1231
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: [
1232
+ (items.length > 0 || shareTarget && shareUrl) && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
1158
1233
  renderItems(items),
1159
- shareTarget && shareUrl && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
1234
+ shareTarget && shareUrl && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
1160
1235
  ] }),
1161
- adminItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
1236
+ adminItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
1162
1237
  "section",
1163
1238
  {
1164
1239
  "aria-label": "Admin actions",
1165
1240
  className: `px-3.5 py-3 ${items.length > 0 ? "border-t border-[var(--color-border)]" : ""}`,
1166
1241
  children: [
1167
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("h3", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-[var(--color-text-muted)]", children: "Admin" }),
1168
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "flex flex-wrap gap-1", children: renderItems(adminItems) })
1242
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("h3", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-[var(--color-text-muted)]", children: "Admin" }),
1243
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "flex flex-wrap gap-1", children: renderItems(adminItems) })
1169
1244
  ]
1170
1245
  }
1171
1246
  )
@@ -1173,12 +1248,12 @@ function GenreActionBand({
1173
1248
  }
1174
1249
 
1175
1250
  // src/genre-tree/GenreDetailExtras.tsx
1176
- var import_ui5 = require("@behindthemusictree/ui");
1177
- var import_genre_tree9 = require("@behindthemusictree/app-kit/genre-tree");
1251
+ var import_ui6 = require("@behindthemusictree/ui");
1252
+ var import_genre_tree12 = require("@behindthemusictree/app-kit/genre-tree");
1178
1253
 
1179
1254
  // src/genre-tree/ClampedText.tsx
1180
1255
  var import_react10 = require("react");
1181
- var import_jsx_runtime16 = require("react/jsx-runtime");
1256
+ var import_jsx_runtime20 = require("react/jsx-runtime");
1182
1257
  function ClampedText({ children, className: className2 = "" }) {
1183
1258
  const ref = (0, import_react10.useRef)(null);
1184
1259
  const [expanded, setExpanded] = (0, import_react10.useState)(false);
@@ -1188,9 +1263,9 @@ function ClampedText({ children, className: className2 = "" }) {
1188
1263
  if (!element || expanded) return;
1189
1264
  setOverflows(element.scrollHeight > element.clientHeight);
1190
1265
  }, [children, expanded]);
1191
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
1192
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { ref, className: `${className2} ${expanded ? "" : "line-clamp-3"}`, children }),
1193
- (overflows || expanded) && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1266
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
1267
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { ref, className: `${className2} ${expanded ? "" : "line-clamp-3"}`, children }),
1268
+ (overflows || expanded) && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
1194
1269
  "button",
1195
1270
  {
1196
1271
  type: "button",
@@ -1208,7 +1283,7 @@ var import_react11 = require("react");
1208
1283
  var import_react_query = require("@tanstack/react-query");
1209
1284
  var import_zod = require("zod");
1210
1285
  var import_fa2 = require("react-icons/fa");
1211
- var import_ui4 = require("@behindthemusictree/ui");
1286
+ var import_ui5 = require("@behindthemusictree/ui");
1212
1287
  var import_auth = require("@behindthemusictree/app-kit/auth");
1213
1288
  var import_transport3 = require("@behindthemusictree/app-kit/transport");
1214
1289
  var import_transport4 = require("@behindthemusictree/app-kit/transport");
@@ -1217,22 +1292,22 @@ var import_player4 = require("@behindthemusictree/app-kit/player");
1217
1292
  var import_player5 = require("@behindthemusictree/app-kit/player");
1218
1293
  var import_popup4 = require("@behindthemusictree/app-kit/popup");
1219
1294
  var import_transport6 = require("@behindthemusictree/app-kit/transport");
1220
- var import_genre_tree4 = require("@behindthemusictree/app-kit/genre-tree");
1221
- var import_genre_tree5 = require("@behindthemusictree/app-kit/genre-tree");
1222
- var import_genre_tree6 = require("@behindthemusictree/app-kit/genre-tree");
1223
1295
  var import_genre_tree7 = require("@behindthemusictree/app-kit/genre-tree");
1296
+ var import_genre_tree8 = require("@behindthemusictree/app-kit/genre-tree");
1297
+ var import_genre_tree9 = require("@behindthemusictree/app-kit/genre-tree");
1298
+ var import_genre_tree10 = require("@behindthemusictree/app-kit/genre-tree");
1224
1299
 
1225
1300
  // src/genre-tree/MusicBrainzRecordingLink.tsx
1226
- var import_genre_tree3 = require("@behindthemusictree/app-kit/genre-tree");
1227
- var import_jsx_runtime17 = require("react/jsx-runtime");
1301
+ var import_genre_tree6 = require("@behindthemusictree/app-kit/genre-tree");
1302
+ var import_jsx_runtime21 = require("react/jsx-runtime");
1228
1303
  function MusicBrainzRecordingLink({
1229
1304
  song,
1230
1305
  children,
1231
1306
  className: className2 = ""
1232
1307
  }) {
1233
- const url = (0, import_genre_tree3.musicBrainzRecordingUrl)(song);
1234
- if (!url) return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_jsx_runtime17.Fragment, { children });
1235
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1308
+ const url = (0, import_genre_tree6.musicBrainzRecordingUrl)(song);
1309
+ if (!url) return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_jsx_runtime21.Fragment, { children });
1310
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1236
1311
  "a",
1237
1312
  {
1238
1313
  href: url,
@@ -1247,11 +1322,11 @@ function MusicBrainzRecordingLink({
1247
1322
  }
1248
1323
 
1249
1324
  // src/genre-tree/GenrePlaylistTracks.tsx
1250
- var import_genre_tree8 = require("@behindthemusictree/app-kit/genre-tree");
1325
+ var import_genre_tree11 = require("@behindthemusictree/app-kit/genre-tree");
1251
1326
  var import_track_noun = require("@behindthemusictree/app-kit/track-noun");
1252
- var import_jsx_runtime18 = require("react/jsx-runtime");
1327
+ var import_jsx_runtime22 = require("react/jsx-runtime");
1253
1328
  var CATALOGUE_PAGE_SIZE = 100;
1254
- var SongPageSchema = (0, import_transport4.PaginatedResponseSchema)(import_genre_tree6.SongDetailedSchema).extend({
1329
+ var SongPageSchema = (0, import_transport4.PaginatedResponseSchema)(import_genre_tree9.SongDetailedSchema).extend({
1255
1330
  overallTotal: import_zod.z.number().nullable(),
1256
1331
  page: import_zod.z.number().nullable(),
1257
1332
  totalPages: import_zod.z.number().nullable()
@@ -1278,7 +1353,7 @@ function GenrePlaylistTracks({
1278
1353
  renderTracksToolbar,
1279
1354
  artist = null
1280
1355
  }) {
1281
- const { fetchGenrePlaylistTracksPage, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist } = (0, import_genre_tree7.useTrackList)();
1356
+ const { fetchGenrePlaylistTracksPage, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist } = (0, import_genre_tree10.useTrackList)();
1282
1357
  const { session, sessionRestored } = (0, import_auth.useSession)();
1283
1358
  const { playerTrackObject, playState, handlePlayPauseAction } = (0, import_player4.usePlayer)();
1284
1359
  const { showPopup } = (0, import_popup4.usePopup)();
@@ -1287,11 +1362,11 @@ function GenrePlaylistTracks({
1287
1362
  const { fetch } = (0, import_transport3.useFetchWrapper)(getBackendBaseUrl);
1288
1363
  const playback = (0, import_transport3.usePlaybackContext)();
1289
1364
  const { data, isPending, isError, refetch, hasNextPage, isFetchingNextPage, fetchNextPage } = (0, import_react_query.useInfiniteQuery)({
1290
- queryKey: [...scope === "me" ? import_genre_tree4.genrePlaylistQueryKeys.me.tracks(uuid) : artist ? [...import_genre_tree5.libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid), "artist", artist.uuid] : import_genre_tree5.libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid), ...playback.key ? [playback.key] : []],
1365
+ queryKey: [...scope === "me" ? import_genre_tree7.genrePlaylistQueryKeys.me.tracks(uuid) : artist ? [...import_genre_tree8.libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid), "artist", artist.uuid] : import_genre_tree8.libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid), ...playback.key ? [playback.key] : []],
1291
1366
  queryFn: async ({ pageParam }) => {
1292
1367
  if (scope === "me") return fetchGenrePlaylistTracksPage(uuid, scope, Number(pageParam));
1293
1368
  const response = await fetch(
1294
- import_genre_tree5.libraryEndpoints.reference.songs.list(),
1369
+ import_genre_tree8.libraryEndpoints.reference.songs.list(),
1295
1370
  true,
1296
1371
  false,
1297
1372
  {},
@@ -1322,35 +1397,35 @@ function GenrePlaylistTracks({
1322
1397
  const started = artist ? playNewTrackListFromArtist(artist, scope, { startSongUuid, ordering: "title", genreUuid }) : playNewTrackListFromGenrePlaylist(genrePlaylist, scope, { startSongUuid });
1323
1398
  started.catch((error) => {
1324
1399
  console.error("Failed to play genre playlist:", error);
1325
- showPopup(/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(InternalErrorPopup, { errorCode: import_transport6.ErrorCode.CLIENT_UNKNOWN }));
1400
+ showPopup(/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(InternalErrorPopup, { errorCode: import_transport6.ErrorCode.CLIENT_UNKNOWN }));
1326
1401
  });
1327
1402
  };
1328
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_ui4.PanelSection, { title: `${(0, import_track_noun.capitalize)(trackNoun.plural)}${total !== void 0 ? ` (${total})` : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
1403
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ui5.PanelSection, { title: `${(0, import_track_noun.capitalize)(trackNoun.plural)}${total !== void 0 ? ` (${total})` : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { children: [
1329
1404
  renderTracksToolbar?.(),
1330
- isPending ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_ui4.Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("p", { role: "alert", children: [
1405
+ isPending ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ui5.Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("p", { role: "alert", children: [
1331
1406
  "Couldn't load ",
1332
1407
  trackNoun.plural,
1333
1408
  ".",
1334
1409
  " ",
1335
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
1336
- ] }) : tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { children: "\u2014" }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("ul", { className: "mt-1 max-h-64 overflow-y-auto text-[0.8125rem] leading-[1.45] [container-type:inline-size]", "aria-label": `${genrePlaylist.name} ${trackNoun.plural}`, children: [
1410
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
1411
+ ] }) : tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { children: "\u2014" }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("ul", { className: "mt-1 max-h-64 overflow-y-auto text-[0.8125rem] leading-[1.45] [container-type:inline-size]", "aria-label": `${genrePlaylist.name} ${trackNoun.plural}`, children: [
1337
1412
  tracks.map((track, index) => {
1338
1413
  const label = track.artists?.length ? `${track.title} \u2014 ${track.artists.map((artist2) => artist2.name).join(", ")}` : track.title;
1339
1414
  const isCurrent = playerTrackObject?.track.id === track.uuid;
1340
1415
  const isPlaying = isCurrent && playState === import_player5.PlayStates.PLAYING;
1341
- const text = /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: TEXT_CELL, children: [
1342
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: TITLE_CELL, children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree6.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
1343
- track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: SEPARATOR, children: " \u2014 " }) : null,
1344
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_genre_tree8.ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
1345
- track.album && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: ALBUM_CELL, children: track.album.name })
1416
+ const text = /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("span", { className: TEXT_CELL, children: [
1417
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: TITLE_CELL, children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree9.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
1418
+ track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: SEPARATOR, children: " \u2014 " }) : null,
1419
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_genre_tree11.ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
1420
+ track.album && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: ALBUM_CELL, children: track.album.name })
1346
1421
  ] });
1347
1422
  const leading = renderSongLeading?.(track, index);
1348
1423
  const actions = renderSongActions?.(track);
1349
- const isUnplayable = (0, import_genre_tree6.isSong)(track) && (0, import_genre_tree6.songUnplayableReason)(track) !== null;
1424
+ const isUnplayable = (0, import_genre_tree9.isSong)(track) && (0, import_genre_tree9.songUnplayableReason)(track) !== null;
1350
1425
  if (isUnplayable) {
1351
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("li", { className: "flex min-h-8 items-center gap-1 px-1 opacity-50", children: [
1426
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("li", { className: "flex min-h-8 items-center gap-1 px-1 opacity-50", children: [
1352
1427
  leading,
1353
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1428
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1354
1429
  "button",
1355
1430
  {
1356
1431
  type: "button",
@@ -1358,27 +1433,27 @@ function GenrePlaylistTracks({
1358
1433
  "aria-disabled": "true",
1359
1434
  "aria-label": label,
1360
1435
  className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full opacity-0 cursor-not-allowed",
1361
- children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_fa2.FaPlay, { size: 10 })
1436
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_fa2.FaPlay, { size: 10 })
1362
1437
  }
1363
1438
  ),
1364
1439
  text,
1365
1440
  actions
1366
1441
  ] }, track.uuid);
1367
1442
  }
1368
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
1443
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
1369
1444
  "li",
1370
1445
  {
1371
1446
  className: `group flex min-h-8 items-center gap-1 rounded px-1 hover:bg-[var(--color-neutral-100)] ${isCurrent ? "font-medium text-[var(--color-primary)]" : ""}`,
1372
1447
  children: [
1373
1448
  leading,
1374
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1449
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1375
1450
  "button",
1376
1451
  {
1377
1452
  type: "button",
1378
1453
  "aria-label": `${isPlaying ? "Pause" : "Play"} ${label}`,
1379
1454
  className: `flex h-6 w-6 shrink-0 items-center justify-center rounded-full cursor-pointer group-hover:opacity-100 focus-visible:opacity-100 ${isCurrent && playState !== import_player5.PlayStates.STOPPED ? "opacity-100" : "opacity-0 [@media(pointer:coarse)]:opacity-100"}`,
1380
1455
  onClick: () => isCurrent ? handlePlayPauseAction() : play(track.uuid),
1381
- children: isCurrent && playState === import_player5.PlayStates.LOADING ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_ui4.RingLoader, { size: 10 }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_fa2.FaPause, { size: 10 }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_fa2.FaPlay, { size: 10 })
1456
+ children: isCurrent && playState === import_player5.PlayStates.LOADING ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ui5.RingLoader, { size: 10 }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_fa2.FaPause, { size: 10 }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_fa2.FaPlay, { size: 10 })
1382
1457
  }
1383
1458
  ),
1384
1459
  text,
@@ -1388,7 +1463,7 @@ function GenrePlaylistTracks({
1388
1463
  track.uuid
1389
1464
  );
1390
1465
  }),
1391
- hasNextPage && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("li", { ref: setSentinel, "aria-hidden": "true", className: "h-4" })
1466
+ hasNextPage && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("li", { ref: setSentinel, "aria-hidden": "true", className: "h-4" })
1392
1467
  ] })
1393
1468
  ] }) });
1394
1469
  }
@@ -1396,7 +1471,7 @@ function GenrePlaylistTracks({
1396
1471
  // src/genre-tree/GenreDetailExtras.tsx
1397
1472
  var import_track_noun2 = require("@behindthemusictree/app-kit/track-noun");
1398
1473
  var import_si2 = require("react-icons/si");
1399
- var import_jsx_runtime19 = require("react/jsx-runtime");
1474
+ var import_jsx_runtime23 = require("react/jsx-runtime");
1400
1475
  function GenreDetailExtras({
1401
1476
  genreUuid,
1402
1477
  genrePlaylist,
@@ -1413,24 +1488,24 @@ function GenreDetailExtras({
1413
1488
  ...trackSlots
1414
1489
  }) {
1415
1490
  const trackNoun = (0, import_track_noun2.useTrackNoun)();
1416
- const { data: overview, isPending } = (0, import_genre_tree9.useFetchGenreOverview)(
1491
+ const { data: overview, isPending } = (0, import_genre_tree12.useFetchGenreOverview)(
1417
1492
  genreUuid,
1418
1493
  scope,
1419
1494
  getBackendBaseUrl,
1420
1495
  criteriaOverviewSchema
1421
1496
  );
1422
1497
  if (isPending) {
1423
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
1424
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_ui5.PanelSection, { title: "Summary", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { "aria-busy": "true", "data-testid": "genre-detail-extras-skeleton", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_ui5.Skeleton, { className: "h-4 w-3/4" }) }) }),
1498
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
1499
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ui6.PanelSection, { title: "Summary", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { "aria-busy": "true", "data-testid": "genre-detail-extras-skeleton", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ui6.Skeleton, { className: "h-4 w-3/4" }) }) }),
1425
1500
  family
1426
1501
  ] });
1427
1502
  }
1428
- if (!overview) return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_jsx_runtime19.Fragment, { children: family });
1503
+ if (!overview) return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_jsx_runtime23.Fragment, { children: family });
1429
1504
  const { summary, summarySourceUrl, essentialSongs } = overview;
1430
1505
  const wikidataUrl = wikidataId ? `https://www.wikidata.org/wiki/${encodeURIComponent(wikidataId)}` : null;
1431
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
1432
- (summarySourceUrl || wikidataUrl) && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_ui5.PanelSection, { title: "Links", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex flex-wrap gap-x-3 gap-y-1 text-xs text-[var(--color-text)]", children: [
1433
- summarySourceUrl && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
1506
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
1507
+ (summarySourceUrl || wikidataUrl) && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ui6.PanelSection, { title: "Links", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex flex-wrap gap-x-3 gap-y-1 text-xs text-[var(--color-text)]", children: [
1508
+ summarySourceUrl && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
1434
1509
  "a",
1435
1510
  {
1436
1511
  href: summarySourceUrl,
@@ -1438,12 +1513,12 @@ function GenreDetailExtras({
1438
1513
  rel: "noopener noreferrer",
1439
1514
  className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
1440
1515
  children: [
1441
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_si2.SiWikipedia, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1516
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_si2.SiWikipedia, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1442
1517
  "Wikipedia"
1443
1518
  ]
1444
1519
  }
1445
1520
  ),
1446
- wikidataUrl && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
1521
+ wikidataUrl && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
1447
1522
  "a",
1448
1523
  {
1449
1524
  href: wikidataUrl,
@@ -1451,17 +1526,17 @@ function GenreDetailExtras({
1451
1526
  rel: "noopener noreferrer",
1452
1527
  className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
1453
1528
  children: [
1454
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_si2.SiWikidata, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1529
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_si2.SiWikidata, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1455
1530
  "Wikidata"
1456
1531
  ]
1457
1532
  }
1458
1533
  )
1459
1534
  ] }) }),
1460
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_ui5.PanelSection, { title: "Summary", children: renderSummary ? renderSummary(overview) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ClampedText, { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: summary ?? "\u2014" }, genreUuid) }),
1535
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ui6.PanelSection, { title: "Summary", children: renderSummary ? renderSummary(overview) : /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ClampedText, { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: summary ?? "\u2014" }, genreUuid) }),
1461
1536
  family,
1462
1537
  renderGenreDetailExtras?.(overview),
1463
- (renderEssentialTracks || essentialSongs) && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_ui5.PanelSection, { title: `Essential ${trackNoun.plural}`, children: renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && essentialSongs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("ul", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: essentialSongs.map((track) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("li", { className: "py-1", children: track.title }, track.uuid)) }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: "\u2014" }) }),
1464
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1538
+ (renderEssentialTracks || essentialSongs) && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ui6.PanelSection, { title: `Essential ${trackNoun.plural}`, children: renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && essentialSongs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("ul", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: essentialSongs.map((track) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("li", { className: "py-1", children: track.title }, track.uuid)) }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: "\u2014" }) }),
1539
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1465
1540
  GenrePlaylistTracks,
1466
1541
  {
1467
1542
  genreUuid,
@@ -1477,7 +1552,7 @@ function GenreDetailExtras({
1477
1552
  }
1478
1553
 
1479
1554
  // src/genre-tree/GenreInfoPanelBody.tsx
1480
- var import_jsx_runtime20 = require("react/jsx-runtime");
1555
+ var import_jsx_runtime24 = require("react/jsx-runtime");
1481
1556
  var import_react12 = require("react");
1482
1557
  function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
1483
1558
  const { isPlaying, setIsPlaying } = (0, import_player6.usePlayer)();
@@ -1491,7 +1566,7 @@ function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
1491
1566
  console.error("Failed to load genre playlist tracks:", error);
1492
1567
  });
1493
1568
  };
1494
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
1569
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1495
1570
  GenreActionBand,
1496
1571
  {
1497
1572
  ...bandProps,
@@ -1512,9 +1587,9 @@ function GenreInfoPanelBody({
1512
1587
  band,
1513
1588
  extras
1514
1589
  }) {
1515
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
1590
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
1516
1591
  toolbar,
1517
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(GenreNodeActionBand, { ...band, genrePlaylist, playback }),
1592
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(GenreNodeActionBand, { ...band, genrePlaylist, playback }),
1518
1593
  /* @__PURE__ */ (0, import_react12.createElement)(
1519
1594
  GenreDetailExtras,
1520
1595
  {
@@ -1533,8 +1608,8 @@ function GenreInfoPanelBody({
1533
1608
 
1534
1609
  // src/genre-tree/ChangeParentPopup.tsx
1535
1610
  var import_react13 = require("react");
1536
- var import_genre_tree10 = require("@behindthemusictree/app-kit/genre-tree");
1537
- var import_jsx_runtime21 = require("react/jsx-runtime");
1611
+ var import_genre_tree13 = require("@behindthemusictree/app-kit/genre-tree");
1612
+ var import_jsx_runtime25 = require("react/jsx-runtime");
1538
1613
  function ChangeParentPopup({
1539
1614
  genrePlaylist,
1540
1615
  genrePlaylists,
@@ -1543,9 +1618,9 @@ function ChangeParentPopup({
1543
1618
  onClose,
1544
1619
  onParentChanged
1545
1620
  }) {
1546
- const { mutate, isPending, formErrors } = (0, import_genre_tree10.useUpdateGenre)(scope, getBackendBaseUrl);
1621
+ const { mutate, isPending, formErrors } = (0, import_genre_tree13.useUpdateGenre)(scope, getBackendBaseUrl);
1547
1622
  const candidates = (0, import_react13.useMemo)(
1548
- () => (0, import_genre_tree10.getReparentCandidates)(genrePlaylists, genrePlaylist.uuid),
1623
+ () => (0, import_genre_tree13.getReparentCandidates)(genrePlaylists, genrePlaylist.uuid),
1549
1624
  [genrePlaylists, genrePlaylist.uuid]
1550
1625
  );
1551
1626
  const handleSelect = (newParent) => {
@@ -1569,12 +1644,12 @@ function ChangeParentPopup({
1569
1644
  window.addEventListener("keydown", handleKeyDown);
1570
1645
  return () => window.removeEventListener("keydown", handleKeyDown);
1571
1646
  }, [onClose]);
1572
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
1573
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("p", { className: "mb-3 text-sm text-[var(--color-text-muted)]", children: [
1647
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
1648
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("p", { className: "mb-3 text-sm text-[var(--color-text-muted)]", children: [
1574
1649
  "Current parent: ",
1575
1650
  genrePlaylist.parent?.name ?? "none"
1576
1651
  ] }),
1577
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1652
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1578
1653
  GenreSearch,
1579
1654
  {
1580
1655
  genrePlaylists: candidates,
@@ -1582,22 +1657,22 @@ function ChangeParentPopup({
1582
1657
  placeholder: "Search a new parent\u2026"
1583
1658
  }
1584
1659
  ) }),
1585
- formErrors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { role: "alert", className: "mt-3 text-sm text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
1660
+ formErrors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("p", { role: "alert", className: "mt-3 text-sm text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
1586
1661
  ] });
1587
1662
  }
1588
1663
 
1589
1664
  // src/genre-tree/artist/useArtistPanel.tsx
1590
- var import_ui11 = require("@behindthemusictree/ui");
1591
- var import_genre_tree15 = require("@behindthemusictree/app-kit/genre-tree");
1665
+ var import_ui12 = require("@behindthemusictree/ui");
1666
+ var import_genre_tree18 = require("@behindthemusictree/app-kit/genre-tree");
1592
1667
 
1593
1668
  // src/genre-tree/artist/ArtistHeader.tsx
1594
- var import_ui7 = require("@behindthemusictree/ui");
1669
+ var import_ui8 = require("@behindthemusictree/ui");
1595
1670
 
1596
1671
  // src/genre-tree/artist/ArtistLinks.tsx
1597
1672
  var import_lucide_react8 = require("lucide-react");
1598
1673
  var import_si3 = require("react-icons/si");
1599
- var import_ui6 = require("@behindthemusictree/ui");
1600
- var import_jsx_runtime22 = require("react/jsx-runtime");
1674
+ var import_ui7 = require("@behindthemusictree/ui");
1675
+ var import_jsx_runtime26 = require("react/jsx-runtime");
1601
1676
  var LINK_KINDS = {
1602
1677
  official: { label: "Official website", Icon: import_lucide_react8.Globe },
1603
1678
  wikipedia: { label: "Wikipedia", Icon: import_si3.SiWikipedia },
@@ -1634,7 +1709,7 @@ function artistLinks(artist) {
1634
1709
  return links;
1635
1710
  }
1636
1711
  function ArtistLinks({ links }) {
1637
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ui6.Chip, { href: url, icon: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
1712
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_ui7.Chip, { href: url, icon: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
1638
1713
  }
1639
1714
 
1640
1715
  // src/genre-tree/artist/formatYears.ts
@@ -1645,7 +1720,7 @@ function formatYears(beginYear, endYear, ended) {
1645
1720
  }
1646
1721
 
1647
1722
  // src/genre-tree/artist/ArtistHeader.tsx
1648
- var import_jsx_runtime23 = require("react/jsx-runtime");
1723
+ var import_jsx_runtime27 = require("react/jsx-runtime");
1649
1724
  var countryNames = new Intl.DisplayNames(["en"], { type: "region" });
1650
1725
  function ArtistHeader({ artist }) {
1651
1726
  const fields = [];
@@ -1655,35 +1730,35 @@ function ArtistHeader({ artist }) {
1655
1730
  const years = formatYears(artist.beginYear, artist.endYear, artist.ended);
1656
1731
  if (years) fields.push({ label: "Active", value: years });
1657
1732
  const links = artistLinks(artist);
1658
- if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ArtistLinks, { links }) });
1733
+ if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ArtistLinks, { links }) });
1659
1734
  if (fields.length === 0) return null;
1660
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ui7.FieldList, { items: fields });
1735
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_ui8.FieldList, { items: fields });
1661
1736
  }
1662
1737
 
1663
1738
  // src/genre-tree/artist/ArtistPhoto.tsx
1664
- var import_jsx_runtime24 = require("react/jsx-runtime");
1739
+ var import_jsx_runtime28 = require("react/jsx-runtime");
1665
1740
  function ExternalLink({ href, children }) {
1666
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
1741
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
1667
1742
  }
1668
1743
  function ArtistPhoto({ image, name }) {
1669
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
1670
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
1671
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("figcaption", { className: "text-xs text-[var(--color-text-muted)]", children: [
1744
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
1745
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
1746
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("figcaption", { className: "text-xs text-[var(--color-text-muted)]", children: [
1672
1747
  "Photo: ",
1673
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
1748
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
1674
1749
  ",",
1675
1750
  " ",
1676
- image.licenseUrl ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
1751
+ image.licenseUrl ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
1677
1752
  ] })
1678
1753
  ] });
1679
1754
  }
1680
1755
 
1681
1756
  // src/genre-tree/artist/ArtistMembers.tsx
1682
- var import_ui8 = require("@behindthemusictree/ui");
1683
- var import_jsx_runtime25 = require("react/jsx-runtime");
1757
+ var import_ui9 = require("@behindthemusictree/ui");
1758
+ var import_jsx_runtime29 = require("react/jsx-runtime");
1684
1759
  function MembershipList({ title, memberships, renderArtistLink }) {
1685
1760
  if (memberships.length === 0) return null;
1686
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_ui8.PanelGroup, { title, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("ul", { children: memberships.map((membership, index) => {
1761
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui9.PanelGroup, { title, children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("ul", { children: memberships.map((membership, index) => {
1687
1762
  const details = [
1688
1763
  formatYears(membership.beginYear, membership.endYear, membership.ended),
1689
1764
  ...membership.roles
@@ -1691,9 +1766,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
1691
1766
  const detailsText = details.filter(Boolean).join(" \xB7 ");
1692
1767
  return (
1693
1768
  // A member can have several periods in the same group.
1694
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
1695
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
1696
- detailsText && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
1769
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
1770
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
1771
+ detailsText && /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
1697
1772
  " \u2014 ",
1698
1773
  detailsText
1699
1774
  ] })
@@ -1704,9 +1779,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
1704
1779
  function ArtistMembers({ title, memberships, renderArtistLink }) {
1705
1780
  if (memberships.length === 0) return null;
1706
1781
  const isCurrent = (membership) => !membership.ended && membership.endYear === null;
1707
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_ui8.PanelSection, { title, children: [
1708
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
1709
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1782
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(import_ui9.PanelSection, { title, children: [
1783
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
1784
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
1710
1785
  MembershipList,
1711
1786
  {
1712
1787
  title: "Past",
@@ -1718,8 +1793,8 @@ function ArtistMembers({ title, memberships, renderArtistLink }) {
1718
1793
  }
1719
1794
 
1720
1795
  // src/genre-tree/artist/ArtistDiscography.tsx
1721
- var import_ui9 = require("@behindthemusictree/ui");
1722
- var import_jsx_runtime26 = require("react/jsx-runtime");
1796
+ var import_ui10 = require("@behindthemusictree/ui");
1797
+ var import_jsx_runtime30 = require("react/jsx-runtime");
1723
1798
  var GROUPS = ["Albums", "EPs", "Singles", "Live", "Compilations", "Other"];
1724
1799
  var PRIMARY_GROUPS = { album: "Albums", ep: "EPs", single: "Singles" };
1725
1800
  function discographyGroup({ primaryType, secondaryTypes }) {
@@ -1739,12 +1814,12 @@ function byYear(a, b) {
1739
1814
  function ArtistDiscography({ albums }) {
1740
1815
  if (albums.length === 0) return null;
1741
1816
  const sorted = [...albums].sort(byYear);
1742
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_ui9.PanelSection, { title: "Discography", children: GROUPS.map((group) => {
1817
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui10.PanelSection, { title: "Discography", children: GROUPS.map((group) => {
1743
1818
  const groupAlbums = sorted.filter((album) => discographyGroup(album) === group);
1744
1819
  if (groupAlbums.length === 0) return null;
1745
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_ui9.PanelGroup, { title: group, children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
1746
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
1747
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
1820
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui10.PanelGroup, { title: group, children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
1821
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
1822
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
1748
1823
  ] }, album.uuid)) }) }, group);
1749
1824
  }) });
1750
1825
  }
@@ -1753,20 +1828,20 @@ function ArtistDiscography({ albums }) {
1753
1828
  var import_react14 = require("react");
1754
1829
  var import_react_query2 = require("@tanstack/react-query");
1755
1830
  var import_lucide_react9 = require("lucide-react");
1756
- var import_ui10 = require("@behindthemusictree/ui");
1831
+ var import_ui11 = require("@behindthemusictree/ui");
1757
1832
  var import_player10 = require("@behindthemusictree/app-kit/player");
1758
- var import_genre_tree14 = require("@behindthemusictree/app-kit/genre-tree");
1833
+ var import_genre_tree17 = require("@behindthemusictree/app-kit/genre-tree");
1759
1834
  var import_transport7 = require("@behindthemusictree/app-kit/transport");
1760
1835
 
1761
1836
  // src/genre-tree/track-list-sidebar/TrackItem.tsx
1762
1837
  var import_player9 = require("@behindthemusictree/app-kit/player");
1763
- var import_genre_tree11 = require("@behindthemusictree/app-kit/genre-tree");
1838
+ var import_genre_tree14 = require("@behindthemusictree/app-kit/genre-tree");
1764
1839
 
1765
1840
  // src/genre-tree/TrackPositionPlayPause.tsx
1766
1841
  var import_fa3 = require("react-icons/fa");
1767
1842
  var import_player7 = require("@behindthemusictree/app-kit/player");
1768
1843
  var import_player8 = require("@behindthemusictree/app-kit/player");
1769
- var import_jsx_runtime27 = require("react/jsx-runtime");
1844
+ var import_jsx_runtime31 = require("react/jsx-runtime");
1770
1845
  function TrackPositionPlayPause({
1771
1846
  position,
1772
1847
  uuid,
@@ -1780,14 +1855,14 @@ function TrackPositionPlayPause({
1780
1855
  const isPlaying = isThisTrack && isConfirmedPlayable && playState === import_player8.PlayStates.PLAYING;
1781
1856
  const isPaused = isThisTrack && isConfirmedPlayable && playState === import_player8.PlayStates.PAUSED;
1782
1857
  const isCurrentTrackActive = isPlaying || isPaused;
1783
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
1858
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
1784
1859
  "div",
1785
1860
  {
1786
1861
  className: "track-position-play-pause relative flex items-center justify-center gap-2 text-lg w-16 cursor-pointer",
1787
1862
  style: { minWidth: "4rem", maxWidth: "4rem" },
1788
1863
  onClick: handlePlayPauseClick,
1789
1864
  children: [
1790
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
1865
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
1791
1866
  "span",
1792
1867
  {
1793
1868
  className: "track-position-play-pause__bars",
@@ -1795,18 +1870,18 @@ function TrackPositionPlayPause({
1795
1870
  role: "img",
1796
1871
  "aria-label": isPlaying ? "Playing" : "Paused",
1797
1872
  children: [
1798
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "track-position-play-pause__bar" }),
1799
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "track-position-play-pause__bar" }),
1800
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "track-position-play-pause__bar" })
1873
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "track-position-play-pause__bar" }),
1874
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "track-position-play-pause__bar" }),
1875
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "track-position-play-pause__bar" })
1801
1876
  ]
1802
1877
  }
1803
1878
  ) : position }),
1804
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1879
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
1805
1880
  "div",
1806
1881
  {
1807
1882
  className: `hidden group-hover:flex items-center justify-center ${isCurrentTrackActive ? "" : "absolute inset-0"}`,
1808
1883
  "aria-hidden": "true",
1809
- children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_fa3.FaPause, {}) : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_fa3.FaPlay, {})
1884
+ children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_fa3.FaPause, {}) : /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_fa3.FaPlay, {})
1810
1885
  }
1811
1886
  )
1812
1887
  ]
@@ -1815,9 +1890,9 @@ function TrackPositionPlayPause({
1815
1890
  }
1816
1891
 
1817
1892
  // src/genre-tree/track-list-sidebar/TrackItem.tsx
1818
- var import_genre_tree12 = require("@behindthemusictree/app-kit/genre-tree");
1819
- var import_genre_tree13 = require("@behindthemusictree/app-kit/genre-tree");
1820
- var import_jsx_runtime28 = require("react/jsx-runtime");
1893
+ var import_genre_tree15 = require("@behindthemusictree/app-kit/genre-tree");
1894
+ var import_genre_tree16 = require("@behindthemusictree/app-kit/genre-tree");
1895
+ var import_jsx_runtime32 = require("react/jsx-runtime");
1821
1896
  function TrackItem({
1822
1897
  track,
1823
1898
  position,
@@ -1827,11 +1902,11 @@ function TrackItem({
1827
1902
  renderSongLink
1828
1903
  }) {
1829
1904
  const { handlePlayPauseAction, playerTrackObject, loadTrackForPlayer } = (0, import_player9.usePlayer)();
1830
- const { trackList, selectedTrack, toTrackAtPosition, runtimeUnplayableTrackIds } = (0, import_genre_tree11.useTrackList)();
1905
+ const { trackList, selectedTrack, toTrackAtPosition, runtimeUnplayableTrackIds } = (0, import_genre_tree14.useTrackList)();
1831
1906
  const scope = trackList?.origin?.scope ?? null;
1832
1907
  const isCurrentTrack = playerTrackObject?.track.id === track.uuid;
1833
1908
  const hasPlaybackError = isCurrentTrack && !!playerTrackObject?.loadError;
1834
- const isUnplayable = hasPlaybackError || runtimeUnplayableTrackIds?.has(track.uuid) === true || (0, import_genre_tree12.isSong)(track) && (0, import_genre_tree12.songUnplayableReason)(track) !== null;
1909
+ const isUnplayable = hasPlaybackError || runtimeUnplayableTrackIds?.has(track.uuid) === true || (0, import_genre_tree15.isSong)(track) && (0, import_genre_tree15.songUnplayableReason)(track) !== null;
1835
1910
  const handlePlayPauseClick = (event) => {
1836
1911
  event.stopPropagation();
1837
1912
  if (isCurrentTrack) {
@@ -1841,12 +1916,12 @@ function TrackItem({
1841
1916
  loadTrackForPlayer(track.uuid);
1842
1917
  }
1843
1918
  };
1844
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
1919
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
1845
1920
  "div",
1846
1921
  {
1847
1922
  className: `track-item flex h-14 text-[var(--color-text)] ${isUnplayable ? "opacity-50" : "hover:bg-[var(--color-surface-muted)] group"}`,
1848
1923
  children: [
1849
- isUnplayable ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1924
+ isUnplayable ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1850
1925
  "div",
1851
1926
  {
1852
1927
  className: "flex items-center justify-center text-lg w-16",
@@ -1854,7 +1929,7 @@ function TrackItem({
1854
1929
  "aria-disabled": "true",
1855
1930
  children: position
1856
1931
  }
1857
- ) : /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1932
+ ) : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1858
1933
  TrackPositionPlayPause,
1859
1934
  {
1860
1935
  position,
@@ -1863,12 +1938,12 @@ function TrackItem({
1863
1938
  isSelected: selectedTrack?.uuid === track.uuid
1864
1939
  }
1865
1940
  ),
1866
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
1867
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime28.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_runtime28.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
1868
- track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_genre_tree13.ArtistNames, { artists: track.artists, renderArtistLink }) }) : ""
1941
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
1942
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree15.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
1943
+ track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_genre_tree16.ArtistNames, { artists: track.artists, renderArtistLink }) }) : ""
1869
1944
  ] }),
1870
- /* @__PURE__ */ (0, import_jsx_runtime28.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_runtime28.jsx)("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
1871
- renderDuration && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1945
+ /* @__PURE__ */ (0, import_jsx_runtime32.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_runtime32.jsx)("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
1946
+ renderDuration && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1872
1947
  "div",
1873
1948
  {
1874
1949
  className: "duration flex text-base w-16 items-center justify-center",
@@ -1876,15 +1951,15 @@ function TrackItem({
1876
1951
  children: renderDuration(track)
1877
1952
  }
1878
1953
  ),
1879
- renderActions && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
1954
+ renderActions && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
1880
1955
  ]
1881
1956
  }
1882
1957
  );
1883
1958
  }
1884
1959
 
1885
1960
  // src/genre-tree/artist/ArtistSongs.tsx
1886
- var import_jsx_runtime29 = require("react/jsx-runtime");
1887
- var SongPageSchema2 = (0, import_transport7.PaginatedResponseSchema)(import_genre_tree14.SongDetailedSchema);
1961
+ var import_jsx_runtime33 = require("react/jsx-runtime");
1962
+ var SongPageSchema2 = (0, import_transport7.PaginatedResponseSchema)(import_genre_tree17.SongDetailedSchema);
1888
1963
  var SORTS = [
1889
1964
  { ordering: "title", label: "Title" },
1890
1965
  { ordering: "-albumYear", label: "Year" }
@@ -1893,11 +1968,11 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
1893
1968
  const [page, setPage] = (0, import_react14.useState)(1);
1894
1969
  const [ordering, setOrdering] = (0, import_react14.useState)("title");
1895
1970
  const { fetch } = (0, import_transport7.useFetchWrapper)(getBackendBaseUrl);
1896
- const { playNewTrackListFromArtist } = (0, import_genre_tree14.useTrackList)();
1971
+ const { playNewTrackListFromArtist } = (0, import_genre_tree17.useTrackList)();
1897
1972
  const { playerTrackObject } = (0, import_player10.usePlayer)();
1898
1973
  const { data, isPending, isError } = (0, import_transport7.useQueryWithParse)({
1899
1974
  queryKey: ["referenceSongs", "artist", artist.uuid, ordering, page],
1900
- queryFn: () => fetch(import_genre_tree14.libraryEndpoints.reference.songs.list(), true, false, void 0, {
1975
+ queryFn: () => fetch(import_genre_tree17.libraryEndpoints.reference.songs.list(), true, false, void 0, {
1901
1976
  artist: artist.uuid,
1902
1977
  ordering,
1903
1978
  page
@@ -1912,25 +1987,25 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
1912
1987
  });
1913
1988
  const handleRowClickCapture = (event, song) => {
1914
1989
  if (!(event.target instanceof Element) || !event.target.closest(".track-position-play-pause")) return;
1915
- if (playerTrackObject?.track.id === song.uuid || (0, import_genre_tree14.songUnplayableReason)(song) !== null) return;
1990
+ if (playerTrackObject?.track.id === song.uuid || (0, import_genre_tree17.songUnplayableReason)(song) !== null) return;
1916
1991
  event.stopPropagation();
1917
1992
  void play(song.uuid);
1918
1993
  };
1919
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui10.PanelSection, { title: "Songs", children: isPending ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui10.Skeleton, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(import_jsx_runtime29.Fragment, { children: [
1920
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
1921
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
1994
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ui11.PanelSection, { title: "Songs", children: isPending ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ui11.Skeleton, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
1995
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
1996
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
1922
1997
  "button",
1923
1998
  {
1924
1999
  type: "button",
1925
2000
  onClick: () => void play(),
1926
2001
  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",
1927
2002
  children: [
1928
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_lucide_react9.Play, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
2003
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_lucide_react9.Play, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
1929
2004
  "Play all"
1930
2005
  ]
1931
2006
  }
1932
2007
  ),
1933
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
2008
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
1934
2009
  "button",
1935
2010
  {
1936
2011
  type: "button",
@@ -1945,7 +2020,7 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
1945
2020
  sort.ordering
1946
2021
  )) })
1947
2022
  ] }),
1948
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
2023
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
1949
2024
  TrackItem,
1950
2025
  {
1951
2026
  track: song,
@@ -1954,12 +2029,12 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
1954
2029
  renderSongLink
1955
2030
  }
1956
2031
  ) }, song.uuid)) }),
1957
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui10.Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
2032
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ui11.Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
1958
2033
  ] }) });
1959
2034
  }
1960
2035
 
1961
2036
  // src/genre-tree/artist/useArtistPanel.tsx
1962
- var import_jsx_runtime30 = require("react/jsx-runtime");
2037
+ var import_jsx_runtime34 = require("react/jsx-runtime");
1963
2038
  function useArtistPanel({
1964
2039
  uuid,
1965
2040
  scope,
@@ -1971,7 +2046,7 @@ function useArtistPanel({
1971
2046
  onExpandedChange,
1972
2047
  onClose
1973
2048
  }) {
1974
- const result = (0, import_genre_tree15.useArtist)(uuid, getBackendBaseUrl);
2049
+ const result = (0, import_genre_tree18.useArtist)(uuid, getBackendBaseUrl);
1975
2050
  const panel = { expanded, onExpandedChange, onClose };
1976
2051
  switch (result.status) {
1977
2052
  case "idle":
@@ -1980,26 +2055,26 @@ function useArtistPanel({
1980
2055
  return {
1981
2056
  ...panel,
1982
2057
  title: "Artist",
1983
- content: /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
1984
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui11.Skeleton, { className: "h-3 w-16" }),
1985
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui11.Skeleton, { className: "h-4 w-32" }),
1986
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui11.Skeleton, { className: "h-3 w-16" }),
1987
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui11.Skeleton, { className: "h-4 w-24" }),
1988
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui11.Skeleton, { className: "h-16 mt-2.5" }),
1989
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui11.Skeleton, { className: "h-16" })
2058
+ content: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2059
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui12.Skeleton, { className: "h-3 w-16" }),
2060
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui12.Skeleton, { className: "h-4 w-32" }),
2061
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui12.Skeleton, { className: "h-3 w-16" }),
2062
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui12.Skeleton, { className: "h-4 w-24" }),
2063
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui12.Skeleton, { className: "h-16 mt-2.5" }),
2064
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui12.Skeleton, { className: "h-16" })
1990
2065
  ] })
1991
2066
  };
1992
2067
  case "not-found":
1993
2068
  return {
1994
2069
  ...panel,
1995
2070
  title: "Artist not found",
1996
- content: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This artist does not exist." })
2071
+ content: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This artist does not exist." })
1997
2072
  };
1998
2073
  case "error":
1999
2074
  return {
2000
2075
  ...panel,
2001
2076
  title: "Artist",
2002
- content: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
2077
+ content: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
2003
2078
  };
2004
2079
  case "success": {
2005
2080
  const { artist } = result;
@@ -2008,13 +2083,13 @@ function useArtistPanel({
2008
2083
  return {
2009
2084
  ...panel,
2010
2085
  title: artist.name,
2011
- content: /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_jsx_runtime30.Fragment, { children: [
2012
- shareUrl && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: shareTarget && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl }) }) }),
2013
- artist.image && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ArtistPhoto, { image: artist.image, name: artist.name }),
2014
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ArtistHeader, { artist }),
2015
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
2016
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
2017
- artist.tracksCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
2086
+ content: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2087
+ shareUrl && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: shareTarget && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl }) }) }),
2088
+ artist.image && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ArtistPhoto, { image: artist.image, name: artist.name }),
2089
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ArtistHeader, { artist }),
2090
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
2091
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
2092
+ artist.tracksCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2018
2093
  ArtistSongs,
2019
2094
  {
2020
2095
  artist: { uuid: artist.uuid, name: artist.name },
@@ -2024,56 +2099,56 @@ function useArtistPanel({
2024
2099
  }
2025
2100
  )
2026
2101
  ] }),
2027
- extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ArtistDiscography, { albums: artist.albums }) : void 0
2102
+ extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ArtistDiscography, { albums: artist.albums }) : void 0
2028
2103
  };
2029
2104
  }
2030
2105
  }
2031
2106
  }
2032
2107
 
2033
2108
  // src/genre-tree/song/useSongPanel.tsx
2034
- var import_ui13 = require("@behindthemusictree/ui");
2035
- var import_genre_tree17 = require("@behindthemusictree/app-kit/genre-tree");
2109
+ var import_ui14 = require("@behindthemusictree/ui");
2110
+ var import_genre_tree20 = require("@behindthemusictree/app-kit/genre-tree");
2036
2111
 
2037
2112
  // src/genre-tree/song/SongPanelContent.tsx
2038
2113
  var import_react15 = require("react");
2039
2114
  var import_lucide_react10 = require("lucide-react");
2040
- var import_ui12 = require("@behindthemusictree/ui");
2115
+ var import_ui13 = require("@behindthemusictree/ui");
2041
2116
  var import_player11 = require("@behindthemusictree/app-kit/player");
2042
- var import_genre_tree16 = require("@behindthemusictree/app-kit/genre-tree");
2043
- var import_jsx_runtime31 = require("react/jsx-runtime");
2117
+ var import_genre_tree19 = require("@behindthemusictree/app-kit/genre-tree");
2118
+ var import_jsx_runtime35 = require("react/jsx-runtime");
2044
2119
  var numberFormat = new Intl.NumberFormat("en");
2045
2120
  var dateFormat = new Intl.DateTimeFormat("en", { dateStyle: "medium" });
2046
2121
  var externalLink = "text-[var(--color-primary)] hover:underline focus-visible:underline";
2047
2122
  function SongHistory({ uuid, getBackendBaseUrl }) {
2048
2123
  const [open, setOpen] = (0, import_react15.useState)(false);
2049
- const history = (0, import_genre_tree16.useSongHistory)(uuid, getBackendBaseUrl, open);
2124
+ const history = (0, import_genre_tree19.useSongHistory)(uuid, getBackendBaseUrl, open);
2050
2125
  let body = null;
2051
- if (history.status === "loading") body = /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ui12.Skeleton, { className: "h-10", "aria-busy": "true" });
2052
- else if (history.status === "error") body = /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { className: "text-red-600", children: "Could not load the history." });
2126
+ if (history.status === "loading") body = /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui13.Skeleton, { className: "h-10", "aria-busy": "true" });
2127
+ else if (history.status === "error") body = /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "text-red-600", children: "Could not load the history." });
2053
2128
  else if (history.status === "success")
2054
- body = history.entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("li", { className: "py-1.5 text-sm", children: [
2055
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "font-medium", children: entry.action }),
2056
- entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
2129
+ body = history.entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("li", { className: "py-1.5 text-sm", children: [
2130
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "font-medium", children: entry.action }),
2131
+ entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
2057
2132
  " ",
2058
2133
  "\u2014 ",
2059
2134
  entry.oldValue ?? "none",
2060
2135
  " \u2192 ",
2061
2136
  entry.newValue ?? "none"
2062
2137
  ] }) : null,
2063
- /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("span", { className: "block text-xs text-[var(--color-text-muted)]", children: [
2138
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "block text-xs text-[var(--color-text-muted)]", children: [
2064
2139
  entry.actorPseudo ?? "Anonymous",
2065
2140
  " \xB7 ",
2066
2141
  dateFormat.format(new Date(entry.createdOn))
2067
2142
  ] })
2068
2143
  ] }, entry.uuid)) });
2069
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
2144
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
2070
2145
  "details",
2071
2146
  {
2072
2147
  onToggle: (event) => setOpen(event.currentTarget.open),
2073
2148
  className: "px-6 pb-2.5 mt-2.5 border-t border-[var(--color-border)]",
2074
2149
  children: [
2075
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("summary", { className: "mt-2.5 cursor-pointer text-[0.6875rem] font-semibold uppercase text-[var(--color-text-muted)]", children: "History" }),
2076
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "mt-1.5", children: body })
2150
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("summary", { className: "mt-2.5 cursor-pointer text-[0.6875rem] font-semibold uppercase text-[var(--color-text-muted)]", children: "History" }),
2151
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "mt-1.5", children: body })
2077
2152
  ]
2078
2153
  }
2079
2154
  );
@@ -2087,25 +2162,25 @@ function SongPanelContent({
2087
2162
  genrePath,
2088
2163
  onSelectGenre
2089
2164
  }) {
2090
- const { playNewTrackListFromTrackUuid } = (0, import_genre_tree16.useTrackList)();
2165
+ const { playNewTrackListFromTrackUuid } = (0, import_genre_tree19.useTrackList)();
2091
2166
  const { playerTrackObject, playState, handlePlayPauseAction } = (0, import_player11.usePlayer)();
2092
- const unplayableReason = (0, import_genre_tree16.songUnplayableReason)(song);
2167
+ const unplayableReason = (0, import_genre_tree19.songUnplayableReason)(song);
2093
2168
  const isCurrent = playerTrackObject?.track.id === song.uuid;
2094
2169
  const isPlaying = isCurrent && playState === import_player11.PlayStates.PLAYING;
2095
2170
  const shareTarget = { kind: "song", uuid: song.uuid, name: song.title, scope };
2096
- const musicBrainzUrl = (0, import_genre_tree16.musicBrainzRecordingUrl)(song);
2171
+ const musicBrainzUrl = (0, import_genre_tree19.musicBrainzRecordingUrl)(song);
2097
2172
  const fields = [];
2098
2173
  if (song.genre) {
2099
2174
  const path = genrePath.length > 0 ? genrePath : null;
2100
2175
  fields.push({
2101
2176
  label: "Genre",
2102
- value: path ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("span", { children: [
2103
- index > 0 && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { "aria-hidden": "true", children: "\u203A " }),
2104
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("button", { type: "button", onClick: () => onSelectGenre(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
2177
+ value: path ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { children: [
2178
+ index > 0 && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { "aria-hidden": "true", children: "\u203A " }),
2179
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("button", { type: "button", onClick: () => onSelectGenre(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
2105
2180
  ] }, genrePlaylist.uuid)) }) : song.genre.name
2106
2181
  });
2107
2182
  } else {
2108
- fields.push({ label: "Genre", value: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
2183
+ fields.push({ label: "Genre", value: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
2109
2184
  }
2110
2185
  if (song.firstReleaseYear) fields.push({ label: "First released", value: song.firstReleaseYear });
2111
2186
  const viewCount = song.youtubeVideo?.viewCount;
@@ -2115,11 +2190,11 @@ function SongPanelContent({
2115
2190
  const links = [];
2116
2191
  if (musicBrainzUrl)
2117
2192
  links.push(
2118
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
2193
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
2119
2194
  );
2120
2195
  if (song.youtubeVideo)
2121
2196
  links.push(
2122
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
2197
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2123
2198
  "a",
2124
2199
  {
2125
2200
  href: `https://www.youtube.com/watch?v=${encodeURIComponent(song.youtubeVideo.id)}`,
@@ -2134,33 +2209,33 @@ function SongPanelContent({
2134
2209
  if (links.length > 0)
2135
2210
  fields.push({
2136
2211
  label: "Links",
2137
- value: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "flex gap-3", children: links })
2212
+ value: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "flex gap-3", children: links })
2138
2213
  });
2139
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_jsx_runtime31.Fragment, { children: [
2140
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
2141
- unplayableReason ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { className: "flex items-center px-2.5 text-sm text-[var(--color-text-muted)]", children: unplayableReason }) : /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
2214
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
2215
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
2216
+ unplayableReason ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "flex items-center px-2.5 text-sm text-[var(--color-text-muted)]", children: unplayableReason }) : /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
2142
2217
  "button",
2143
2218
  {
2144
2219
  type: "button",
2145
2220
  onClick: () => isCurrent ? handlePlayPauseAction() : playNewTrackListFromTrackUuid(song, scope),
2146
2221
  className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none",
2147
2222
  children: [
2148
- /* @__PURE__ */ (0, import_jsx_runtime31.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_runtime31.jsx)(import_lucide_react10.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react10.Play, { "aria-hidden": "true", className: "w-5 h-5" }) }),
2223
+ /* @__PURE__ */ (0, import_jsx_runtime35.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_runtime35.jsx)(import_lucide_react10.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react10.Play, { "aria-hidden": "true", className: "w-5 h-5" }) }),
2149
2224
  isPlaying ? "Pause" : "Play"
2150
2225
  ]
2151
2226
  }
2152
2227
  ),
2153
- shareUrl && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
2228
+ shareUrl && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
2154
2229
  ] }) }),
2155
- song.artists && song.artists.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { className: "px-6 pt-2.5 text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_genre_tree16.ArtistNames, { artists: song.artists, renderArtistLink }) }),
2156
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ui12.FieldList, { items: fields }),
2157
- song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ui12.PanelSection, { title: "Essential in", children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
2158
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
2230
+ song.artists && song.artists.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "px-6 pt-2.5 text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_genre_tree19.ArtistNames, { artists: song.artists, renderArtistLink }) }),
2231
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui13.FieldList, { items: fields }),
2232
+ song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui13.PanelSection, { title: "Essential in", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
2233
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
2159
2234
  ] });
2160
2235
  }
2161
2236
 
2162
2237
  // src/genre-tree/song/useSongPanel.tsx
2163
- var import_jsx_runtime32 = require("react/jsx-runtime");
2238
+ var import_jsx_runtime36 = require("react/jsx-runtime");
2164
2239
  function useSongPanel({
2165
2240
  uuid,
2166
2241
  scope,
@@ -2173,7 +2248,7 @@ function useSongPanel({
2173
2248
  onExpandedChange,
2174
2249
  onClose
2175
2250
  }) {
2176
- const result = (0, import_genre_tree17.useSong)(uuid, getBackendBaseUrl);
2251
+ const result = (0, import_genre_tree20.useSong)(uuid, getBackendBaseUrl);
2177
2252
  const panel = { expanded, onExpandedChange, onClose };
2178
2253
  switch (result.status) {
2179
2254
  case "idle":
@@ -2182,24 +2257,24 @@ function useSongPanel({
2182
2257
  return {
2183
2258
  ...panel,
2184
2259
  title: "Song",
2185
- content: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2186
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui13.Skeleton, { className: "h-3 w-16" }),
2187
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui13.Skeleton, { className: "h-4 w-32" }),
2188
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui13.Skeleton, { className: "h-3 w-16" }),
2189
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui13.Skeleton, { className: "h-4 w-24" })
2260
+ content: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2261
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui14.Skeleton, { className: "h-3 w-16" }),
2262
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui14.Skeleton, { className: "h-4 w-32" }),
2263
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui14.Skeleton, { className: "h-3 w-16" }),
2264
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui14.Skeleton, { className: "h-4 w-24" })
2190
2265
  ] })
2191
2266
  };
2192
2267
  case "not-found":
2193
2268
  return {
2194
2269
  ...panel,
2195
2270
  title: "Song not found",
2196
- content: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This song does not exist." })
2271
+ content: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This song does not exist." })
2197
2272
  };
2198
2273
  case "error":
2199
2274
  return {
2200
2275
  ...panel,
2201
2276
  title: "Song",
2202
- content: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this song." })
2277
+ content: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this song." })
2203
2278
  };
2204
2279
  case "success": {
2205
2280
  const { song } = result;
@@ -2207,7 +2282,7 @@ function useSongPanel({
2207
2282
  return {
2208
2283
  ...panel,
2209
2284
  title: song.title,
2210
- content: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
2285
+ content: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2211
2286
  SongPanelContent,
2212
2287
  {
2213
2288
  song,
@@ -2226,29 +2301,29 @@ function useSongPanel({
2226
2301
 
2227
2302
  // src/genre-tree/SizeMetricToggle.tsx
2228
2303
  var import_md = require("react-icons/md");
2229
- var import_ui14 = require("@behindthemusictree/ui");
2230
- var import_jsx_runtime33 = require("react/jsx-runtime");
2304
+ var import_ui15 = require("@behindthemusictree/ui");
2305
+ var import_jsx_runtime37 = require("react/jsx-runtime");
2231
2306
  function SizeMetricToggle({
2232
2307
  value,
2233
2308
  onChange
2234
2309
  }) {
2235
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
2236
- import_ui14.SegmentedControl,
2310
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
2311
+ import_ui15.SegmentedControl,
2237
2312
  {
2238
2313
  variant: "floating",
2239
2314
  "aria-label": "Size nodes by",
2240
2315
  value,
2241
2316
  onChange,
2242
2317
  options: [
2243
- { value: "songs", label: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_md.MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
2244
- { value: "views", label: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_md.MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
2318
+ { value: "songs", label: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_md.MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
2319
+ { value: "views", label: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_md.MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
2245
2320
  ]
2246
2321
  }
2247
2322
  );
2248
2323
  }
2249
2324
 
2250
2325
  // src/genre-tree/GenreTreeView.tsx
2251
- var import_jsx_runtime34 = require("react/jsx-runtime");
2326
+ var import_jsx_runtime38 = require("react/jsx-runtime");
2252
2327
  var NO_POP_ROOT_TITLE = "This genre tree has no 'Mainstream Pop' root yet";
2253
2328
  var REGIONS_CLASS = "[--gtv-center-left:var(--layout-center-left)] [--gtv-center-right:var(--layout-center-right)] [--gtv-center-top:var(--layout-band-top)]";
2254
2329
  var noop = () => {
@@ -2282,6 +2357,9 @@ function GenreTreeView({
2282
2357
  artistExpanded: controlledArtistExpanded,
2283
2358
  onArtistExpandedChange,
2284
2359
  getShareUrl,
2360
+ extraSearchGroups = [],
2361
+ onSearchQueryChange,
2362
+ onSearchFocus,
2285
2363
  artistSearch = true,
2286
2364
  onArtistSelect,
2287
2365
  artistSearchBaseUrl,
@@ -2347,7 +2425,7 @@ function GenreTreeView({
2347
2425
  allowWheelRotation: internalAllowWheelRotation,
2348
2426
  toggleWheelRotation,
2349
2427
  onNodeHover: handleNodeHover
2350
- } = (0, import_genre_tree19.useGenreTreeViewState)({
2428
+ } = (0, import_genre_tree21.useGenreTreeViewState)({
2351
2429
  scope,
2352
2430
  getBackendBaseUrl,
2353
2431
  viewMode: controlledViewMode,
@@ -2366,12 +2444,12 @@ function GenreTreeView({
2366
2444
  restoreRootUuid = selectedGenrePlaylist.root.uuid;
2367
2445
  setStackedRestoreOwner({ revision: restoreRevision, rootUuid: restoreRootUuid });
2368
2446
  }
2369
- const isGenreUpdating = (0, import_react_query3.useIsMutating)({ mutationKey: import_genre_tree19.genreUpdateMutationKey }) > 0;
2370
- const { mutate: updateGenre } = (0, import_genre_tree19.useUpdateGenre)(scope, getBackendBaseUrl);
2447
+ const isGenreUpdating = (0, import_react_query3.useIsMutating)({ mutationKey: import_genre_tree21.genreUpdateMutationKey }) > 0;
2448
+ const { mutate: updateGenre } = (0, import_genre_tree21.useUpdateGenre)(scope, getBackendBaseUrl);
2371
2449
  const busy = deferredSizeMetric !== sizeMetric || isTreeFetching || isGenreUpdating;
2372
2450
  const isArtistFiltered = artistUuid !== null;
2373
2451
  const canSizeByViews = !isArtistFiltered && hasViewsCount(visibleGenrePlaylists);
2374
- const artistResult = (0, import_genre_tree19.useArtist)(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
2452
+ const artistResult = (0, import_genre_tree21.useArtist)(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
2375
2453
  const filterArtistName = artistResult.status === "success" ? artistResult.artist.name : null;
2376
2454
  const filterArtist = (0, import_react16.useMemo)(
2377
2455
  () => artistUuid !== null && filterArtistName !== null ? { uuid: artistUuid, name: filterArtistName } : null,
@@ -2391,7 +2469,7 @@ function GenreTreeView({
2391
2469
  const openSongRef = (0, import_react16.useRef)(openSong);
2392
2470
  openSongRef.current = openSong;
2393
2471
  const resolvedRenderSongLink = (0, import_react16.useCallback)(
2394
- (song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
2472
+ (song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
2395
2473
  [renderSongLink]
2396
2474
  );
2397
2475
  const [expandedSongUuid, setExpandedSongUuid] = (0, import_react16.useState)(null);
@@ -2403,7 +2481,7 @@ function GenreTreeView({
2403
2481
  scope,
2404
2482
  getBackendBaseUrl: artistBaseUrl,
2405
2483
  getShareUrl: getShareUrlRef.current,
2406
- renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
2484
+ renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
2407
2485
  expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
2408
2486
  onExpandedChange: (on) => {
2409
2487
  if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
@@ -2412,7 +2490,7 @@ function GenreTreeView({
2412
2490
  renderSongLink: resolvedRenderSongLink,
2413
2491
  onClose: () => changeArtist(null)
2414
2492
  });
2415
- const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
2493
+ const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
2416
2494
  const getGenrePath = (genre) => {
2417
2495
  const path = [];
2418
2496
  let current = visibleGenrePlaylists.find((playlist) => playlist.criteria?.uuid === genre.uuid);
@@ -2468,7 +2546,7 @@ function GenreTreeView({
2468
2546
  []
2469
2547
  );
2470
2548
  const panelRef = (0, import_react16.useRef)(null);
2471
- const obscuredInset = (0, import_ui15.useObscuredInset)(panelRef);
2549
+ const obscuredInset = (0, import_ui16.useObscuredInset)(panelRef);
2472
2550
  const hostPanel = { obscuredInset, onNodeStyleResolverChange: handleNodeStyleResolverChange };
2473
2551
  const nodeMetric = isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric;
2474
2552
  const treeNodes = (0, import_react16.useMemo)(() => toTreeNodes(visibleGenrePlaylists, nodeMetric), [visibleGenrePlaylists, nodeMetric]);
@@ -2491,52 +2569,27 @@ function GenreTreeView({
2491
2569
  const handlePrevious = previousPlaylist && visibleGenrePlaylists.includes(previousPlaylist) ? () => handleGenreSearchSelect(previousPlaylist) : void 0;
2492
2570
  const { showPopup, hidePopup } = (0, import_popup5.usePopup)();
2493
2571
  const [searchText, setSearchText] = (0, import_react16.useState)("");
2494
- const artistSearchResult = (0, import_genre_tree18.useArtistSearch)(artistBaseUrl, artistSearch ? searchText : "");
2495
- const artistGroup = artistSearch ? {
2496
- key: "artists",
2497
- label: "Artists",
2498
- items: artistSearchResult.artists.map((artist) => ({
2499
- key: artist.uuid,
2500
- content: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2501
- /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "block truncate font-medium", children: [
2502
- artist.name,
2503
- artist.disambiguation && /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "font-normal text-[var(--color-neutral-500)]", children: [
2504
- " \xB7 ",
2505
- artist.disambiguation
2506
- ] })
2507
- ] }),
2508
- artist.matchedAlias && /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [
2509
- "also known as ",
2510
- artist.matchedAlias
2511
- ] })
2512
- ] }),
2513
- onSelect: () => {
2514
- openArtist(artist.uuid);
2515
- onArtistSelect?.(artist);
2516
- }
2517
- })),
2518
- status: artistSearchResult.isError ? "Search failed" : artistSearchResult.settled && artistSearchResult.artists.length === 0 ? "No artists found" : "",
2519
- loading: artistSearchResult.pending
2520
- } : void 0;
2521
- const songSearchResult = (0, import_genre_tree18.useSongSearch)(songBaseUrl, songSearch ? searchText : "");
2522
- const songGroup = songSearch ? {
2523
- key: "songs",
2524
- label: "Songs",
2525
- items: songSearchResult.songs.map((song) => ({
2526
- key: song.uuid,
2527
- content: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: (0, import_genre_tree18.isSongSearchResultPlayable)(song) ? void 0 : "opacity-60", children: [
2528
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "block truncate font-medium", children: song.title }),
2529
- /* @__PURE__ */ (0, import_jsx_runtime34.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 ") })
2530
- ] }),
2531
- onSelect: () => {
2532
- openSong(song.uuid);
2533
- onSongSelect?.(song);
2534
- }
2535
- })),
2536
- status: songSearchResult.isError ? "Search failed" : songSearchResult.settled && songSearchResult.songs.length === 0 ? "No songs found" : "",
2537
- loading: songSearchResult.pending
2538
- } : void 0;
2539
- const extraGroups = [artistGroup, songGroup].filter((group) => group !== void 0);
2572
+ const artistGroup = useArtistSearchGroup(
2573
+ artistBaseUrl,
2574
+ searchText,
2575
+ (artist) => {
2576
+ openArtist(artist.uuid);
2577
+ onArtistSelect?.(artist);
2578
+ },
2579
+ Boolean(artistSearch)
2580
+ );
2581
+ const songGroup = useSongSearchGroup(
2582
+ songBaseUrl,
2583
+ searchText,
2584
+ (song) => {
2585
+ openSong(song.uuid);
2586
+ onSongSelect?.(song);
2587
+ },
2588
+ Boolean(songSearch)
2589
+ );
2590
+ const extraGroups = [...extraSearchGroups, artistGroup, songGroup].filter(
2591
+ (group) => group !== void 0
2592
+ );
2540
2593
  const handleSelectedNodeChange = (node) => {
2541
2594
  if (node !== null && songUuid !== null) changeSong(null);
2542
2595
  selectNode(node);
@@ -2560,7 +2613,7 @@ function GenreTreeView({
2560
2613
  };
2561
2614
  const renderGenreBody = (relation) => {
2562
2615
  const { node } = relation;
2563
- const family = /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2616
+ const family = /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2564
2617
  import_genre_tree_view4.GenreNodeFamily,
2565
2618
  {
2566
2619
  node,
@@ -2571,10 +2624,10 @@ function GenreTreeView({
2571
2624
  onSelectNode: selectGenreFromPanel
2572
2625
  }
2573
2626
  );
2574
- const ancestors = /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_genre_tree_view4.GenreNodeAncestors, { ancestorNodes: relation.ancestorNodes, onSelectNode: selectGenreFromPanel });
2627
+ const ancestors = /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_genre_tree_view4.GenreNodeAncestors, { ancestorNodes: relation.ancestorNodes, onSelectNode: selectGenreFromPanel });
2575
2628
  const genrePlaylist = genrePlaylistByNodeId.get(node.id);
2576
2629
  const criteria = genrePlaylist?.criteria;
2577
- if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2630
+ if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_jsx_runtime38.Fragment, { children: [
2578
2631
  family,
2579
2632
  ancestors
2580
2633
  ] }) };
@@ -2594,11 +2647,11 @@ function GenreTreeView({
2594
2647
  (sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist.uuid && sibling.criteria?.side === "pop"
2595
2648
  ) : void 0;
2596
2649
  return {
2597
- primary: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2598
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2650
+ primary: /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_jsx_runtime38.Fragment, { children: [
2651
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2599
2652
  GenreInfoPanelBody,
2600
2653
  {
2601
- family: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2654
+ family: /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_jsx_runtime38.Fragment, { children: [
2602
2655
  family,
2603
2656
  ancestors
2604
2657
  ] }),
@@ -2616,7 +2669,7 @@ function GenreTreeView({
2616
2669
  onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
2617
2670
  onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
2618
2671
  onChangeParent: readOnly ? void 0 : () => showPopup(
2619
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2672
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2620
2673
  ChangeParentPopup,
2621
2674
  {
2622
2675
  genrePlaylist: { ...genrePlaylist, criteria },
@@ -2691,19 +2744,19 @@ function GenreTreeView({
2691
2744
  edge: activePanel.expanded ? { label: "Shrink panel", onClick: () => activePanel.onExpandedChange?.(false) } : isCollapsed ? null : { label: "Hide panel", onClick: () => changeCollapsed(true) },
2692
2745
  center: activePanel.expanded ? null : isCollapsed ? { label: "Show panel", onClick: () => changeCollapsed(false) } : activePanel.onExpandedChange ? { label: "Extend panel", onClick: () => activePanel.onExpandedChange?.(true) } : null
2693
2746
  } : void 0;
2694
- const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2695
- import_ui15.IconButton,
2747
+ const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2748
+ import_ui16.IconButton,
2696
2749
  {
2697
2750
  "aria-label": activePanel.expanded ? "Shrink panel" : "Extend panel",
2698
2751
  style: activePanel.headerAccent && { color: "inherit" },
2699
2752
  onClick: () => activePanel.onExpandedChange?.(!activePanel.expanded),
2700
- children: activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react12.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react12.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
2753
+ children: activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_lucide_react12.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_lucide_react12.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
2701
2754
  }
2702
2755
  ) : null;
2703
2756
  const renderArtistFilter = () => {
2704
2757
  if (isLoading || !filterArtist) return null;
2705
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
2706
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2758
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
2759
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2707
2760
  "button",
2708
2761
  {
2709
2762
  type: "button",
@@ -2715,7 +2768,7 @@ function GenreTreeView({
2715
2768
  children: filterArtist.name
2716
2769
  }
2717
2770
  ),
2718
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2771
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2719
2772
  "button",
2720
2773
  {
2721
2774
  type: "button",
@@ -2727,8 +2780,8 @@ function GenreTreeView({
2727
2780
  )
2728
2781
  ] });
2729
2782
  };
2730
- const actions = /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2731
- !isLoading && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2783
+ const actions = /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_jsx_runtime38.Fragment, { children: [
2784
+ !isLoading && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2732
2785
  GenreSearch,
2733
2786
  {
2734
2787
  genrePlaylists: visibleGenrePlaylists,
@@ -2736,14 +2789,18 @@ function GenreTreeView({
2736
2789
  selectedName: selectedGenrePlaylist?.name ?? (typeof builtInPanel?.title === "string" ? builtInPanel.title : null),
2737
2790
  onClear: builtInSongPanel ? () => changeSong(null) : builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
2738
2791
  onPrevious: handlePrevious,
2739
- onQueryChange: artistSearch || songSearch ? setSearchText : void 0,
2792
+ onQueryChange: (text) => {
2793
+ setSearchText(text);
2794
+ onSearchQueryChange?.(text);
2795
+ },
2796
+ onFocus: onSearchFocus,
2740
2797
  extraGroups
2741
2798
  }
2742
2799
  ),
2743
- !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
2744
- /* @__PURE__ */ (0, import_jsx_runtime34.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: [
2745
- !isLoading && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2746
- import_ui15.SegmentedControl,
2800
+ !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
2801
+ /* @__PURE__ */ (0, import_jsx_runtime38.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: [
2802
+ !isLoading && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2803
+ import_ui16.SegmentedControl,
2747
2804
  {
2748
2805
  "aria-label": "Tree view mode",
2749
2806
  value: viewMode,
@@ -2756,14 +2813,14 @@ function GenreTreeView({
2756
2813
  ]
2757
2814
  }
2758
2815
  ),
2759
- !isLoading && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2816
+ !isLoading && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2760
2817
  "div",
2761
2818
  {
2762
2819
  className: "flex items-center gap-1",
2763
2820
  role: "group",
2764
2821
  "aria-label": "Tree display options",
2765
- children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2766
- import_ui15.Button,
2822
+ children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2823
+ import_ui16.Button,
2767
2824
  {
2768
2825
  variant: allowWheelRotation ? "default" : "outline",
2769
2826
  size: "sm",
@@ -2773,8 +2830,8 @@ function GenreTreeView({
2773
2830
  )
2774
2831
  }
2775
2832
  ),
2776
- !isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2777
- import_ui15.IconTextButton,
2833
+ !isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2834
+ import_ui16.IconTextButton,
2778
2835
  {
2779
2836
  icon: import_lucide_react11.Plus,
2780
2837
  text: "Add root",
@@ -2783,34 +2840,34 @@ function GenreTreeView({
2783
2840
  )
2784
2841
  ] })
2785
2842
  ] });
2786
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
2843
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
2787
2844
  "div",
2788
2845
  {
2789
2846
  "data-left-panel": isPanelShown && !isCollapsed ? void 0 : "hidden",
2790
2847
  className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
2791
2848
  children: [
2792
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2849
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2793
2850
  "div",
2794
2851
  {
2795
2852
  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"}`,
2796
2853
  children: actions
2797
2854
  }
2798
2855
  ),
2799
- !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2856
+ !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2800
2857
  "div",
2801
2858
  {
2802
2859
  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"}`,
2803
2860
  children: renderArtistFilter()
2804
2861
  }
2805
2862
  ),
2806
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
2807
- !isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
2808
- isLoading ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_genre_tree_view4.GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
2863
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
2864
+ !isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
2865
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_genre_tree_view4.GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
2809
2866
  // The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
2810
2867
  // and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
2811
2868
  // Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
2812
2869
  // The panel's own Zoom in button is hidden: the search bar carries it.
2813
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2870
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2814
2871
  GenrePlaylistTreeWheel,
2815
2872
  {
2816
2873
  scope,
@@ -2827,9 +2884,9 @@ function GenreTreeView({
2827
2884
  allowWheelRotation,
2828
2885
  sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
2829
2886
  busy,
2830
- floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex items-center gap-2", children: [
2887
+ floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "flex items-center gap-2", children: [
2831
2888
  floatingControlsLeading,
2832
- canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2889
+ canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2833
2890
  ] }) : void 0,
2834
2891
  defaultViewport,
2835
2892
  viewportRestore,
@@ -2838,7 +2895,7 @@ function GenreTreeView({
2838
2895
  ) }) })
2839
2896
  ) : viewMode === "pop-core" ? (
2840
2897
  // Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
2841
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2898
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2842
2899
  GenrePlaylistTreeWheelRadialPopCore,
2843
2900
  {
2844
2901
  scope,
@@ -2855,9 +2912,9 @@ function GenreTreeView({
2855
2912
  allowWheelRotation,
2856
2913
  sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
2857
2914
  busy,
2858
- floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex items-center gap-2", children: [
2915
+ floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "flex items-center gap-2", children: [
2859
2916
  floatingControlsLeading,
2860
- canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2917
+ canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2861
2918
  ] }) : void 0,
2862
2919
  defaultViewport,
2863
2920
  viewportRestore,
@@ -2865,7 +2922,7 @@ function GenreTreeView({
2865
2922
  ...focus
2866
2923
  }
2867
2924
  ) }) })
2868
- ) : viewMode === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2925
+ ) : viewMode === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2869
2926
  GenrePlaylistTreeWheelRadialPopCore,
2870
2927
  {
2871
2928
  outline: true,
@@ -2881,13 +2938,13 @@ function GenreTreeView({
2881
2938
  selectedNodeId,
2882
2939
  readOnly
2883
2940
  }
2884
- ) }) : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "tree-container relative flex flex-col gap-4 w-full pt-14 overflow-x-auto overflow-y-auto text-[var(--color-text)]", children: Object.entries(groupedGenrePlaylistsByRoot).map(
2941
+ ) }) : /* @__PURE__ */ (0, import_jsx_runtime38.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(
2885
2942
  ([uuid, genrePlaylistTreePerRoot]) => {
2886
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2943
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2887
2944
  "div",
2888
2945
  {
2889
2946
  className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
2890
- children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2947
+ children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2891
2948
  GenrePlaylistTreePerRoot,
2892
2949
  {
2893
2950
  scope,
@@ -2913,8 +2970,8 @@ function GenreTreeView({
2913
2970
  );
2914
2971
  }
2915
2972
  ) }),
2916
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2917
- import_ui15.DetailPanel,
2973
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2974
+ import_ui16.DetailPanel,
2918
2975
  {
2919
2976
  ref: panelRef,
2920
2977
  side: "left",
@@ -2930,7 +2987,7 @@ function GenreTreeView({
2930
2987
  aside: activePanel?.aside,
2931
2988
  edgeTab: detailTab,
2932
2989
  headerClassName: activePanel?.compact ? "pt-[calc(var(--layout-search-h)+var(--layout-panel-inset)+0.5rem)]" : void 0,
2933
- children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2990
+ children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_jsx_runtime38.Fragment, { children: [
2934
2991
  activePanel.content,
2935
2992
  activePanel.aside
2936
2993
  ] }) : activePanel?.content
@@ -2945,10 +3002,10 @@ function GenreTreeView({
2945
3002
  // src/genre-tree/GenreDetailPanel.tsx
2946
3003
  var import_react17 = require("react");
2947
3004
  var import_genre_tree_view5 = require("@behindthemusictree/genre-tree-view");
2948
- var import_ui16 = require("@behindthemusictree/ui");
3005
+ var import_ui17 = require("@behindthemusictree/ui");
2949
3006
  var import_lucide_react13 = require("lucide-react");
2950
- var import_genre_tree20 = require("@behindthemusictree/app-kit/genre-tree");
2951
- var import_jsx_runtime35 = require("react/jsx-runtime");
3007
+ var import_genre_tree22 = require("@behindthemusictree/app-kit/genre-tree");
3008
+ var import_jsx_runtime39 = require("react/jsx-runtime");
2952
3009
  var RELATIVE_ITEM_COUNT = 1;
2953
3010
  function toNode(genre, parentId) {
2954
3011
  return {
@@ -2975,7 +3032,7 @@ function GenreDetailPanel({
2975
3032
  edgeTab,
2976
3033
  extras
2977
3034
  }) {
2978
- const { data: detail, isPending, isError } = (0, import_genre_tree20.useFetchGenreDetail)(genreUuid, scope, getBackendBaseUrl);
3035
+ const { data: detail, isPending, isError } = (0, import_genre_tree22.useFetchGenreDetail)(genreUuid, scope, getBackendBaseUrl);
2979
3036
  const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: [] });
2980
3037
  const view = (0, import_react17.useMemo)(() => {
2981
3038
  if (!detail) return null;
@@ -3003,17 +3060,17 @@ function GenreDetailPanel({
3003
3060
  };
3004
3061
  }, [detail]);
3005
3062
  if (isError) {
3006
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
3063
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
3007
3064
  }
3008
3065
  if (isPending || !detail || !view) {
3009
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
3010
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui16.Skeleton, { className: "h-6 w-2/3" }),
3011
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui16.Skeleton, { className: "h-4 w-full" }),
3012
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui16.Skeleton, { className: "h-4 w-3/4" })
3066
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
3067
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_ui17.Skeleton, { className: "h-6 w-2/3" }),
3068
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_ui17.Skeleton, { className: "h-4 w-full" }),
3069
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_ui17.Skeleton, { className: "h-4 w-3/4" })
3013
3070
  ] });
3014
3071
  }
3015
3072
  const selectGenre = (nodeId) => onSelectGenre(nodeId);
3016
- const family = /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
3073
+ const family = /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
3017
3074
  import_genre_tree_view5.GenreNodeFamily,
3018
3075
  {
3019
3076
  node: view.node,
@@ -3024,20 +3081,20 @@ function GenreDetailPanel({
3024
3081
  onSelectNode: selectGenre
3025
3082
  }
3026
3083
  );
3027
- const ancestors = /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_genre_tree_view5.GenreNodeAncestors, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
3028
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
3029
- import_ui16.DetailPanel,
3084
+ const ancestors = /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_genre_tree_view5.GenreNodeAncestors, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
3085
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
3086
+ import_ui17.DetailPanel,
3030
3087
  {
3031
3088
  side: "left",
3032
3089
  title: view.node.name,
3033
3090
  headerAccent: { background: view.fill, color: import_genre_tree_view5.ACCENT_TEXT_COLOR },
3034
- headerActions: onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
3035
- import_ui16.IconButton,
3091
+ headerActions: onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
3092
+ import_ui17.IconButton,
3036
3093
  {
3037
3094
  "aria-label": expanded ? "Shrink panel" : "Extend panel",
3038
3095
  style: { color: "inherit" },
3039
3096
  onClick: () => onExpandedChange(!expanded),
3040
- children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react13.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react13.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
3097
+ children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_lucide_react13.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_lucide_react13.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
3041
3098
  }
3042
3099
  ) : void 0,
3043
3100
  onClose,
@@ -3046,10 +3103,10 @@ function GenreDetailPanel({
3046
3103
  onExpandedChange,
3047
3104
  edgeTab,
3048
3105
  children: [
3049
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
3106
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
3050
3107
  GenreInfoPanelBody,
3051
3108
  {
3052
- family: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
3109
+ family: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_jsx_runtime39.Fragment, { children: [
3053
3110
  family,
3054
3111
  ancestors
3055
3112
  ] }),
@@ -3075,10 +3132,10 @@ var import_genre_tree_view6 = require("@behindthemusictree/genre-tree-view");
3075
3132
  // src/genre-tree/track-list-sidebar/TrackListSidebar.tsx
3076
3133
  var import_react18 = require("react");
3077
3134
  var import_lucide_react14 = require("lucide-react");
3078
- var import_genre_tree21 = require("@behindthemusictree/app-kit/genre-tree");
3079
- var import_genre_tree22 = require("@behindthemusictree/app-kit/genre-tree");
3135
+ var import_genre_tree23 = require("@behindthemusictree/app-kit/genre-tree");
3136
+ var import_genre_tree24 = require("@behindthemusictree/app-kit/genre-tree");
3080
3137
  var import_track_noun3 = require("@behindthemusictree/app-kit/track-noun");
3081
- var import_jsx_runtime36 = require("react/jsx-runtime");
3138
+ var import_jsx_runtime40 = require("react/jsx-runtime");
3082
3139
  var useBrowserLayoutEffect = typeof window === "undefined" ? import_react18.useEffect : import_react18.useLayoutEffect;
3083
3140
  function TrackListSidebar({
3084
3141
  className: className2 = "",
@@ -3091,7 +3148,7 @@ function TrackListSidebar({
3091
3148
  renderArtistLink,
3092
3149
  renderSongLink
3093
3150
  }) {
3094
- const { trackList, loadMore, autoAdvanceScrollRequest } = (0, import_genre_tree21.useTrackList)();
3151
+ const { trackList, loadMore, autoAdvanceScrollRequest } = (0, import_genre_tree23.useTrackList)();
3095
3152
  const trackNoun = (0, import_track_noun3.useTrackNoun)();
3096
3153
  const trackListRef = (0, import_react18.useRef)(null);
3097
3154
  const previousOriginRef = (0, import_react18.useRef)(trackList?.origin ?? null);
@@ -3180,25 +3237,25 @@ function TrackListSidebar({
3180
3237
  }
3181
3238
  };
3182
3239
  const positionClasses = layout === "inline" ? "relative w-full flex h-full flex-col" : "fixed right-0 w-[50rem] flex flex-col";
3183
- return trackList ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
3240
+ return trackList ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
3184
3241
  "div",
3185
3242
  {
3186
3243
  "data-theme": "dark",
3187
3244
  className: `track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${className2}`,
3188
3245
  style: layout === "fixed" ? { top: "63.5px", maxHeight: "calc(100vh - 63.5px - 79px)" } : void 0,
3189
3246
  children: [
3190
- /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
3191
- /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
3192
- /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "origin flex text-xl", children: [
3193
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
3194
- /* @__PURE__ */ (0, import_jsx_runtime36.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_runtime36.jsx)("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
3247
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
3248
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
3249
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "origin flex text-xl", children: [
3250
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
3251
+ /* @__PURE__ */ (0, import_jsx_runtime40.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_runtime40.jsx)("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
3195
3252
  ] }),
3196
- /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
3197
- 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 `,
3253
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
3254
+ trackList.origin.type === import_genre_tree24.TrackListOriginType.GENRE_PLAYLIST ? "Genre \u2022 " : trackList.origin.type === import_genre_tree24.TrackListOriginType.ARTIST ? "Artist \u2022 " : `${trackNoun.singular} playlist \u2022 `,
3198
3255
  `${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`
3199
3256
  ] })
3200
3257
  ] }),
3201
- (isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
3258
+ (isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3202
3259
  "button",
3203
3260
  {
3204
3261
  type: "button",
@@ -3207,11 +3264,11 @@ function TrackListSidebar({
3207
3264
  "aria-expanded": !isCollapsed,
3208
3265
  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"}`,
3209
3266
  onClick: isCollapsed ? onExpand : onMinimize,
3210
- children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_lucide_react14.ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
3267
+ children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react14.ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
3211
3268
  }
3212
3269
  )
3213
3270
  ] }),
3214
- !isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
3271
+ !isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
3215
3272
  "ul",
3216
3273
  {
3217
3274
  ref: trackListRef,
@@ -3221,7 +3278,7 @@ function TrackListSidebar({
3221
3278
  onPointerDown: markUserScrollIntent,
3222
3279
  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",
3223
3280
  children: [
3224
- trackList.tracks.map((track, index) => /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
3281
+ trackList.tracks.map((track, index) => /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3225
3282
  TrackItem,
3226
3283
  {
3227
3284
  track,
@@ -3232,7 +3289,7 @@ function TrackListSidebar({
3232
3289
  renderSongLink
3233
3290
  }
3234
3291
  ) }, track.uuid)),
3235
- hasMore && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
3292
+ hasMore && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
3236
3293
  ]
3237
3294
  }
3238
3295
  )
@@ -3245,14 +3302,14 @@ function TrackListSidebar({
3245
3302
  var import_react19 = require("react");
3246
3303
  var import_tailwind_merge = require("tailwind-merge");
3247
3304
  var import_player13 = require("@behindthemusictree/app-kit/player");
3248
- var import_genre_tree24 = require("@behindthemusictree/app-kit/genre-tree");
3249
- var import_ui17 = require("@behindthemusictree/ui");
3305
+ var import_genre_tree26 = require("@behindthemusictree/app-kit/genre-tree");
3306
+ var import_ui18 = require("@behindthemusictree/ui");
3250
3307
 
3251
3308
  // src/genre-tree/PlayerControls.tsx
3252
3309
  var import_lucide_react15 = require("lucide-react");
3253
- var import_genre_tree23 = require("@behindthemusictree/app-kit/genre-tree");
3310
+ var import_genre_tree25 = require("@behindthemusictree/app-kit/genre-tree");
3254
3311
  var import_player12 = require("@behindthemusictree/app-kit/player");
3255
- var import_jsx_runtime37 = require("react/jsx-runtime");
3312
+ var import_jsx_runtime41 = require("react/jsx-runtime");
3256
3313
  function PlayerControls() {
3257
3314
  const {
3258
3315
  duration,
@@ -3264,22 +3321,22 @@ function PlayerControls() {
3264
3321
  setVolume,
3265
3322
  volume
3266
3323
  } = (0, import_player12.usePlayer)();
3267
- const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } = (0, import_genre_tree23.useTrackList)();
3324
+ const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } = (0, import_genre_tree25.useTrackList)();
3268
3325
  const currentTime = (0, import_player12.useCurrentTime)();
3269
3326
  const isBusy = isLoading || playState === import_player12.PlayStates.LOADING;
3270
3327
  const isReady = !!playerTrackObject?.isReady && !isBusy && !playerTrackObject.loadError;
3271
3328
  const safeDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;
3272
3329
  const safeCurrentTime = Number.isFinite(currentTime) ? Math.max(0, Math.min(currentTime, safeDuration)) : 0;
3273
3330
  const isPlaying = playState === import_player12.PlayStates.PLAYING;
3274
- return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
3331
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
3275
3332
  "section",
3276
3333
  {
3277
3334
  "aria-label": "Player controls",
3278
3335
  className: "player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200",
3279
3336
  children: [
3280
- /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
3281
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { "aria-label": "Elapsed time", children: (0, import_genre_tree23.formatTime)(safeCurrentTime) }),
3282
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
3337
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
3338
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { "aria-label": "Elapsed time", children: (0, import_genre_tree25.formatTime)(safeCurrentTime) }),
3339
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3283
3340
  "input",
3284
3341
  {
3285
3342
  "aria-label": "Seek",
@@ -3293,11 +3350,11 @@ function PlayerControls() {
3293
3350
  onChange: (event) => seekTo(Number(event.currentTarget.value))
3294
3351
  }
3295
3352
  ),
3296
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { "aria-label": "Duration", children: (0, import_genre_tree23.formatTime)(safeDuration) })
3353
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { "aria-label": "Duration", children: (0, import_genre_tree25.formatTime)(safeDuration) })
3297
3354
  ] }),
3298
- /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
3299
- /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
3300
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
3355
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
3356
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
3357
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3301
3358
  "button",
3302
3359
  {
3303
3360
  type: "button",
@@ -3306,10 +3363,10 @@ function PlayerControls() {
3306
3363
  disabled: !isReady || !hasPreviousTrack,
3307
3364
  onClick: playPreviousTrack,
3308
3365
  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",
3309
- children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react15.SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
3366
+ children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_lucide_react15.SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
3310
3367
  }
3311
3368
  ),
3312
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
3369
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3313
3370
  "button",
3314
3371
  {
3315
3372
  type: "button",
@@ -3318,10 +3375,10 @@ function PlayerControls() {
3318
3375
  disabled: !isReady,
3319
3376
  onClick: handlePlayPauseAction,
3320
3377
  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",
3321
- children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react15.Pause, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react15.Play, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
3378
+ children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_lucide_react15.Pause, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_lucide_react15.Play, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
3322
3379
  }
3323
3380
  ),
3324
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
3381
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3325
3382
  "button",
3326
3383
  {
3327
3384
  type: "button",
@@ -3330,14 +3387,14 @@ function PlayerControls() {
3330
3387
  disabled: !isReady || !hasNextTrack,
3331
3388
  onClick: playNextTrack,
3332
3389
  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",
3333
- children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react15.SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
3390
+ children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_lucide_react15.SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
3334
3391
  }
3335
3392
  )
3336
3393
  ] }),
3337
- /* @__PURE__ */ (0, import_jsx_runtime37.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: [
3338
- volume === 0 ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react15.VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react15.Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
3339
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "sr-only", children: "Volume" }),
3340
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
3394
+ /* @__PURE__ */ (0, import_jsx_runtime41.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: [
3395
+ volume === 0 ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_lucide_react15.VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_lucide_react15.Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
3396
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "sr-only", children: "Volume" }),
3397
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3341
3398
  "input",
3342
3399
  {
3343
3400
  "aria-label": "Volume",
@@ -3359,7 +3416,7 @@ function PlayerControls() {
3359
3416
  }
3360
3417
 
3361
3418
  // src/genre-tree/PlayerPanel.tsx
3362
- var import_jsx_runtime38 = require("react/jsx-runtime");
3419
+ var import_jsx_runtime42 = require("react/jsx-runtime");
3363
3420
  var PANEL_ID = "player-panel";
3364
3421
  var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
3365
3422
  function PlayerPanel({
@@ -3372,7 +3429,7 @@ function PlayerPanel({
3372
3429
  renderSongLink
3373
3430
  }) {
3374
3431
  const { playerTrackObject, retryPlayback } = (0, import_player13.usePlayer)();
3375
- const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0, import_genre_tree24.useTrackListSidebarVisibility)();
3432
+ const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0, import_genre_tree26.useTrackListSidebarVisibility)();
3376
3433
  const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = (0, import_react19.useState)("");
3377
3434
  const loadedTrackTitle = playerTrackObject?.track.title;
3378
3435
  (0, import_react19.useEffect)(() => {
@@ -3399,7 +3456,7 @@ function PlayerPanel({
3399
3456
  onClick: () => setIsExtendRequested(true),
3400
3457
  expanded: false
3401
3458
  } : null;
3402
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
3459
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
3403
3460
  "div",
3404
3461
  {
3405
3462
  id: PANEL_ID,
@@ -3412,8 +3469,8 @@ function PlayerPanel({
3412
3469
  ),
3413
3470
  style: isExtended ? { width: extendedWidth } : void 0,
3414
3471
  children: [
3415
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
3416
- import_ui17.PanelEdgeTab,
3472
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
3473
+ import_ui18.PanelEdgeTab,
3417
3474
  {
3418
3475
  side: "right",
3419
3476
  edge: tabEdge,
@@ -3422,7 +3479,7 @@ function PlayerPanel({
3422
3479
  className: "absolute right-full top-1/2 -translate-y-1/2"
3423
3480
  }
3424
3481
  ),
3425
- /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
3482
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
3426
3483
  "div",
3427
3484
  {
3428
3485
  className: (0, import_tailwind_merge.twMerge)(
@@ -3430,20 +3487,20 @@ function PlayerPanel({
3430
3487
  isExtended ? "flex-row" : "flex-col"
3431
3488
  ),
3432
3489
  children: [
3433
- /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
3434
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
3435
- /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "relative w-full shrink-0", children: [
3436
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_player13.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
3437
- playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: [
3490
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
3491
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
3492
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "relative w-full shrink-0", children: [
3493
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_player13.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
3494
+ playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: [
3438
3495
  playerTrackObject.loadError,
3439
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("button", { type: "button", className: "ml-2 underline", onClick: () => {
3496
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("button", { type: "button", className: "ml-2 underline", onClick: () => {
3440
3497
  void retryPlayback();
3441
3498
  }, children: "Retry" })
3442
3499
  ] })
3443
3500
  ] }),
3444
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(PlayerControls, {})
3501
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(PlayerControls, {})
3445
3502
  ] }),
3446
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
3503
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
3447
3504
  TrackListSidebar,
3448
3505
  {
3449
3506
  layout: "inline",
@@ -3469,33 +3526,33 @@ function PlayerPanel({
3469
3526
  // src/genre-tree/Rating.tsx
3470
3527
  var import_uuid = require("uuid");
3471
3528
  var import_ai = require("react-icons/ai");
3472
- var import_genre_tree25 = require("@behindthemusictree/app-kit/genre-tree");
3473
- var import_jsx_runtime39 = require("react/jsx-runtime");
3529
+ var import_genre_tree27 = require("@behindthemusictree/app-kit/genre-tree");
3530
+ var import_jsx_runtime43 = require("react/jsx-runtime");
3474
3531
  function Rating({ rating, handleChange }) {
3475
3532
  const instanceId = (0, import_uuid.v4)();
3476
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
3477
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
3533
+ return /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
3534
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
3478
3535
  "input",
3479
3536
  {
3480
3537
  id: `star-${instanceId}-${i}`,
3481
3538
  className: "w-6 h-6 opacity-0",
3482
3539
  type: "radio",
3483
3540
  name: "rating",
3484
- value: i === 0 ? import_genre_tree25.FORM_RATING_NULL_VALUE : i,
3541
+ value: i === 0 ? import_genre_tree27.FORM_RATING_NULL_VALUE : i,
3485
3542
  checked: rating === (i === 0 ? null : i),
3486
3543
  onChange: handleChange
3487
3544
  }
3488
3545
  ),
3489
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex", children: [
3490
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
3546
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "flex", children: [
3547
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
3491
3548
  import_ai.AiOutlineStar,
3492
3549
  {
3493
- 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)]"}`
3550
+ className: `w-7 h-7 absolute top-0 left-0 ${rating === import_genre_tree27.FORM_RATING_NULL_VALUE ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
3494
3551
  }
3495
3552
  ),
3496
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "flex-grow w-8" }),
3553
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "flex-grow w-8" }),
3497
3554
  " "
3498
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
3555
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
3499
3556
  import_ai.AiFillStar,
3500
3557
  {
3501
3558
  className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
@@ -3517,9 +3574,15 @@ function Rating({ rating, handleChange }) {
3517
3574
  GenreTreeWheelSkeleton,
3518
3575
  PlayerPanel,
3519
3576
  Rating,
3577
+ SEARCH_FAILED_STATUS,
3578
+ SearchBox,
3520
3579
  TrackItem,
3521
3580
  TrackListSidebar,
3522
3581
  TrackPositionPlayPause,
3523
- useGenrePlaylistPlayback
3582
+ searchStatus,
3583
+ useArtistSearchGroup,
3584
+ useGenrePlaylistPlayback,
3585
+ useGenreRemoteSearchGroup,
3586
+ useSongSearchGroup
3524
3587
  });
3525
3588
  //# sourceMappingURL=index.cjs.map