@behindthemusictree/app-kit-ui 34.0.0 → 34.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -46,7 +46,7 @@ __export(genre_tree_exports, {
46
46
  module.exports = __toCommonJS(genre_tree_exports);
47
47
 
48
48
  // src/genre-tree/GenreTreeView.tsx
49
- var import_react16 = require("react");
49
+ var import_react17 = require("react");
50
50
  var import_react_query3 = require("@tanstack/react-query");
51
51
  var import_lucide_react11 = require("lucide-react");
52
52
  var import_lucide_react12 = require("lucide-react");
@@ -702,6 +702,9 @@ function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
702
702
  ] });
703
703
  }
704
704
 
705
+ // src/genre-tree/GenreTreeSearch.tsx
706
+ var import_react8 = require("react");
707
+
705
708
  // src/genre-tree/GenreSearch.tsx
706
709
  var import_lucide_react3 = require("lucide-react");
707
710
  var import_ui4 = require("@behindthemusictree/ui");
@@ -902,6 +905,44 @@ function GenreSearch({
902
905
  ) });
903
906
  }
904
907
 
908
+ // src/genre-tree/GenreTreeSearch.tsx
909
+ var import_jsx_runtime14 = require("react/jsx-runtime");
910
+ function GenreTreeSearch({
911
+ artistBaseUrl,
912
+ songBaseUrl,
913
+ onArtistSelect,
914
+ onSongSelect,
915
+ artistSearch,
916
+ songSearch,
917
+ SearchGroups,
918
+ extraGroups = [],
919
+ onQueryChange,
920
+ onFocus,
921
+ ...props
922
+ }) {
923
+ const [query, setQuery] = (0, import_react8.useState)("");
924
+ const [focused, setFocused] = (0, import_react8.useState)(false);
925
+ const deferredQuery = (0, import_react8.useDeferredValue)(query);
926
+ const artistGroup = useArtistSearchGroup(artistBaseUrl, deferredQuery, onArtistSelect, artistSearch);
927
+ const songGroup = useSongSearchGroup(songBaseUrl, deferredQuery, onSongSelect, songSearch);
928
+ const render = (hostGroups) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
929
+ GenreSearch,
930
+ {
931
+ ...props,
932
+ onQueryChange: (text) => {
933
+ setQuery(text);
934
+ onQueryChange?.(text);
935
+ },
936
+ onFocus: () => {
937
+ setFocused(true);
938
+ onFocus?.();
939
+ },
940
+ extraGroups: [...extraGroups, ...hostGroups, artistGroup, songGroup].filter((group) => group !== void 0)
941
+ }
942
+ );
943
+ return SearchGroups ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SearchGroups, { query: deferredQuery, focused, children: render }) : render([]);
944
+ }
945
+
905
946
  // src/genre-tree/GenreInfoPanelBody.tsx
906
947
  var import_genre_tree18 = require("@behindthemusictree/app-kit/genre-tree");
907
948
  var import_track_noun3 = require("@behindthemusictree/app-kit/track-noun");
@@ -915,25 +956,25 @@ var import_lucide_react6 = require("lucide-react");
915
956
  var import_popup3 = require("@behindthemusictree/app-kit/popup");
916
957
 
917
958
  // src/genre-tree/SharePopup.tsx
918
- var import_react9 = require("react");
959
+ var import_react10 = require("react");
919
960
  var import_lucide_react5 = require("lucide-react");
920
961
  var import_fa = require("react-icons/fa");
921
962
  var import_si = require("react-icons/si");
922
963
 
923
964
  // src/popup/Toast.tsx
924
- var import_react8 = require("react");
965
+ var import_react9 = require("react");
925
966
  var import_react_dom2 = require("react-dom");
926
- var import_jsx_runtime14 = require("react/jsx-runtime");
967
+ var import_jsx_runtime15 = require("react/jsx-runtime");
927
968
  function Toast({ message, onClose, durationMs = 2200 }) {
928
- const [mounted, setMounted] = (0, import_react8.useState)(false);
929
- (0, import_react8.useEffect)(() => {
969
+ const [mounted, setMounted] = (0, import_react9.useState)(false);
970
+ (0, import_react9.useEffect)(() => {
930
971
  setMounted(true);
931
972
  const timeout = window.setTimeout(onClose, durationMs);
932
973
  return () => window.clearTimeout(timeout);
933
974
  }, [durationMs, onClose]);
934
975
  if (!mounted) return null;
935
976
  return (0, import_react_dom2.createPortal)(
936
- /* @__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)(
977
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "pointer-events-none fixed inset-x-0 bottom-8 z-[60] flex justify-center px-4", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
937
978
  "div",
938
979
  {
939
980
  role: "status",
@@ -948,12 +989,12 @@ function Toast({ message, onClose, durationMs = 2200 }) {
948
989
  }
949
990
 
950
991
  // src/genre-tree/ShareDestinationButton.tsx
951
- var import_jsx_runtime15 = require("react/jsx-runtime");
992
+ var import_jsx_runtime16 = require("react/jsx-runtime");
952
993
  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";
953
994
  function Contents({ icon, label }) {
954
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
955
- /* @__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 }),
956
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "max-w-full truncate", children: label })
995
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
996
+ /* @__PURE__ */ (0, import_jsx_runtime16.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 }),
997
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "max-w-full truncate", children: label })
957
998
  ] });
958
999
  }
959
1000
  function ShareDestinationButton({
@@ -965,29 +1006,29 @@ function ShareDestinationButton({
965
1006
  ariaLabel
966
1007
  }) {
967
1008
  if (href) {
968
- 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 }) });
1009
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", "aria-label": ariaLabel, className, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Contents, { icon, label }) });
969
1010
  }
970
- 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 }) });
1011
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", onClick, disabled, "aria-label": ariaLabel, className, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Contents, { icon, label }) });
971
1012
  }
972
1013
 
973
1014
  // src/genre-tree/SharePreviewCard.tsx
974
1015
  var import_lucide_react4 = require("lucide-react");
975
- var import_jsx_runtime16 = require("react/jsx-runtime");
1016
+ var import_jsx_runtime17 = require("react/jsx-runtime");
976
1017
  function SharePreviewCard({ target, url }) {
977
1018
  const Icon = { genre: import_lucide_react4.Music2, artist: import_lucide_react4.UserRound, song: import_lucide_react4.Music }[target.kind];
978
1019
  const typeLabel = { genre: "Genre", artist: "Artist", song: "Song" }[target.kind];
979
- 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: [
980
- /* @__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" }) }),
981
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col justify-center gap-1 p-4", children: [
982
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-xs font-medium uppercase tracking-wide text-[var(--color-text-muted)]", children: typeLabel }),
983
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "truncate text-base font-semibold text-[var(--color-text)]", title: target.name, children: target.name }),
984
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "truncate text-sm text-[var(--color-primary)]", title: url, children: url })
1020
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "rounded-3xl bg-[var(--color-surface-muted)] p-4", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex min-h-32 overflow-hidden rounded-2xl bg-[var(--color-surface)] shadow-lg", children: [
1021
+ /* @__PURE__ */ (0, import_jsx_runtime17.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_runtime17.jsx)(Icon, { "aria-hidden": "true", className: "h-10 w-10" }) }),
1022
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col justify-center gap-1 p-4", children: [
1023
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-xs font-medium uppercase tracking-wide text-[var(--color-text-muted)]", children: typeLabel }),
1024
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "truncate text-base font-semibold text-[var(--color-text)]", title: target.name, children: target.name }),
1025
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "truncate text-sm text-[var(--color-primary)]", title: url, children: url })
985
1026
  ] })
986
1027
  ] }) });
987
1028
  }
988
1029
 
989
1030
  // src/genre-tree/SharePopup.tsx
990
- var import_jsx_runtime17 = require("react/jsx-runtime");
1031
+ var import_jsx_runtime18 = require("react/jsx-runtime");
991
1032
  function resolveAbsoluteUrl(url) {
992
1033
  try {
993
1034
  const parsed = new URL(url, window.location.href);
@@ -1020,20 +1061,20 @@ async function copyText(text) {
1020
1061
  }
1021
1062
  }
1022
1063
  function SharePopup({ target, url, onClose }) {
1023
- const [absoluteUrl, setAbsoluteUrl] = (0, import_react9.useState)(url);
1024
- const [copyStatus, setCopyStatus] = (0, import_react9.useState)("idle");
1025
- const [canUseNativeShare, setCanUseNativeShare] = (0, import_react9.useState)(false);
1026
- const [shareError, setShareError] = (0, import_react9.useState)(null);
1027
- const [toastId, setToastId] = (0, import_react9.useState)(0);
1028
- (0, import_react9.useEffect)(() => {
1064
+ const [absoluteUrl, setAbsoluteUrl] = (0, import_react10.useState)(url);
1065
+ const [copyStatus, setCopyStatus] = (0, import_react10.useState)("idle");
1066
+ const [canUseNativeShare, setCanUseNativeShare] = (0, import_react10.useState)(false);
1067
+ const [shareError, setShareError] = (0, import_react10.useState)(null);
1068
+ const [toastId, setToastId] = (0, import_react10.useState)(0);
1069
+ (0, import_react10.useEffect)(() => {
1029
1070
  setAbsoluteUrl(resolveAbsoluteUrl(url) ?? "");
1030
1071
  setCopyStatus("idle");
1031
1072
  setShareError(null);
1032
1073
  }, [url]);
1033
- (0, import_react9.useEffect)(() => {
1074
+ (0, import_react10.useEffect)(() => {
1034
1075
  setCanUseNativeShare(typeof navigator !== "undefined" && typeof navigator.share === "function");
1035
1076
  }, []);
1036
- const destinationUrls = (0, import_react9.useMemo)(() => {
1077
+ const destinationUrls = (0, import_react10.useMemo)(() => {
1037
1078
  if (!absoluteUrl) return null;
1038
1079
  const message = `${target.name} \u2014 ${absoluteUrl}`;
1039
1080
  return {
@@ -1051,7 +1092,7 @@ function SharePopup({ target, url, onClose }) {
1051
1092
  setCopyStatus(copied ? "idle" : "failed");
1052
1093
  if (copied) setToastId((id) => id + 1);
1053
1094
  };
1054
- const dismissToast = (0, import_react9.useCallback)(() => setToastId(0), []);
1095
+ const dismissToast = (0, import_react10.useCallback)(() => setToastId(0), []);
1055
1096
  const handleNativeShare = async () => {
1056
1097
  if (!absoluteUrl || !navigator.share) return;
1057
1098
  setShareError(null);
@@ -1062,8 +1103,8 @@ function SharePopup({ target, url, onClose }) {
1062
1103
  setShareError("Could not open the share options.");
1063
1104
  }
1064
1105
  };
1065
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
1066
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1106
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
1107
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1067
1108
  BasePopup_default,
1068
1109
  {
1069
1110
  title: "Share",
@@ -1078,67 +1119,67 @@ function SharePopup({ target, url, onClose }) {
1078
1119
  titleTextClassName: "!text-xl !font-semibold !text-[var(--color-text)]",
1079
1120
  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)]",
1080
1121
  contentClassName: "!rounded-none !bg-transparent !px-6 !pb-6 !pt-2",
1081
- children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex flex-col gap-4", children: [
1082
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SharePreviewCard, { target, url: absoluteUrl || url }),
1083
- /* @__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: [
1084
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1122
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex flex-col gap-4", children: [
1123
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(SharePreviewCard, { target, url: absoluteUrl || url }),
1124
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "grid grid-cols-3 gap-1 rounded-3xl bg-[var(--color-surface)] p-3 sm:grid-cols-4", children: [
1125
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1085
1126
  ShareDestinationButton,
1086
1127
  {
1087
1128
  label: "Copy Link",
1088
- icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.Link2, { "aria-hidden": "true", className: "h-6 w-6" }),
1129
+ icon: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react5.Link2, { "aria-hidden": "true", className: "h-6 w-6" }),
1089
1130
  onClick: () => void handleCopy(),
1090
1131
  ariaLabel: "Copy link",
1091
1132
  disabled: !absoluteUrl
1092
1133
  }
1093
1134
  ),
1094
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1135
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1095
1136
  ShareDestinationButton,
1096
1137
  {
1097
1138
  label: "WhatsApp",
1098
- icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_fa.FaWhatsapp, { "aria-hidden": "true", className: "h-9 w-9 text-[#25D366]" }),
1139
+ icon: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_fa.FaWhatsapp, { "aria-hidden": "true", className: "h-9 w-9 text-[#25D366]" }),
1099
1140
  href: destinationUrls?.whatsapp,
1100
1141
  ariaLabel: `Share ${target.name} via WhatsApp`,
1101
1142
  disabled: !absoluteUrl
1102
1143
  }
1103
1144
  ),
1104
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1145
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1105
1146
  ShareDestinationButton,
1106
1147
  {
1107
1148
  label: "Gmail",
1108
- icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_si.SiGmail, { "aria-hidden": "true", className: "h-8 w-8 text-[#EA4335]" }),
1149
+ icon: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_si.SiGmail, { "aria-hidden": "true", className: "h-8 w-8 text-[#EA4335]" }),
1109
1150
  href: destinationUrls?.gmail,
1110
1151
  ariaLabel: `Email ${target.name} with Gmail`,
1111
1152
  disabled: !absoluteUrl
1112
1153
  }
1113
1154
  ),
1114
- canUseNativeShare && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1155
+ canUseNativeShare && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1115
1156
  ShareDestinationButton,
1116
1157
  {
1117
1158
  label: "More",
1118
- icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.Share2, { "aria-hidden": "true", className: "h-6 w-6" }),
1159
+ icon: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react5.Share2, { "aria-hidden": "true", className: "h-6 w-6" }),
1119
1160
  onClick: () => void handleNativeShare(),
1120
1161
  ariaLabel: "More sharing options",
1121
1162
  disabled: !absoluteUrl
1122
1163
  }
1123
1164
  )
1124
1165
  ] }),
1125
- 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." }),
1126
- shareError && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: shareError })
1166
+ copyStatus === "failed" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: "Could not copy the link." }),
1167
+ shareError && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: shareError })
1127
1168
  ] })
1128
1169
  }
1129
1170
  ),
1130
- toastId > 0 && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Toast, { message: "Link copied", onClose: dismissToast }, toastId)
1171
+ toastId > 0 && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Toast, { message: "Link copied", onClose: dismissToast }, toastId)
1131
1172
  ] });
1132
1173
  }
1133
1174
 
1134
1175
  // src/genre-tree/ShareActionButton.tsx
1135
- var import_jsx_runtime18 = require("react/jsx-runtime");
1176
+ var import_jsx_runtime19 = require("react/jsx-runtime");
1136
1177
  function ShareActionButton({ target, url }) {
1137
1178
  const { showPopup, hidePopup } = (0, import_popup3.usePopup)();
1138
1179
  const openSharePopup = () => {
1139
- showPopup(/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(SharePopup, { target, url, onClose: hidePopup }));
1180
+ showPopup(/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(SharePopup, { target, url, onClose: hidePopup }));
1140
1181
  };
1141
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
1182
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
1142
1183
  "button",
1143
1184
  {
1144
1185
  type: "button",
@@ -1147,7 +1188,7 @@ function ShareActionButton({ target, url }) {
1147
1188
  "aria-label": `Share ${target.name}`,
1148
1189
  title: `Share ${target.name}`,
1149
1190
  children: [
1150
- /* @__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" }) }),
1191
+ /* @__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)]", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react6.Share2, { "aria-hidden": "true", className: "w-5 h-5" }) }),
1151
1192
  "Share"
1152
1193
  ]
1153
1194
  }
@@ -1155,7 +1196,7 @@ function ShareActionButton({ target, url }) {
1155
1196
  }
1156
1197
 
1157
1198
  // src/genre-tree/GenreActionBand.tsx
1158
- var import_jsx_runtime19 = require("react/jsx-runtime");
1199
+ var import_jsx_runtime20 = require("react/jsx-runtime");
1159
1200
  function GenreActionBand({
1160
1201
  node,
1161
1202
  onPlayPause,
@@ -1177,7 +1218,7 @@ function GenreActionBand({
1177
1218
  items.push({
1178
1219
  key: "play-pause",
1179
1220
  label: isPlaying ? "Pause" : "Play",
1180
- 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" }),
1221
+ icon: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react7.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react7.Play, { "aria-hidden": "true", className: "w-5 h-5" }),
1181
1222
  onClick: onPlayPause
1182
1223
  });
1183
1224
  }
@@ -1185,24 +1226,24 @@ function GenreActionBand({
1185
1226
  items.push({
1186
1227
  key: "zoom-in",
1187
1228
  label: "Focus",
1188
- icon: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react7.ZoomIn, { "aria-hidden": "true", className: "w-5 h-5" }),
1229
+ icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react7.ZoomIn, { "aria-hidden": "true", className: "w-5 h-5" }),
1189
1230
  onClick: onZoomIn
1190
1231
  });
1191
1232
  }
1192
1233
  if (onAddChild) {
1193
- 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 });
1234
+ adminItems.push({ key: "add-child", label: "Add", icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react7.Plus, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onAddChild });
1194
1235
  }
1195
1236
  if (onRename) {
1196
- 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 });
1237
+ adminItems.push({ key: "rename", label: "Rename", icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react7.Pencil, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onRename });
1197
1238
  }
1198
1239
  if (onChangeParent) {
1199
- 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 });
1240
+ adminItems.push({ key: "change-parent", label: "Parent", icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react7.MoveUpRight, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onChangeParent });
1200
1241
  }
1201
1242
  if (onToggleSide) {
1202
1243
  adminItems.push({
1203
1244
  key: "toggle-side",
1204
1245
  label: side === "pop" ? "Make core" : "Make pop",
1205
- icon: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react7.ArrowLeftRight, { "aria-hidden": "true", className: "w-5 h-5" }),
1246
+ icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react7.ArrowLeftRight, { "aria-hidden": "true", className: "w-5 h-5" }),
1206
1247
  onClick: onToggleSide,
1207
1248
  disabled: !!toggleSideDisabledReason,
1208
1249
  title: toggleSideDisabledReason
@@ -1218,7 +1259,7 @@ function GenreActionBand({
1218
1259
  });
1219
1260
  }
1220
1261
  if (items.length === 0 && (!shareTarget || !shareUrl) && adminItems.length === 0) return null;
1221
- const renderItems = (list) => list.map((item) => /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
1262
+ const renderItems = (list) => list.map((item) => /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
1222
1263
  "button",
1223
1264
  {
1224
1265
  type: "button",
@@ -1227,25 +1268,25 @@ function GenreActionBand({
1227
1268
  onClick: item.onClick,
1228
1269
  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",
1229
1270
  children: [
1230
- /* @__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 }),
1271
+ /* @__PURE__ */ (0, import_jsx_runtime20.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 }),
1231
1272
  item.label
1232
1273
  ]
1233
1274
  },
1234
1275
  item.key
1235
1276
  ));
1236
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: [
1237
- (items.length > 0 || shareTarget && shareUrl) && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
1277
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: [
1278
+ (items.length > 0 || shareTarget && shareUrl) && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
1238
1279
  renderItems(items),
1239
- shareTarget && shareUrl && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
1280
+ shareTarget && shareUrl && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
1240
1281
  ] }),
1241
- adminItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
1282
+ adminItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
1242
1283
  "section",
1243
1284
  {
1244
1285
  "aria-label": "Admin actions",
1245
1286
  className: `px-3.5 py-3 ${items.length > 0 ? "border-t border-[var(--color-border)]" : ""}`,
1246
1287
  children: [
1247
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("h3", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-[var(--color-text-muted)]", children: "Admin" }),
1248
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "flex flex-wrap gap-1", children: renderItems(adminItems) })
1288
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("h3", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-[var(--color-text-muted)]", children: "Admin" }),
1289
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "flex flex-wrap gap-1", children: renderItems(adminItems) })
1249
1290
  ]
1250
1291
  }
1251
1292
  )
@@ -1257,20 +1298,20 @@ var import_ui6 = require("@behindthemusictree/ui");
1257
1298
  var import_genre_tree17 = require("@behindthemusictree/app-kit/genre-tree");
1258
1299
 
1259
1300
  // src/genre-tree/ClampedText.tsx
1260
- var import_react10 = require("react");
1261
- var import_jsx_runtime20 = require("react/jsx-runtime");
1301
+ var import_react11 = require("react");
1302
+ var import_jsx_runtime21 = require("react/jsx-runtime");
1262
1303
  function ClampedText({ children, className: className2 = "" }) {
1263
- const ref = (0, import_react10.useRef)(null);
1264
- const [expanded, setExpanded] = (0, import_react10.useState)(false);
1265
- const [overflows, setOverflows] = (0, import_react10.useState)(false);
1266
- (0, import_react10.useLayoutEffect)(() => {
1304
+ const ref = (0, import_react11.useRef)(null);
1305
+ const [expanded, setExpanded] = (0, import_react11.useState)(false);
1306
+ const [overflows, setOverflows] = (0, import_react11.useState)(false);
1307
+ (0, import_react11.useLayoutEffect)(() => {
1267
1308
  const element = ref.current;
1268
1309
  if (!element || expanded) return;
1269
1310
  setOverflows(element.scrollHeight > element.clientHeight);
1270
1311
  }, [children, expanded]);
1271
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
1272
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { ref, className: `${className2} ${expanded ? "" : "line-clamp-3"}`, children }),
1273
- (overflows || expanded) && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
1312
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
1313
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { ref, className: `${className2} ${expanded ? "" : "line-clamp-3"}`, children }),
1314
+ (overflows || expanded) && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1274
1315
  "button",
1275
1316
  {
1276
1317
  type: "button",
@@ -1284,7 +1325,7 @@ function ClampedText({ children, className: className2 = "" }) {
1284
1325
  }
1285
1326
 
1286
1327
  // src/genre-tree/GenrePlaylistTracks.tsx
1287
- var import_react11 = require("react");
1328
+ var import_react12 = require("react");
1288
1329
  var import_react_query = require("@tanstack/react-query");
1289
1330
  var import_zod = require("zod");
1290
1331
  var import_fa2 = require("react-icons/fa");
@@ -1305,15 +1346,15 @@ var import_genre_tree15 = require("@behindthemusictree/app-kit/genre-tree");
1305
1346
 
1306
1347
  // src/genre-tree/MusicBrainzRecordingLink.tsx
1307
1348
  var import_genre_tree10 = require("@behindthemusictree/app-kit/genre-tree");
1308
- var import_jsx_runtime21 = require("react/jsx-runtime");
1349
+ var import_jsx_runtime22 = require("react/jsx-runtime");
1309
1350
  function MusicBrainzRecordingLink({
1310
1351
  song,
1311
1352
  children,
1312
1353
  className: className2 = ""
1313
1354
  }) {
1314
1355
  const url = (0, import_genre_tree10.musicBrainzRecordingUrl)(song);
1315
- if (!url) return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_jsx_runtime21.Fragment, { children });
1316
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1356
+ if (!url) return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_jsx_runtime22.Fragment, { children });
1357
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1317
1358
  "a",
1318
1359
  {
1319
1360
  href: url,
@@ -1330,7 +1371,7 @@ function MusicBrainzRecordingLink({
1330
1371
  // src/genre-tree/GenrePlaylistTracks.tsx
1331
1372
  var import_genre_tree16 = require("@behindthemusictree/app-kit/genre-tree");
1332
1373
  var import_track_noun = require("@behindthemusictree/app-kit/track-noun");
1333
- var import_jsx_runtime22 = require("react/jsx-runtime");
1374
+ var import_jsx_runtime23 = require("react/jsx-runtime");
1334
1375
  var CATALOGUE_PAGE_SIZE = 100;
1335
1376
  var SongPageSchema = (0, import_transport4.PaginatedResponseSchema)(import_genre_tree14.SongDetailedSchema).extend({
1336
1377
  overallTotal: import_zod.z.number().nullable(),
@@ -1386,8 +1427,8 @@ function GenrePlaylistTracks({
1386
1427
  getNextPageParam: (lastPage) => lastPage.nextPage ?? void 0,
1387
1428
  enabled: playback.ready && (scope !== "me" || sessionRestored && !!session?.accessToken)
1388
1429
  });
1389
- const [sentinel, setSentinel] = (0, import_react11.useState)(null);
1390
- (0, import_react11.useEffect)(() => {
1430
+ const [sentinel, setSentinel] = (0, import_react12.useState)(null);
1431
+ (0, import_react12.useEffect)(() => {
1391
1432
  if (!hasNextPage || isFetchingNextPage || !sentinel) return;
1392
1433
  const observer = new IntersectionObserver((entries) => {
1393
1434
  if (entries.some((entry) => entry.isIntersecting)) {
@@ -1409,35 +1450,35 @@ function GenrePlaylistTracks({
1409
1450
  }) : scope === "reference" ? playNewTrackListFromGenreCatalogue({ uuid: genreUuid, name: genrePlaylist.name }, scope, { startSongUuid, ...selection && { selection } }) : playNewTrackListFromGenrePlaylist(genrePlaylist, scope, { startSongUuid });
1410
1451
  started.catch((error) => {
1411
1452
  console.error("Failed to play genre playlist:", error);
1412
- showPopup(/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(InternalErrorPopup, { errorCode: import_transport6.ErrorCode.CLIENT_UNKNOWN }));
1453
+ showPopup(/* @__PURE__ */ (0, import_jsx_runtime23.jsx)(InternalErrorPopup, { errorCode: import_transport6.ErrorCode.CLIENT_UNKNOWN }));
1413
1454
  });
1414
1455
  };
1415
- 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: [
1456
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ui5.PanelSection, { title: `${(0, import_track_noun.capitalize)(trackNoun.plural)}${total !== void 0 ? ` (${total})` : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { children: [
1416
1457
  renderTracksToolbar?.(),
1417
- 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: [
1458
+ isPending ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ui5.Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("p", { role: "alert", children: [
1418
1459
  "Couldn't load ",
1419
1460
  trackNoun.plural,
1420
1461
  ".",
1421
1462
  " ",
1422
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
1423
- ] }) : 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: [
1463
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
1464
+ ] }) : tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { children: "\u2014" }) : /* @__PURE__ */ (0, import_jsx_runtime23.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: [
1424
1465
  tracks.map((track, index) => {
1425
1466
  const label = track.artists?.length ? `${track.title} \u2014 ${track.artists.map((artist2) => artist2.name).join(", ")}` : track.title;
1426
1467
  const isCurrent = playerTrackObject?.track.id === track.uuid;
1427
1468
  const isPlaying = isCurrent && playState === import_player5.PlayStates.PLAYING;
1428
- const text = /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("span", { className: TEXT_CELL, children: [
1429
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: TITLE_CELL, children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree14.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
1430
- track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: SEPARATOR, children: " \u2014 " }) : null,
1431
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_genre_tree16.ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
1432
- track.album && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: ALBUM_CELL, children: track.album.name })
1469
+ const text = /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { className: TEXT_CELL, children: [
1470
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: TITLE_CELL, children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree14.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
1471
+ track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: SEPARATOR, children: " \u2014 " }) : null,
1472
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_genre_tree16.ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
1473
+ track.album && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: ALBUM_CELL, children: track.album.name })
1433
1474
  ] });
1434
1475
  const leading = renderSongLeading?.(track, index);
1435
1476
  const actions = renderSongActions?.(track);
1436
1477
  const isUnplayable = (0, import_genre_tree14.isSong)(track) && (0, import_genre_tree14.songUnplayableReason)(track) !== null;
1437
1478
  if (isUnplayable) {
1438
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("li", { className: "flex min-h-8 items-center gap-1 px-1 opacity-50", children: [
1479
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("li", { className: "flex min-h-8 items-center gap-1 px-1 opacity-50", children: [
1439
1480
  leading,
1440
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1481
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1441
1482
  "button",
1442
1483
  {
1443
1484
  type: "button",
@@ -1445,27 +1486,27 @@ function GenrePlaylistTracks({
1445
1486
  "aria-disabled": "true",
1446
1487
  "aria-label": label,
1447
1488
  className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full opacity-0 cursor-not-allowed",
1448
- children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_fa2.FaPlay, { size: 10 })
1489
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_fa2.FaPlay, { size: 10 })
1449
1490
  }
1450
1491
  ),
1451
1492
  text,
1452
1493
  actions
1453
1494
  ] }, track.uuid);
1454
1495
  }
1455
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
1496
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
1456
1497
  "li",
1457
1498
  {
1458
1499
  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)]" : ""}`,
1459
1500
  children: [
1460
1501
  leading,
1461
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1502
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1462
1503
  "button",
1463
1504
  {
1464
1505
  type: "button",
1465
1506
  "aria-label": `${isPlaying ? "Pause" : "Play"} ${label}`,
1466
1507
  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"}`,
1467
1508
  onClick: () => isCurrent ? handlePlayPauseAction() : play(track.uuid),
1468
- 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 })
1509
+ children: isCurrent && playState === import_player5.PlayStates.LOADING ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ui5.RingLoader, { size: 10 }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_fa2.FaPause, { size: 10 }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_fa2.FaPlay, { size: 10 })
1469
1510
  }
1470
1511
  ),
1471
1512
  text,
@@ -1475,7 +1516,7 @@ function GenrePlaylistTracks({
1475
1516
  track.uuid
1476
1517
  );
1477
1518
  }),
1478
- hasNextPage && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("li", { ref: setSentinel, "aria-hidden": "true", className: "h-4" })
1519
+ hasNextPage && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("li", { ref: setSentinel, "aria-hidden": "true", className: "h-4" })
1479
1520
  ] })
1480
1521
  ] }) });
1481
1522
  }
@@ -1483,7 +1524,7 @@ function GenrePlaylistTracks({
1483
1524
  // src/genre-tree/GenreDetailExtras.tsx
1484
1525
  var import_track_noun2 = require("@behindthemusictree/app-kit/track-noun");
1485
1526
  var import_si2 = require("react-icons/si");
1486
- var import_jsx_runtime23 = require("react/jsx-runtime");
1527
+ var import_jsx_runtime24 = require("react/jsx-runtime");
1487
1528
  function GenreDetailExtras({
1488
1529
  genreUuid,
1489
1530
  genrePlaylist,
@@ -1508,17 +1549,17 @@ function GenreDetailExtras({
1508
1549
  criteriaOverviewSchema
1509
1550
  );
1510
1551
  if (isPending) {
1511
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
1512
- /* @__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" }) }) }),
1552
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
1553
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ui6.PanelSection, { title: "Summary", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { "aria-busy": "true", "data-testid": "genre-detail-extras-skeleton", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ui6.Skeleton, { className: "h-4 w-3/4" }) }) }),
1513
1554
  family
1514
1555
  ] });
1515
1556
  }
1516
- if (!overview) return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_jsx_runtime23.Fragment, { children: family });
1557
+ if (!overview) return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_jsx_runtime24.Fragment, { children: family });
1517
1558
  const { summary, summarySourceUrl, essentialSongs } = overview;
1518
1559
  const wikidataUrl = wikidataId ? `https://www.wikidata.org/wiki/${encodeURIComponent(wikidataId)}` : null;
1519
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
1520
- (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: [
1521
- summarySourceUrl && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
1560
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
1561
+ (summarySourceUrl || wikidataUrl) && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ui6.PanelSection, { title: "Links", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex flex-wrap gap-x-3 gap-y-1 text-xs text-[var(--color-text)]", children: [
1562
+ summarySourceUrl && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
1522
1563
  "a",
1523
1564
  {
1524
1565
  href: summarySourceUrl,
@@ -1526,12 +1567,12 @@ function GenreDetailExtras({
1526
1567
  rel: "noopener noreferrer",
1527
1568
  className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
1528
1569
  children: [
1529
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_si2.SiWikipedia, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1570
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_si2.SiWikipedia, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1530
1571
  "Wikipedia"
1531
1572
  ]
1532
1573
  }
1533
1574
  ),
1534
- wikidataUrl && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
1575
+ wikidataUrl && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
1535
1576
  "a",
1536
1577
  {
1537
1578
  href: wikidataUrl,
@@ -1539,17 +1580,17 @@ function GenreDetailExtras({
1539
1580
  rel: "noopener noreferrer",
1540
1581
  className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
1541
1582
  children: [
1542
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_si2.SiWikidata, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1583
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_si2.SiWikidata, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1543
1584
  "Wikidata"
1544
1585
  ]
1545
1586
  }
1546
1587
  )
1547
1588
  ] }) }),
1548
- /* @__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) }),
1589
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ui6.PanelSection, { title: "Summary", children: renderSummary ? renderSummary(overview) : /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ClampedText, { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: summary ?? "\u2014" }, genreUuid) }),
1549
1590
  family,
1550
1591
  renderGenreDetailExtras?.(overview),
1551
- (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" }) }),
1552
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1592
+ (renderEssentialTracks || essentialSongs) && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ui6.PanelSection, { title: `Essential ${trackNoun.plural}`, children: renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && essentialSongs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("ul", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: essentialSongs.map((track) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("li", { className: "py-1", children: track.title }, track.uuid)) }) : /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: "\u2014" }) }),
1593
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1553
1594
  GenrePlaylistTracks,
1554
1595
  {
1555
1596
  genreUuid,
@@ -1566,8 +1607,8 @@ function GenreDetailExtras({
1566
1607
  }
1567
1608
 
1568
1609
  // src/genre-tree/GenreInfoPanelBody.tsx
1569
- var import_jsx_runtime24 = require("react/jsx-runtime");
1570
- var import_react12 = require("react");
1610
+ var import_jsx_runtime25 = require("react/jsx-runtime");
1611
+ var import_react13 = require("react");
1571
1612
  function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
1572
1613
  const { isPlaying, setIsPlaying } = (0, import_player6.usePlayer)();
1573
1614
  const isCurrentTrackList = playback.playingNodeId === genrePlaylist.uuid;
@@ -1580,7 +1621,7 @@ function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
1580
1621
  console.error("Failed to load genre playlist tracks:", error);
1581
1622
  });
1582
1623
  };
1583
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1624
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1584
1625
  GenreActionBand,
1585
1626
  {
1586
1627
  ...bandProps,
@@ -1609,24 +1650,24 @@ function GenreInfoPanelBody({
1609
1650
  const scopes = counts.genreAndSubgenresTracksCount;
1610
1651
  return "canonical" in scopes ? `${counts.genreOnlyTracksCount} this genre only \xB7 ${scopes.canonical} with canonical subgenres \xB7 ${scopes.regional} with regional subgenres \xB7 ${scopes.all} with all subgenres` : `${counts.genreOnlyTracksCount} this genre only \xB7 ${scopes.all} with subgenres`;
1611
1652
  };
1612
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
1653
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_jsx_runtime25.Fragment, { children: [
1613
1654
  toolbar,
1614
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(GenreNodeActionBand, { ...band, genrePlaylist, playback }),
1615
- summary && catalogue && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "px-3.5 py-3 text-sm text-[var(--color-text)]", children: [
1616
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("p", { children: [
1655
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(GenreNodeActionBand, { ...band, genrePlaylist, playback }),
1656
+ summary && catalogue && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "px-3.5 py-3 text-sm text-[var(--color-text)]", children: [
1657
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("p", { children: [
1617
1658
  "Catalogue ",
1618
1659
  noun.plural,
1619
1660
  ": ",
1620
1661
  describeCounts(catalogue)
1621
1662
  ] }),
1622
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("p", { children: [
1663
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("p", { children: [
1623
1664
  "Playlist ",
1624
1665
  noun.plural,
1625
1666
  ": ",
1626
1667
  describeCounts(summary)
1627
1668
  ] })
1628
1669
  ] }),
1629
- /* @__PURE__ */ (0, import_react12.createElement)(
1670
+ /* @__PURE__ */ (0, import_react13.createElement)(
1630
1671
  GenreDetailExtras,
1631
1672
  {
1632
1673
  ...extras,
@@ -1644,9 +1685,9 @@ function GenreInfoPanelBody({
1644
1685
  }
1645
1686
 
1646
1687
  // src/genre-tree/ChangeParentPopup.tsx
1647
- var import_react13 = require("react");
1688
+ var import_react14 = require("react");
1648
1689
  var import_genre_tree19 = require("@behindthemusictree/app-kit/genre-tree");
1649
- var import_jsx_runtime25 = require("react/jsx-runtime");
1690
+ var import_jsx_runtime26 = require("react/jsx-runtime");
1650
1691
  function ChangeParentPopup({
1651
1692
  genrePlaylist,
1652
1693
  genrePlaylists,
@@ -1656,7 +1697,7 @@ function ChangeParentPopup({
1656
1697
  onParentChanged
1657
1698
  }) {
1658
1699
  const { mutate, isPending, formErrors } = (0, import_genre_tree19.useUpdateGenre)(scope, getBackendBaseUrl);
1659
- const candidates = (0, import_react13.useMemo)(
1700
+ const candidates = (0, import_react14.useMemo)(
1660
1701
  () => (0, import_genre_tree19.getReparentCandidates)(genrePlaylists, genrePlaylist.uuid),
1661
1702
  [genrePlaylists, genrePlaylist.uuid]
1662
1703
  );
@@ -1674,19 +1715,19 @@ function ChangeParentPopup({
1674
1715
  }
1675
1716
  );
1676
1717
  };
1677
- (0, import_react13.useEffect)(() => {
1718
+ (0, import_react14.useEffect)(() => {
1678
1719
  const handleKeyDown = (event) => {
1679
1720
  if (event.key === "Escape") onClose();
1680
1721
  };
1681
1722
  window.addEventListener("keydown", handleKeyDown);
1682
1723
  return () => window.removeEventListener("keydown", handleKeyDown);
1683
1724
  }, [onClose]);
1684
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
1685
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("p", { className: "mb-3 text-sm text-[var(--color-text-muted)]", children: [
1725
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
1726
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("p", { className: "mb-3 text-sm text-[var(--color-text-muted)]", children: [
1686
1727
  "Current parent: ",
1687
1728
  genrePlaylist.parent?.name ?? "none"
1688
1729
  ] }),
1689
- /* @__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)(
1730
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1690
1731
  GenreSearch,
1691
1732
  {
1692
1733
  genrePlaylists: candidates,
@@ -1694,7 +1735,7 @@ function ChangeParentPopup({
1694
1735
  placeholder: "Search a new parent\u2026"
1695
1736
  }
1696
1737
  ) }),
1697
- 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(" ") })
1738
+ formErrors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { role: "alert", className: "mt-3 text-sm text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
1698
1739
  ] });
1699
1740
  }
1700
1741
 
@@ -1709,7 +1750,7 @@ var import_ui8 = require("@behindthemusictree/ui");
1709
1750
  var import_lucide_react8 = require("lucide-react");
1710
1751
  var import_si3 = require("react-icons/si");
1711
1752
  var import_ui7 = require("@behindthemusictree/ui");
1712
- var import_jsx_runtime26 = require("react/jsx-runtime");
1753
+ var import_jsx_runtime27 = require("react/jsx-runtime");
1713
1754
  var LINK_KINDS = {
1714
1755
  official: { label: "Official website", Icon: import_lucide_react8.Globe },
1715
1756
  wikipedia: { label: "Wikipedia", Icon: import_si3.SiWikipedia },
@@ -1746,7 +1787,7 @@ function artistLinks(artist) {
1746
1787
  return links;
1747
1788
  }
1748
1789
  function ArtistLinks({ links }) {
1749
- 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)) });
1790
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_ui7.Chip, { href: url, icon: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
1750
1791
  }
1751
1792
 
1752
1793
  // src/genre-tree/artist/formatYears.ts
@@ -1757,7 +1798,7 @@ function formatYears(beginYear, endYear, ended) {
1757
1798
  }
1758
1799
 
1759
1800
  // src/genre-tree/artist/ArtistHeader.tsx
1760
- var import_jsx_runtime27 = require("react/jsx-runtime");
1801
+ var import_jsx_runtime28 = require("react/jsx-runtime");
1761
1802
  var countryNames = new Intl.DisplayNames(["en"], { type: "region" });
1762
1803
  function ArtistHeader({ artist }) {
1763
1804
  const fields = [];
@@ -1767,35 +1808,35 @@ function ArtistHeader({ artist }) {
1767
1808
  const years = formatYears(artist.beginYear, artist.endYear, artist.ended);
1768
1809
  if (years) fields.push({ label: "Active", value: years });
1769
1810
  const links = artistLinks(artist);
1770
- if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ArtistLinks, { links }) });
1811
+ if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ArtistLinks, { links }) });
1771
1812
  if (fields.length === 0) return null;
1772
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_ui8.FieldList, { items: fields });
1813
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_ui8.FieldList, { items: fields });
1773
1814
  }
1774
1815
 
1775
1816
  // src/genre-tree/artist/ArtistPhoto.tsx
1776
- var import_jsx_runtime28 = require("react/jsx-runtime");
1817
+ var import_jsx_runtime29 = require("react/jsx-runtime");
1777
1818
  function ExternalLink({ href, children }) {
1778
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
1819
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
1779
1820
  }
1780
1821
  function ArtistPhoto({ image, name }) {
1781
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
1782
- /* @__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" }),
1783
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("figcaption", { className: "text-xs text-[var(--color-text-muted)]", children: [
1822
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
1823
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
1824
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("figcaption", { className: "text-xs text-[var(--color-text-muted)]", children: [
1784
1825
  "Photo: ",
1785
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
1826
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
1786
1827
  ",",
1787
1828
  " ",
1788
- image.licenseUrl ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
1829
+ image.licenseUrl ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
1789
1830
  ] })
1790
1831
  ] });
1791
1832
  }
1792
1833
 
1793
1834
  // src/genre-tree/artist/ArtistMembers.tsx
1794
1835
  var import_ui9 = require("@behindthemusictree/ui");
1795
- var import_jsx_runtime29 = require("react/jsx-runtime");
1836
+ var import_jsx_runtime30 = require("react/jsx-runtime");
1796
1837
  function MembershipList({ title, memberships, renderArtistLink }) {
1797
1838
  if (memberships.length === 0) return null;
1798
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui9.PanelGroup, { title, children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("ul", { children: memberships.map((membership, index) => {
1839
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui9.PanelGroup, { title, children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("ul", { children: memberships.map((membership, index) => {
1799
1840
  const details = [
1800
1841
  formatYears(membership.beginYear, membership.endYear, membership.ended),
1801
1842
  ...membership.roles
@@ -1803,9 +1844,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
1803
1844
  const detailsText = details.filter(Boolean).join(" \xB7 ");
1804
1845
  return (
1805
1846
  // A member can have several periods in the same group.
1806
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
1807
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
1808
- detailsText && /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
1847
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
1848
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
1849
+ detailsText && /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
1809
1850
  " \u2014 ",
1810
1851
  detailsText
1811
1852
  ] })
@@ -1816,9 +1857,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
1816
1857
  function ArtistMembers({ title, memberships, renderArtistLink }) {
1817
1858
  if (memberships.length === 0) return null;
1818
1859
  const isCurrent = (membership) => !membership.ended && membership.endYear === null;
1819
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(import_ui9.PanelSection, { title, children: [
1820
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
1821
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
1860
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_ui9.PanelSection, { title, children: [
1861
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
1862
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1822
1863
  MembershipList,
1823
1864
  {
1824
1865
  title: "Past",
@@ -1831,7 +1872,7 @@ function ArtistMembers({ title, memberships, renderArtistLink }) {
1831
1872
 
1832
1873
  // src/genre-tree/artist/ArtistDiscography.tsx
1833
1874
  var import_ui10 = require("@behindthemusictree/ui");
1834
- var import_jsx_runtime30 = require("react/jsx-runtime");
1875
+ var import_jsx_runtime31 = require("react/jsx-runtime");
1835
1876
  var GROUPS = ["Albums", "EPs", "Singles", "Live", "Compilations", "Other"];
1836
1877
  var PRIMARY_GROUPS = { album: "Albums", ep: "EPs", single: "Singles" };
1837
1878
  function discographyGroup({ primaryType, secondaryTypes }) {
@@ -1851,18 +1892,18 @@ function byYear(a, b) {
1851
1892
  function ArtistDiscography({ albums }) {
1852
1893
  if (albums.length === 0) return null;
1853
1894
  const sorted = [...albums].sort(byYear);
1854
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui10.PanelSection, { title: "Discography", children: GROUPS.map((group) => {
1895
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ui10.PanelSection, { title: "Discography", children: GROUPS.map((group) => {
1855
1896
  const groupAlbums = sorted.filter((album) => discographyGroup(album) === group);
1856
1897
  if (groupAlbums.length === 0) return null;
1857
- 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: [
1858
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
1859
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
1898
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ui10.PanelGroup, { title: group, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
1899
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
1900
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
1860
1901
  ] }, album.uuid)) }) }, group);
1861
1902
  }) });
1862
1903
  }
1863
1904
 
1864
1905
  // src/genre-tree/artist/ArtistSongs.tsx
1865
- var import_react14 = require("react");
1906
+ var import_react15 = require("react");
1866
1907
  var import_react_query2 = require("@tanstack/react-query");
1867
1908
  var import_lucide_react9 = require("lucide-react");
1868
1909
  var import_ui11 = require("@behindthemusictree/ui");
@@ -1878,7 +1919,7 @@ var import_genre_tree20 = require("@behindthemusictree/app-kit/genre-tree");
1878
1919
  var import_fa3 = require("react-icons/fa");
1879
1920
  var import_player7 = require("@behindthemusictree/app-kit/player");
1880
1921
  var import_player8 = require("@behindthemusictree/app-kit/player");
1881
- var import_jsx_runtime31 = require("react/jsx-runtime");
1922
+ var import_jsx_runtime32 = require("react/jsx-runtime");
1882
1923
  function TrackPositionPlayPause({
1883
1924
  position,
1884
1925
  uuid,
@@ -1892,14 +1933,14 @@ function TrackPositionPlayPause({
1892
1933
  const isPlaying = isThisTrack && isConfirmedPlayable && playState === import_player8.PlayStates.PLAYING;
1893
1934
  const isPaused = isThisTrack && isConfirmedPlayable && playState === import_player8.PlayStates.PAUSED;
1894
1935
  const isCurrentTrackActive = isPlaying || isPaused;
1895
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
1936
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
1896
1937
  "div",
1897
1938
  {
1898
1939
  className: "track-position-play-pause relative flex items-center justify-center gap-2 text-lg w-16 cursor-pointer",
1899
1940
  style: { minWidth: "4rem", maxWidth: "4rem" },
1900
1941
  onClick: handlePlayPauseClick,
1901
1942
  children: [
1902
- /* @__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)(
1943
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
1903
1944
  "span",
1904
1945
  {
1905
1946
  className: "track-position-play-pause__bars",
@@ -1907,18 +1948,18 @@ function TrackPositionPlayPause({
1907
1948
  role: "img",
1908
1949
  "aria-label": isPlaying ? "Playing" : "Paused",
1909
1950
  children: [
1910
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "track-position-play-pause__bar" }),
1911
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "track-position-play-pause__bar" }),
1912
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "track-position-play-pause__bar" })
1951
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "track-position-play-pause__bar" }),
1952
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "track-position-play-pause__bar" }),
1953
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "track-position-play-pause__bar" })
1913
1954
  ]
1914
1955
  }
1915
1956
  ) : position }),
1916
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
1957
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1917
1958
  "div",
1918
1959
  {
1919
1960
  className: `hidden group-hover:flex items-center justify-center ${isCurrentTrackActive ? "" : "absolute inset-0"}`,
1920
1961
  "aria-hidden": "true",
1921
- 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, {})
1962
+ children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_fa3.FaPause, {}) : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_fa3.FaPlay, {})
1922
1963
  }
1923
1964
  )
1924
1965
  ]
@@ -1929,7 +1970,7 @@ function TrackPositionPlayPause({
1929
1970
  // src/genre-tree/track-list-sidebar/TrackItem.tsx
1930
1971
  var import_genre_tree21 = require("@behindthemusictree/app-kit/genre-tree");
1931
1972
  var import_genre_tree22 = require("@behindthemusictree/app-kit/genre-tree");
1932
- var import_jsx_runtime32 = require("react/jsx-runtime");
1973
+ var import_jsx_runtime33 = require("react/jsx-runtime");
1933
1974
  function TrackItem({
1934
1975
  track,
1935
1976
  position,
@@ -1953,12 +1994,12 @@ function TrackItem({
1953
1994
  loadTrackForPlayer(track.uuid);
1954
1995
  }
1955
1996
  };
1956
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
1997
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
1957
1998
  "div",
1958
1999
  {
1959
2000
  className: `track-item flex h-14 text-[var(--color-text)] ${isUnplayable ? "opacity-50" : "hover:bg-[var(--color-surface-muted)] group"}`,
1960
2001
  children: [
1961
- isUnplayable ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
2002
+ isUnplayable ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
1962
2003
  "div",
1963
2004
  {
1964
2005
  className: "flex items-center justify-center text-lg w-16",
@@ -1966,7 +2007,7 @@ function TrackItem({
1966
2007
  "aria-disabled": "true",
1967
2008
  children: position
1968
2009
  }
1969
- ) : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
2010
+ ) : /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
1970
2011
  TrackPositionPlayPause,
1971
2012
  {
1972
2013
  position,
@@ -1975,12 +2016,12 @@ function TrackItem({
1975
2016
  isSelected: selectedTrack?.uuid === track.uuid
1976
2017
  }
1977
2018
  ),
1978
- /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
1979
- /* @__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_tree21.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
1980
- 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_tree22.ArtistNames, { artists: track.artists, renderArtistLink }) }) : ""
2019
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
2020
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree21.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
2021
+ track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_genre_tree22.ArtistNames, { artists: track.artists, renderArtistLink }) }) : ""
1981
2022
  ] }),
1982
- /* @__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 }) : "" }),
1983
- renderDuration && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
2023
+ /* @__PURE__ */ (0, import_jsx_runtime33.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_runtime33.jsx)("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
2024
+ renderDuration && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
1984
2025
  "div",
1985
2026
  {
1986
2027
  className: "duration flex text-base w-16 items-center justify-center",
@@ -1988,22 +2029,22 @@ function TrackItem({
1988
2029
  children: renderDuration(track)
1989
2030
  }
1990
2031
  ),
1991
- 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) })
2032
+ renderActions && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
1992
2033
  ]
1993
2034
  }
1994
2035
  );
1995
2036
  }
1996
2037
 
1997
2038
  // src/genre-tree/artist/ArtistSongs.tsx
1998
- var import_jsx_runtime33 = require("react/jsx-runtime");
2039
+ var import_jsx_runtime34 = require("react/jsx-runtime");
1999
2040
  var SongPageSchema2 = (0, import_transport7.PaginatedResponseSchema)(import_genre_tree23.SongDetailedSchema);
2000
2041
  var SORTS = [
2001
2042
  { ordering: "title", label: "Title" },
2002
2043
  { ordering: "-albumYear", label: "Year" }
2003
2044
  ];
2004
2045
  function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLink }) {
2005
- const [page, setPage] = (0, import_react14.useState)(1);
2006
- const [ordering, setOrdering] = (0, import_react14.useState)("title");
2046
+ const [page, setPage] = (0, import_react15.useState)(1);
2047
+ const [ordering, setOrdering] = (0, import_react15.useState)("title");
2007
2048
  const { fetch } = (0, import_transport7.useFetchWrapper)(getBackendBaseUrl);
2008
2049
  const { playNewTrackListFromArtist } = (0, import_genre_tree23.useTrackList)();
2009
2050
  const { playerTrackObject } = (0, import_player10.usePlayer)();
@@ -2028,21 +2069,21 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
2028
2069
  event.stopPropagation();
2029
2070
  void play(song.uuid);
2030
2071
  };
2031
- 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: [
2032
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
2033
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
2072
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui11.PanelSection, { title: "Songs", children: isPending ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui11.Skeleton, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2073
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
2074
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
2034
2075
  "button",
2035
2076
  {
2036
2077
  type: "button",
2037
2078
  onClick: () => void play(),
2038
2079
  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",
2039
2080
  children: [
2040
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_lucide_react9.Play, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
2081
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react9.Play, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
2041
2082
  "Play all"
2042
2083
  ]
2043
2084
  }
2044
2085
  ),
2045
- /* @__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)(
2086
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2046
2087
  "button",
2047
2088
  {
2048
2089
  type: "button",
@@ -2057,7 +2098,7 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
2057
2098
  sort.ordering
2058
2099
  )) })
2059
2100
  ] }),
2060
- /* @__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)(
2101
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2061
2102
  TrackItem,
2062
2103
  {
2063
2104
  track: song,
@@ -2066,12 +2107,12 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
2066
2107
  renderSongLink
2067
2108
  }
2068
2109
  ) }, song.uuid)) }),
2069
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ui11.Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
2110
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui11.Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
2070
2111
  ] }) });
2071
2112
  }
2072
2113
 
2073
2114
  // src/genre-tree/artist/useArtistPanel.tsx
2074
- var import_jsx_runtime34 = require("react/jsx-runtime");
2115
+ var import_jsx_runtime35 = require("react/jsx-runtime");
2075
2116
  function useArtistPanel({
2076
2117
  uuid,
2077
2118
  scope,
@@ -2092,26 +2133,26 @@ function useArtistPanel({
2092
2133
  return {
2093
2134
  ...panel,
2094
2135
  title: "Artist",
2095
- content: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2096
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui12.Skeleton, { className: "h-3 w-16" }),
2097
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui12.Skeleton, { className: "h-4 w-32" }),
2098
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui12.Skeleton, { className: "h-3 w-16" }),
2099
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui12.Skeleton, { className: "h-4 w-24" }),
2100
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui12.Skeleton, { className: "h-16 mt-2.5" }),
2101
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui12.Skeleton, { className: "h-16" })
2136
+ content: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2137
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui12.Skeleton, { className: "h-3 w-16" }),
2138
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui12.Skeleton, { className: "h-4 w-32" }),
2139
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui12.Skeleton, { className: "h-3 w-16" }),
2140
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui12.Skeleton, { className: "h-4 w-24" }),
2141
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui12.Skeleton, { className: "h-16 mt-2.5" }),
2142
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui12.Skeleton, { className: "h-16" })
2102
2143
  ] })
2103
2144
  };
2104
2145
  case "not-found":
2105
2146
  return {
2106
2147
  ...panel,
2107
2148
  title: "Artist not found",
2108
- 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." })
2149
+ content: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This artist does not exist." })
2109
2150
  };
2110
2151
  case "error":
2111
2152
  return {
2112
2153
  ...panel,
2113
2154
  title: "Artist",
2114
- content: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
2155
+ content: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
2115
2156
  };
2116
2157
  case "success": {
2117
2158
  const { artist } = result;
@@ -2120,13 +2161,13 @@ function useArtistPanel({
2120
2161
  return {
2121
2162
  ...panel,
2122
2163
  title: artist.name,
2123
- content: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2124
- 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 }) }) }),
2125
- artist.image && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ArtistPhoto, { image: artist.image, name: artist.name }),
2126
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ArtistHeader, { artist }),
2127
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
2128
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
2129
- artist.tracksCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2164
+ content: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
2165
+ shareUrl && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: shareTarget && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl }) }) }),
2166
+ artist.image && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ArtistPhoto, { image: artist.image, name: artist.name }),
2167
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ArtistHeader, { artist }),
2168
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
2169
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
2170
+ artist.tracksCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2130
2171
  ArtistSongs,
2131
2172
  {
2132
2173
  artist: { uuid: artist.uuid, name: artist.name },
@@ -2136,7 +2177,7 @@ function useArtistPanel({
2136
2177
  }
2137
2178
  )
2138
2179
  ] }),
2139
- extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ArtistDiscography, { albums: artist.albums }) : void 0
2180
+ extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ArtistDiscography, { albums: artist.albums }) : void 0
2140
2181
  };
2141
2182
  }
2142
2183
  }
@@ -2147,45 +2188,45 @@ var import_ui14 = require("@behindthemusictree/ui");
2147
2188
  var import_genre_tree26 = require("@behindthemusictree/app-kit/genre-tree");
2148
2189
 
2149
2190
  // src/genre-tree/song/SongPanelContent.tsx
2150
- var import_react15 = require("react");
2191
+ var import_react16 = require("react");
2151
2192
  var import_lucide_react10 = require("lucide-react");
2152
2193
  var import_ui13 = require("@behindthemusictree/ui");
2153
2194
  var import_player11 = require("@behindthemusictree/app-kit/player");
2154
2195
  var import_genre_tree25 = require("@behindthemusictree/app-kit/genre-tree");
2155
- var import_jsx_runtime35 = require("react/jsx-runtime");
2196
+ var import_jsx_runtime36 = require("react/jsx-runtime");
2156
2197
  var numberFormat = new Intl.NumberFormat("en");
2157
2198
  var dateFormat = new Intl.DateTimeFormat("en", { dateStyle: "medium" });
2158
2199
  var externalLink = "text-[var(--color-primary)] hover:underline focus-visible:underline";
2159
2200
  function SongHistory({ uuid, getBackendBaseUrl }) {
2160
- const [open, setOpen] = (0, import_react15.useState)(false);
2201
+ const [open, setOpen] = (0, import_react16.useState)(false);
2161
2202
  const history = (0, import_genre_tree25.useSongHistory)(uuid, getBackendBaseUrl, open);
2162
2203
  let body = null;
2163
- if (history.status === "loading") body = /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui13.Skeleton, { className: "h-10", "aria-busy": "true" });
2164
- else if (history.status === "error") body = /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "text-red-600", children: "Could not load the history." });
2204
+ if (history.status === "loading") body = /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.Skeleton, { className: "h-10", "aria-busy": "true" });
2205
+ else if (history.status === "error") body = /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "text-red-600", children: "Could not load the history." });
2165
2206
  else if (history.status === "success")
2166
- 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: [
2167
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "font-medium", children: entry.action }),
2168
- entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
2207
+ body = history.entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("li", { className: "py-1.5 text-sm", children: [
2208
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "font-medium", children: entry.action }),
2209
+ entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
2169
2210
  " ",
2170
2211
  "\u2014 ",
2171
2212
  entry.oldValue ?? "none",
2172
2213
  " \u2192 ",
2173
2214
  entry.newValue ?? "none"
2174
2215
  ] }) : null,
2175
- /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "block text-xs text-[var(--color-text-muted)]", children: [
2216
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("span", { className: "block text-xs text-[var(--color-text-muted)]", children: [
2176
2217
  entry.actorPseudo ?? "Anonymous",
2177
2218
  " \xB7 ",
2178
2219
  dateFormat.format(new Date(entry.createdOn))
2179
2220
  ] })
2180
2221
  ] }, entry.uuid)) });
2181
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
2222
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
2182
2223
  "details",
2183
2224
  {
2184
2225
  onToggle: (event) => setOpen(event.currentTarget.open),
2185
2226
  className: "px-6 pb-2.5 mt-2.5 border-t border-[var(--color-border)]",
2186
2227
  children: [
2187
- /* @__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" }),
2188
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "mt-1.5", children: body })
2228
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("summary", { className: "mt-2.5 cursor-pointer text-[0.6875rem] font-semibold uppercase text-[var(--color-text-muted)]", children: "History" }),
2229
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "mt-1.5", children: body })
2189
2230
  ]
2190
2231
  }
2191
2232
  );
@@ -2211,13 +2252,13 @@ function SongPanelContent({
2211
2252
  const path = genrePath.length > 0 ? genrePath : null;
2212
2253
  fields.push({
2213
2254
  label: "Genre",
2214
- 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: [
2215
- index > 0 && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { "aria-hidden": "true", children: "\u203A " }),
2216
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("button", { type: "button", onClick: () => onSelectGenre(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
2255
+ value: path ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("span", { children: [
2256
+ index > 0 && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { "aria-hidden": "true", children: "\u203A " }),
2257
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("button", { type: "button", onClick: () => onSelectGenre(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
2217
2258
  ] }, genrePlaylist.uuid)) }) : song.genre.name
2218
2259
  });
2219
2260
  } else {
2220
- fields.push({ label: "Genre", value: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
2261
+ fields.push({ label: "Genre", value: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
2221
2262
  }
2222
2263
  if (song.firstReleaseYear) fields.push({ label: "First released", value: song.firstReleaseYear });
2223
2264
  const viewCount = song.youtubeVideo?.viewCount;
@@ -2227,11 +2268,11 @@ function SongPanelContent({
2227
2268
  const links = [];
2228
2269
  if (musicBrainzUrl)
2229
2270
  links.push(
2230
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
2271
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
2231
2272
  );
2232
2273
  if (song.youtubeVideo)
2233
2274
  links.push(
2234
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2275
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2235
2276
  "a",
2236
2277
  {
2237
2278
  href: `https://www.youtube.com/watch?v=${encodeURIComponent(song.youtubeVideo.id)}`,
@@ -2246,33 +2287,33 @@ function SongPanelContent({
2246
2287
  if (links.length > 0)
2247
2288
  fields.push({
2248
2289
  label: "Links",
2249
- value: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "flex gap-3", children: links })
2290
+ value: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "flex gap-3", children: links })
2250
2291
  });
2251
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
2252
- /* @__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: [
2253
- 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)(
2292
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_jsx_runtime36.Fragment, { children: [
2293
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
2294
+ unplayableReason ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "flex items-center px-2.5 text-sm text-[var(--color-text-muted)]", children: unplayableReason }) : /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
2254
2295
  "button",
2255
2296
  {
2256
2297
  type: "button",
2257
2298
  onClick: () => isCurrent ? handlePlayPauseAction() : playNewTrackListFromTrackUuid(song, scope),
2258
2299
  className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none",
2259
2300
  children: [
2260
- /* @__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" }) }),
2301
+ /* @__PURE__ */ (0, import_jsx_runtime36.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_runtime36.jsx)(import_lucide_react10.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_lucide_react10.Play, { "aria-hidden": "true", className: "w-5 h-5" }) }),
2261
2302
  isPlaying ? "Pause" : "Play"
2262
2303
  ]
2263
2304
  }
2264
2305
  ),
2265
- shareUrl && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
2306
+ shareUrl && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
2266
2307
  ] }) }),
2267
- 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_tree25.ArtistNames, { artists: song.artists, renderArtistLink }) }),
2268
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui13.FieldList, { items: fields }),
2269
- 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)) }) }),
2270
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
2308
+ song.artists && song.artists.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "px-6 pt-2.5 text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_genre_tree25.ArtistNames, { artists: song.artists, renderArtistLink }) }),
2309
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.FieldList, { items: fields }),
2310
+ song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.PanelSection, { title: "Essential in", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
2311
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
2271
2312
  ] });
2272
2313
  }
2273
2314
 
2274
2315
  // src/genre-tree/song/useSongPanel.tsx
2275
- var import_jsx_runtime36 = require("react/jsx-runtime");
2316
+ var import_jsx_runtime37 = require("react/jsx-runtime");
2276
2317
  function useSongPanel({
2277
2318
  uuid,
2278
2319
  scope,
@@ -2294,24 +2335,24 @@ function useSongPanel({
2294
2335
  return {
2295
2336
  ...panel,
2296
2337
  title: "Song",
2297
- content: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2298
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui14.Skeleton, { className: "h-3 w-16" }),
2299
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui14.Skeleton, { className: "h-4 w-32" }),
2300
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui14.Skeleton, { className: "h-3 w-16" }),
2301
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui14.Skeleton, { className: "h-4 w-24" })
2338
+ content: /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2339
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui14.Skeleton, { className: "h-3 w-16" }),
2340
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui14.Skeleton, { className: "h-4 w-32" }),
2341
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui14.Skeleton, { className: "h-3 w-16" }),
2342
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui14.Skeleton, { className: "h-4 w-24" })
2302
2343
  ] })
2303
2344
  };
2304
2345
  case "not-found":
2305
2346
  return {
2306
2347
  ...panel,
2307
2348
  title: "Song not found",
2308
- 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." })
2349
+ content: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This song does not exist." })
2309
2350
  };
2310
2351
  case "error":
2311
2352
  return {
2312
2353
  ...panel,
2313
2354
  title: "Song",
2314
- content: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this song." })
2355
+ content: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this song." })
2315
2356
  };
2316
2357
  case "success": {
2317
2358
  const { song } = result;
@@ -2319,7 +2360,7 @@ function useSongPanel({
2319
2360
  return {
2320
2361
  ...panel,
2321
2362
  title: song.title,
2322
- content: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2363
+ content: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
2323
2364
  SongPanelContent,
2324
2365
  {
2325
2366
  song,
@@ -2339,12 +2380,12 @@ function useSongPanel({
2339
2380
  // src/genre-tree/SizeMetricToggle.tsx
2340
2381
  var import_md = require("react-icons/md");
2341
2382
  var import_ui15 = require("@behindthemusictree/ui");
2342
- var import_jsx_runtime37 = require("react/jsx-runtime");
2383
+ var import_jsx_runtime38 = require("react/jsx-runtime");
2343
2384
  function SizeMetricToggle({
2344
2385
  value,
2345
2386
  onChange
2346
2387
  }) {
2347
- return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
2388
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2348
2389
  import_ui15.SegmentedControl,
2349
2390
  {
2350
2391
  variant: "floating",
@@ -2352,15 +2393,15 @@ function SizeMetricToggle({
2352
2393
  value,
2353
2394
  onChange,
2354
2395
  options: [
2355
- { 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" },
2356
- { 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" }
2396
+ { value: "songs", label: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_md.MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
2397
+ { value: "views", label: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_md.MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
2357
2398
  ]
2358
2399
  }
2359
2400
  );
2360
2401
  }
2361
2402
 
2362
2403
  // src/genre-tree/GenreTreeView.tsx
2363
- var import_jsx_runtime38 = require("react/jsx-runtime");
2404
+ var import_jsx_runtime39 = require("react/jsx-runtime");
2364
2405
  var NO_POP_ROOT_TITLE = "This genre tree has no 'Mainstream Pop' root yet";
2365
2406
  var REGIONS_CLASS = "[--gtv-center-left:var(--layout-center-left)] [--gtv-center-right:var(--layout-center-right)] [--gtv-center-top:var(--layout-band-top)]";
2366
2407
  var noop = () => {
@@ -2395,6 +2436,7 @@ function GenreTreeView({
2395
2436
  onArtistExpandedChange,
2396
2437
  getShareUrl,
2397
2438
  extraSearchGroups = [],
2439
+ SearchGroups,
2398
2440
  onSearchQueryChange,
2399
2441
  onSearchFocus,
2400
2442
  artistSearch = true,
@@ -2421,21 +2463,21 @@ function GenreTreeView({
2421
2463
  placement = "default",
2422
2464
  ...focus
2423
2465
  }) {
2424
- const getShareUrlRef = (0, import_react16.useRef)(getShareUrl);
2466
+ const getShareUrlRef = (0, import_react17.useRef)(getShareUrl);
2425
2467
  getShareUrlRef.current = getShareUrl;
2426
- const [internalArtistUuid, setInternalArtistUuid] = (0, import_react16.useState)(null);
2468
+ const [internalArtistUuid, setInternalArtistUuid] = (0, import_react17.useState)(null);
2427
2469
  const isArtistControlled = controlledArtistUuid !== void 0;
2428
2470
  const artistUuid = isArtistControlled ? controlledArtistUuid : internalArtistUuid;
2429
- const [internalSongUuid, setInternalSongUuid] = (0, import_react16.useState)(null);
2471
+ const [internalSongUuid, setInternalSongUuid] = (0, import_react17.useState)(null);
2430
2472
  const isSongControlled = controlledSongUuid !== void 0;
2431
2473
  const songUuid = isSongControlled ? controlledSongUuid : internalSongUuid;
2432
2474
  const changeSong = (uuid) => {
2433
2475
  if (!isSongControlled) setInternalSongUuid(uuid);
2434
2476
  onSongUuidChange?.(uuid);
2435
2477
  };
2436
- const [internalSizeMetric, setInternalSizeMetric] = (0, import_react16.useState)("songs");
2478
+ const [internalSizeMetric, setInternalSizeMetric] = (0, import_react17.useState)("songs");
2437
2479
  const sizeMetric = controlledSizeMetric ?? internalSizeMetric;
2438
- const deferredSizeMetric = (0, import_react16.useDeferredValue)(sizeMetric);
2480
+ const deferredSizeMetric = (0, import_react17.useDeferredValue)(sizeMetric);
2439
2481
  const changeSizeMetric = (metric) => {
2440
2482
  if (controlledSizeMetric === void 0) setInternalSizeMetric(metric);
2441
2483
  onSizeMetricChange?.(metric);
@@ -2472,7 +2514,7 @@ function GenreTreeView({
2472
2514
  artistUuid
2473
2515
  });
2474
2516
  const restoreRevision = viewportRestore?.revision;
2475
- const [stackedRestoreOwner, setStackedRestoreOwner] = (0, import_react16.useState)({ revision: restoreRevision, rootUuid: selectedGenrePlaylist?.root.uuid });
2517
+ const [stackedRestoreOwner, setStackedRestoreOwner] = (0, import_react17.useState)({ revision: restoreRevision, rootUuid: selectedGenrePlaylist?.root.uuid });
2476
2518
  let restoreRootUuid = stackedRestoreOwner.rootUuid;
2477
2519
  if (stackedRestoreOwner.revision !== restoreRevision) {
2478
2520
  restoreRootUuid = selectedGenrePlaylist?.root.uuid;
@@ -2488,7 +2530,7 @@ function GenreTreeView({
2488
2530
  const canSizeByViews = !isArtistFiltered && hasViewsCount(visibleGenrePlaylists);
2489
2531
  const artistResult = (0, import_genre_tree27.useArtist)(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
2490
2532
  const filterArtistName = artistResult.status === "success" ? artistResult.artist.name : null;
2491
- const filterArtist = (0, import_react16.useMemo)(
2533
+ const filterArtist = (0, import_react17.useMemo)(
2492
2534
  () => artistUuid !== null && filterArtistName !== null ? { uuid: artistUuid, name: filterArtistName } : null,
2493
2535
  [artistUuid, filterArtistName]
2494
2536
  );
@@ -2503,14 +2545,14 @@ function GenreTreeView({
2503
2545
  if (selectedNodeId !== null) clearSelection();
2504
2546
  changeSong(uuid);
2505
2547
  };
2506
- const openSongRef = (0, import_react16.useRef)(openSong);
2548
+ const openSongRef = (0, import_react17.useRef)(openSong);
2507
2549
  openSongRef.current = openSong;
2508
- const resolvedRenderSongLink = (0, import_react16.useCallback)(
2509
- (song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
2550
+ const resolvedRenderSongLink = (0, import_react17.useCallback)(
2551
+ (song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
2510
2552
  [renderSongLink]
2511
2553
  );
2512
- const [expandedSongUuid, setExpandedSongUuid] = (0, import_react16.useState)(null);
2513
- const [expandedArtistUuid, setExpandedArtistUuid] = (0, import_react16.useState)(null);
2554
+ const [expandedSongUuid, setExpandedSongUuid] = (0, import_react17.useState)(null);
2555
+ const [expandedArtistUuid, setExpandedArtistUuid] = (0, import_react17.useState)(null);
2514
2556
  const artistBaseUrl = artistSearchBaseUrl ?? getBackendBaseUrl;
2515
2557
  const songBaseUrl = getBackendBaseUrl;
2516
2558
  const artistPanel = useArtistPanel({
@@ -2518,7 +2560,7 @@ function GenreTreeView({
2518
2560
  scope,
2519
2561
  getBackendBaseUrl: artistBaseUrl,
2520
2562
  getShareUrl: getShareUrlRef.current,
2521
- 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 })),
2563
+ renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
2522
2564
  expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
2523
2565
  onExpandedChange: (on) => {
2524
2566
  if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
@@ -2527,7 +2569,7 @@ function GenreTreeView({
2527
2569
  renderSongLink: resolvedRenderSongLink,
2528
2570
  onClose: () => changeArtist(null)
2529
2571
  });
2530
- 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 }));
2572
+ const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
2531
2573
  const getGenrePath = (genre) => {
2532
2574
  const path = [];
2533
2575
  let current = visibleGenrePlaylists.find((playlist) => playlist.criteria?.uuid === genre.uuid);
@@ -2559,8 +2601,8 @@ function GenreTreeView({
2559
2601
  const builtInSongPanel = overlayPanel || hasSelectedGenreNode ? void 0 : songPanel;
2560
2602
  const builtInArtistPanel = overlayPanel || hasSelectedGenreNode || builtInSongPanel ? void 0 : artistPanel;
2561
2603
  const builtInPanel = builtInSongPanel ?? builtInArtistPanel;
2562
- const [uncontrolledCollapsed, setUncontrolledCollapsed] = (0, import_react16.useState)(false);
2563
- const [uncontrolledExpanded, setUncontrolledExpanded] = (0, import_react16.useState)(false);
2604
+ const [uncontrolledCollapsed, setUncontrolledCollapsed] = (0, import_react17.useState)(false);
2605
+ const [uncontrolledExpanded, setUncontrolledExpanded] = (0, import_react17.useState)(false);
2564
2606
  const isCollapsed = collapsed ?? uncontrolledCollapsed;
2565
2607
  const isExpanded = expanded ?? uncontrolledExpanded;
2566
2608
  const changeCollapsed = (next) => {
@@ -2571,28 +2613,28 @@ function GenreTreeView({
2571
2613
  setUncontrolledExpanded(next);
2572
2614
  onExpandedChange?.(next);
2573
2615
  };
2574
- (0, import_react16.useEffect)(() => {
2616
+ (0, import_react17.useEffect)(() => {
2575
2617
  setUncontrolledCollapsed(false);
2576
2618
  }, [selectedNodeId, artistUuid, songUuid]);
2577
- const [isWheelRevealed, setIsWheelRevealed] = (0, import_react16.useState)(false);
2619
+ const [isWheelRevealed, setIsWheelRevealed] = (0, import_react17.useState)(false);
2578
2620
  const usesHandoff = viewMode === "wheel" || viewMode === "pop-core";
2579
2621
  const isTreeReady = !isLoading && (!usesHandoff || isWheelRevealed);
2580
- const [resolveStyle, setResolveStyle] = (0, import_react16.useState)(null);
2581
- const handleNodeStyleResolverChange = (0, import_react16.useCallback)(
2622
+ const [resolveStyle, setResolveStyle] = (0, import_react17.useState)(null);
2623
+ const handleNodeStyleResolverChange = (0, import_react17.useCallback)(
2582
2624
  (resolver) => setResolveStyle(() => resolver),
2583
2625
  []
2584
2626
  );
2585
- const panelRef = (0, import_react16.useRef)(null);
2627
+ const panelRef = (0, import_react17.useRef)(null);
2586
2628
  const obscuredInset = (0, import_ui16.useObscuredInset)(panelRef);
2587
2629
  const hostPanel = { obscuredInset, onNodeStyleResolverChange: handleNodeStyleResolverChange };
2588
2630
  const nodeMetric = isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric;
2589
- const treeNodes = (0, import_react16.useMemo)(() => toTreeNodes(visibleGenrePlaylists, nodeMetric), [visibleGenrePlaylists, nodeMetric]);
2631
+ const treeNodes = (0, import_react17.useMemo)(() => toTreeNodes(visibleGenrePlaylists, nodeMetric), [visibleGenrePlaylists, nodeMetric]);
2590
2632
  const relations = (0, import_genre_tree_view4.useGenreNodeRelations)(
2591
2633
  treeNodes,
2592
2634
  !overlayPanel && hasSelectedGenreNode ? selectedNodeId : null,
2593
2635
  resolveStyle
2594
2636
  );
2595
- const [selectionHistory, setSelectionHistory] = (0, import_react16.useState)({
2637
+ const [selectionHistory, setSelectionHistory] = (0, import_react17.useState)({
2596
2638
  current: selectedNodeId,
2597
2639
  previous: null
2598
2640
  });
@@ -2605,28 +2647,6 @@ function GenreTreeView({
2605
2647
  const previousPlaylist = selectionHistory.previous !== null && selectionHistory.previous !== selectedNodeId ? genrePlaylistByNodeId.get(selectionHistory.previous) : void 0;
2606
2648
  const handlePrevious = previousPlaylist && visibleGenrePlaylists.includes(previousPlaylist) ? () => handleGenreSearchSelect(previousPlaylist) : void 0;
2607
2649
  const { showPopup, hidePopup } = (0, import_popup5.usePopup)();
2608
- const [searchText, setSearchText] = (0, import_react16.useState)("");
2609
- const artistGroup = useArtistSearchGroup(
2610
- artistBaseUrl,
2611
- searchText,
2612
- (artist) => {
2613
- openArtist(artist.uuid);
2614
- onArtistSelect?.(artist);
2615
- },
2616
- Boolean(artistSearch)
2617
- );
2618
- const songGroup = useSongSearchGroup(
2619
- songBaseUrl,
2620
- searchText,
2621
- (song) => {
2622
- openSong(song.uuid);
2623
- onSongSelect?.(song);
2624
- },
2625
- Boolean(songSearch)
2626
- );
2627
- const extraGroups = [...extraSearchGroups, artistGroup, songGroup].filter(
2628
- (group) => group !== void 0
2629
- );
2630
2650
  const handleSelectedNodeChange = (node) => {
2631
2651
  if (node !== null && songUuid !== null) changeSong(null);
2632
2652
  selectNode(node);
@@ -2642,7 +2662,7 @@ function GenreTreeView({
2642
2662
  const handleGenreSearchZoomIn = () => {
2643
2663
  if (selectedUuid) focus.onFocusGenreChange?.(selectedUuid, "zoom-in");
2644
2664
  };
2645
- const zoomInRef = (0, import_react16.useRef)(void 0);
2665
+ const zoomInRef = (0, import_react17.useRef)(void 0);
2646
2666
  zoomInRef.current = canZoomIn ? handleGenreSearchZoomIn : void 0;
2647
2667
  const selectGenreFromPanel = (nodeId) => {
2648
2668
  const genrePlaylist = genrePlaylistByNodeId.get(nodeId);
@@ -2650,7 +2670,7 @@ function GenreTreeView({
2650
2670
  };
2651
2671
  const renderGenreBody = (relation) => {
2652
2672
  const { node } = relation;
2653
- const family = /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2673
+ const family = /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2654
2674
  import_genre_tree_view4.GenreNodeFamily,
2655
2675
  {
2656
2676
  node,
@@ -2661,10 +2681,10 @@ function GenreTreeView({
2661
2681
  onSelectNode: selectGenreFromPanel
2662
2682
  }
2663
2683
  );
2664
- const ancestors = /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_genre_tree_view4.GenreNodeAncestors, { ancestorNodes: relation.ancestorNodes, onSelectNode: selectGenreFromPanel });
2684
+ const ancestors = /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_genre_tree_view4.GenreNodeAncestors, { ancestorNodes: relation.ancestorNodes, onSelectNode: selectGenreFromPanel });
2665
2685
  const genrePlaylist = genrePlaylistByNodeId.get(node.id);
2666
2686
  const criteria = genrePlaylist?.criteria;
2667
- if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_jsx_runtime38.Fragment, { children: [
2687
+ if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_jsx_runtime39.Fragment, { children: [
2668
2688
  family,
2669
2689
  ancestors
2670
2690
  ] }) };
@@ -2684,11 +2704,11 @@ function GenreTreeView({
2684
2704
  (sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist.uuid && sibling.criteria?.side === "pop"
2685
2705
  ) : void 0;
2686
2706
  return {
2687
- primary: /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_jsx_runtime38.Fragment, { children: [
2688
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2707
+ primary: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_jsx_runtime39.Fragment, { children: [
2708
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2689
2709
  GenreInfoPanelBody,
2690
2710
  {
2691
- family: /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_jsx_runtime38.Fragment, { children: [
2711
+ family: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_jsx_runtime39.Fragment, { children: [
2692
2712
  family,
2693
2713
  ancestors
2694
2714
  ] }),
@@ -2707,7 +2727,7 @@ function GenreTreeView({
2707
2727
  onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
2708
2728
  onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
2709
2729
  onChangeParent: readOnly ? void 0 : () => showPopup(
2710
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2730
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2711
2731
  ChangeParentPopup,
2712
2732
  {
2713
2733
  genrePlaylist: { ...genrePlaylist, criteria },
@@ -2782,19 +2802,19 @@ function GenreTreeView({
2782
2802
  edge: activePanel.expanded ? { label: "Shrink panel", onClick: () => activePanel.onExpandedChange?.(false) } : isCollapsed ? null : { label: "Hide panel", onClick: () => changeCollapsed(true) },
2783
2803
  center: activePanel.expanded ? null : isCollapsed ? { label: "Show panel", onClick: () => changeCollapsed(false) } : activePanel.onExpandedChange ? { label: "Extend panel", onClick: () => activePanel.onExpandedChange?.(true) } : null
2784
2804
  } : void 0;
2785
- const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2805
+ const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2786
2806
  import_ui16.IconButton,
2787
2807
  {
2788
2808
  "aria-label": activePanel.expanded ? "Shrink panel" : "Extend panel",
2789
2809
  style: activePanel.headerAccent && { color: "inherit" },
2790
2810
  onClick: () => activePanel.onExpandedChange?.(!activePanel.expanded),
2791
- 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" })
2811
+ children: activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_lucide_react12.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_lucide_react12.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
2792
2812
  }
2793
2813
  ) : null;
2794
2814
  const renderArtistFilter = () => {
2795
2815
  if (isLoading || !filterArtist) return null;
2796
- 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: [
2797
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2816
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
2817
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2798
2818
  "button",
2799
2819
  {
2800
2820
  type: "button",
@@ -2806,7 +2826,7 @@ function GenreTreeView({
2806
2826
  children: filterArtist.name
2807
2827
  }
2808
2828
  ),
2809
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2829
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2810
2830
  "button",
2811
2831
  {
2812
2832
  type: "button",
@@ -2818,26 +2838,36 @@ function GenreTreeView({
2818
2838
  )
2819
2839
  ] });
2820
2840
  };
2821
- const actions = /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_jsx_runtime38.Fragment, { children: [
2822
- !isLoading && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2823
- GenreSearch,
2841
+ const actions = /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_jsx_runtime39.Fragment, { children: [
2842
+ !isLoading && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2843
+ GenreTreeSearch,
2824
2844
  {
2845
+ artistBaseUrl,
2846
+ songBaseUrl,
2847
+ artistSearch: Boolean(artistSearch),
2848
+ songSearch: Boolean(songSearch),
2849
+ onArtistSelect: (artist) => {
2850
+ openArtist(artist.uuid);
2851
+ onArtistSelect?.(artist);
2852
+ },
2853
+ onSongSelect: (song) => {
2854
+ openSong(song.uuid);
2855
+ onSongSelect?.(song);
2856
+ },
2857
+ SearchGroups,
2825
2858
  genrePlaylists: visibleGenrePlaylists,
2826
2859
  onSelect: handleGenreSearchSelect,
2827
2860
  selectedName: selectedGenrePlaylist?.name ?? (typeof builtInPanel?.title === "string" ? builtInPanel.title : null),
2828
2861
  onClear: builtInSongPanel ? () => changeSong(null) : builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
2829
2862
  onPrevious: handlePrevious,
2830
- onQueryChange: (text) => {
2831
- setSearchText(text);
2832
- onSearchQueryChange?.(text);
2833
- },
2863
+ onQueryChange: onSearchQueryChange,
2834
2864
  onFocus: onSearchFocus,
2835
- extraGroups
2865
+ extraGroups: extraSearchGroups
2836
2866
  }
2837
2867
  ),
2838
- !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
2839
- /* @__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: [
2840
- !isLoading && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2868
+ !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
2869
+ /* @__PURE__ */ (0, import_jsx_runtime39.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: [
2870
+ !isLoading && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2841
2871
  import_ui16.SegmentedControl,
2842
2872
  {
2843
2873
  "aria-label": "Tree view mode",
@@ -2851,13 +2881,13 @@ function GenreTreeView({
2851
2881
  ]
2852
2882
  }
2853
2883
  ),
2854
- !isLoading && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2884
+ !isLoading && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2855
2885
  "div",
2856
2886
  {
2857
2887
  className: "flex items-center gap-1",
2858
2888
  role: "group",
2859
2889
  "aria-label": "Tree display options",
2860
- children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2890
+ children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2861
2891
  import_ui16.Button,
2862
2892
  {
2863
2893
  variant: allowWheelRotation ? "default" : "outline",
@@ -2868,7 +2898,7 @@ function GenreTreeView({
2868
2898
  )
2869
2899
  }
2870
2900
  ),
2871
- !isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2901
+ !isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2872
2902
  import_ui16.IconTextButton,
2873
2903
  {
2874
2904
  icon: import_lucide_react11.Plus,
@@ -2878,34 +2908,34 @@ function GenreTreeView({
2878
2908
  )
2879
2909
  ] })
2880
2910
  ] });
2881
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
2911
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
2882
2912
  "div",
2883
2913
  {
2884
2914
  "data-left-panel": isPanelShown && !isCollapsed ? void 0 : "hidden",
2885
2915
  className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
2886
2916
  children: [
2887
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2917
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2888
2918
  "div",
2889
2919
  {
2890
2920
  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"}`,
2891
2921
  children: actions
2892
2922
  }
2893
2923
  ),
2894
- !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2924
+ !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2895
2925
  "div",
2896
2926
  {
2897
2927
  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"}`,
2898
2928
  children: renderArtistFilter()
2899
2929
  }
2900
2930
  ),
2901
- /* @__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: [
2902
- !isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
2903
- isLoading ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_genre_tree_view4.GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
2931
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
2932
+ !isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
2933
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_genre_tree_view4.GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
2904
2934
  // The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
2905
2935
  // and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
2906
2936
  // Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
2907
2937
  // The panel's own Zoom in button is hidden: the search bar carries it.
2908
- /* @__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)(
2938
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2909
2939
  GenrePlaylistTreeWheel,
2910
2940
  {
2911
2941
  scope,
@@ -2922,9 +2952,9 @@ function GenreTreeView({
2922
2952
  allowWheelRotation,
2923
2953
  sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
2924
2954
  busy,
2925
- floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "flex items-center gap-2", children: [
2955
+ floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex items-center gap-2", children: [
2926
2956
  floatingControlsLeading,
2927
- canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2957
+ canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2928
2958
  ] }) : void 0,
2929
2959
  defaultViewport,
2930
2960
  viewportRestore,
@@ -2933,7 +2963,7 @@ function GenreTreeView({
2933
2963
  ) }) })
2934
2964
  ) : viewMode === "pop-core" ? (
2935
2965
  // Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
2936
- /* @__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)(
2966
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2937
2967
  GenrePlaylistTreeWheelRadialPopCore,
2938
2968
  {
2939
2969
  scope,
@@ -2950,9 +2980,9 @@ function GenreTreeView({
2950
2980
  allowWheelRotation,
2951
2981
  sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
2952
2982
  busy,
2953
- floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "flex items-center gap-2", children: [
2983
+ floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex items-center gap-2", children: [
2954
2984
  floatingControlsLeading,
2955
- canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2985
+ canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2956
2986
  ] }) : void 0,
2957
2987
  defaultViewport,
2958
2988
  viewportRestore,
@@ -2960,7 +2990,7 @@ function GenreTreeView({
2960
2990
  ...focus
2961
2991
  }
2962
2992
  ) }) })
2963
- ) : 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)(
2993
+ ) : viewMode === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2964
2994
  GenrePlaylistTreeWheelRadialPopCore,
2965
2995
  {
2966
2996
  outline: true,
@@ -2976,13 +3006,13 @@ function GenreTreeView({
2976
3006
  selectedNodeId,
2977
3007
  readOnly
2978
3008
  }
2979
- ) }) : /* @__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(
3009
+ ) }) : /* @__PURE__ */ (0, import_jsx_runtime39.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(
2980
3010
  ([uuid, genrePlaylistTreePerRoot]) => {
2981
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
3011
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2982
3012
  "div",
2983
3013
  {
2984
3014
  className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
2985
- children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
3015
+ children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2986
3016
  GenrePlaylistTreePerRoot,
2987
3017
  {
2988
3018
  scope,
@@ -3008,7 +3038,7 @@ function GenreTreeView({
3008
3038
  );
3009
3039
  }
3010
3040
  ) }),
3011
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
3041
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
3012
3042
  import_ui16.DetailPanel,
3013
3043
  {
3014
3044
  ref: panelRef,
@@ -3025,7 +3055,7 @@ function GenreTreeView({
3025
3055
  aside: activePanel?.aside,
3026
3056
  edgeTab: detailTab,
3027
3057
  headerClassName: activePanel?.compact ? "pt-[calc(var(--layout-search-h)+var(--layout-panel-inset)+0.5rem)]" : void 0,
3028
- children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_jsx_runtime38.Fragment, { children: [
3058
+ children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_jsx_runtime39.Fragment, { children: [
3029
3059
  activePanel.content,
3030
3060
  activePanel.aside
3031
3061
  ] }) : activePanel?.content
@@ -3038,12 +3068,12 @@ function GenreTreeView({
3038
3068
  }
3039
3069
 
3040
3070
  // src/genre-tree/GenreDetailPanel.tsx
3041
- var import_react17 = require("react");
3071
+ var import_react18 = require("react");
3042
3072
  var import_genre_tree_view5 = require("@behindthemusictree/genre-tree-view");
3043
3073
  var import_ui17 = require("@behindthemusictree/ui");
3044
3074
  var import_lucide_react13 = require("lucide-react");
3045
3075
  var import_genre_tree28 = require("@behindthemusictree/app-kit/genre-tree");
3046
- var import_jsx_runtime39 = require("react/jsx-runtime");
3076
+ var import_jsx_runtime40 = require("react/jsx-runtime");
3047
3077
  var RELATIVE_ITEM_COUNT = 1;
3048
3078
  function toNode(genre, parentId) {
3049
3079
  return {
@@ -3072,7 +3102,7 @@ function GenreDetailPanel({
3072
3102
  }) {
3073
3103
  const { data: detail, isPending, isError } = (0, import_genre_tree28.useFetchGenreDetail)(genreUuid, scope, getBackendBaseUrl);
3074
3104
  const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: [] });
3075
- const view = (0, import_react17.useMemo)(() => {
3105
+ const view = (0, import_react18.useMemo)(() => {
3076
3106
  if (!detail) return null;
3077
3107
  const rootColor = (0, import_genre_tree_view5.getGenreTreeColor)(detail.root.wikidataId);
3078
3108
  const relative = (genre, parentId) => ({
@@ -3098,17 +3128,17 @@ function GenreDetailPanel({
3098
3128
  };
3099
3129
  }, [detail, scope]);
3100
3130
  if (isError) {
3101
- 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." });
3131
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
3102
3132
  }
3103
3133
  if (isPending || !detail || !view) {
3104
- 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: [
3105
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_ui17.Skeleton, { className: "h-6 w-2/3" }),
3106
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_ui17.Skeleton, { className: "h-4 w-full" }),
3107
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_ui17.Skeleton, { className: "h-4 w-3/4" })
3134
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
3135
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui17.Skeleton, { className: "h-6 w-2/3" }),
3136
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui17.Skeleton, { className: "h-4 w-full" }),
3137
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui17.Skeleton, { className: "h-4 w-3/4" })
3108
3138
  ] });
3109
3139
  }
3110
3140
  const selectGenre = (nodeId) => onSelectGenre(nodeId);
3111
- const family = /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
3141
+ const family = /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3112
3142
  import_genre_tree_view5.GenreNodeFamily,
3113
3143
  {
3114
3144
  node: view.node,
@@ -3119,20 +3149,20 @@ function GenreDetailPanel({
3119
3149
  onSelectNode: selectGenre
3120
3150
  }
3121
3151
  );
3122
- const ancestors = /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_genre_tree_view5.GenreNodeAncestors, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
3123
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
3152
+ const ancestors = /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_genre_tree_view5.GenreNodeAncestors, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
3153
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
3124
3154
  import_ui17.DetailPanel,
3125
3155
  {
3126
3156
  side: "left",
3127
3157
  title: view.node.name,
3128
3158
  headerAccent: { background: view.fill, color: import_genre_tree_view5.ACCENT_TEXT_COLOR },
3129
- headerActions: onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
3159
+ headerActions: onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3130
3160
  import_ui17.IconButton,
3131
3161
  {
3132
3162
  "aria-label": expanded ? "Shrink panel" : "Extend panel",
3133
3163
  style: { color: "inherit" },
3134
3164
  onClick: () => onExpandedChange(!expanded),
3135
- 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" })
3165
+ children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react13.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react13.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
3136
3166
  }
3137
3167
  ) : void 0,
3138
3168
  onClose,
@@ -3141,10 +3171,10 @@ function GenreDetailPanel({
3141
3171
  onExpandedChange,
3142
3172
  edgeTab,
3143
3173
  children: [
3144
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
3174
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3145
3175
  GenreInfoPanelBody,
3146
3176
  {
3147
- family: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_jsx_runtime39.Fragment, { children: [
3177
+ family: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
3148
3178
  family,
3149
3179
  ancestors
3150
3180
  ] }),
@@ -3169,13 +3199,13 @@ function GenreDetailPanel({
3169
3199
  var import_genre_tree_view6 = require("@behindthemusictree/genre-tree-view");
3170
3200
 
3171
3201
  // src/genre-tree/track-list-sidebar/TrackListSidebar.tsx
3172
- var import_react18 = require("react");
3202
+ var import_react19 = require("react");
3173
3203
  var import_lucide_react14 = require("lucide-react");
3174
3204
  var import_genre_tree29 = require("@behindthemusictree/app-kit/genre-tree");
3175
3205
  var import_genre_tree30 = require("@behindthemusictree/app-kit/genre-tree");
3176
3206
  var import_track_noun4 = require("@behindthemusictree/app-kit/track-noun");
3177
- var import_jsx_runtime40 = require("react/jsx-runtime");
3178
- var useBrowserLayoutEffect = typeof window === "undefined" ? import_react18.useEffect : import_react18.useLayoutEffect;
3207
+ var import_jsx_runtime41 = require("react/jsx-runtime");
3208
+ var useBrowserLayoutEffect = typeof window === "undefined" ? import_react19.useEffect : import_react19.useLayoutEffect;
3179
3209
  function TrackListSidebar({
3180
3210
  className: className2 = "",
3181
3211
  renderDuration,
@@ -3189,16 +3219,16 @@ function TrackListSidebar({
3189
3219
  }) {
3190
3220
  const { trackList, loadMore, autoAdvanceScrollRequest } = (0, import_genre_tree29.useTrackList)();
3191
3221
  const trackNoun = (0, import_track_noun4.useTrackNoun)();
3192
- const trackListRef = (0, import_react18.useRef)(null);
3193
- const previousOriginRef = (0, import_react18.useRef)(trackList?.origin ?? null);
3194
- const handledAutoAdvanceRequestIdRef = (0, import_react18.useRef)(null);
3195
- const shouldFollowPlaybackRef = (0, import_react18.useRef)(true);
3196
- const programmaticScrollTopRef = (0, import_react18.useRef)(null);
3197
- const userScrollIntentRef = (0, import_react18.useRef)(false);
3198
- const userScrollIntentTimerRef = (0, import_react18.useRef)(null);
3199
- const loadMoreSentinelRef = (0, import_react18.useRef)(null);
3222
+ const trackListRef = (0, import_react19.useRef)(null);
3223
+ const previousOriginRef = (0, import_react19.useRef)(trackList?.origin ?? null);
3224
+ const handledAutoAdvanceRequestIdRef = (0, import_react19.useRef)(null);
3225
+ const shouldFollowPlaybackRef = (0, import_react19.useRef)(true);
3226
+ const programmaticScrollTopRef = (0, import_react19.useRef)(null);
3227
+ const userScrollIntentRef = (0, import_react19.useRef)(false);
3228
+ const userScrollIntentTimerRef = (0, import_react19.useRef)(null);
3229
+ const loadMoreSentinelRef = (0, import_react19.useRef)(null);
3200
3230
  const hasMore = !!trackList && trackList.nextPage !== null;
3201
- (0, import_react18.useEffect)(() => {
3231
+ (0, import_react19.useEffect)(() => {
3202
3232
  const sentinel = loadMoreSentinelRef.current;
3203
3233
  if (!hasMore || !sentinel) return;
3204
3234
  const observer = new IntersectionObserver((entries) => {
@@ -3218,11 +3248,11 @@ function TrackListSidebar({
3218
3248
  programmaticScrollTopRef.current = null;
3219
3249
  if (trackListRef.current) trackListRef.current.scrollTop = 0;
3220
3250
  }, [trackList?.origin]);
3221
- (0, import_react18.useEffect)(() => {
3251
+ (0, import_react19.useEffect)(() => {
3222
3252
  const list = trackListRef.current;
3223
3253
  if (!isCollapsed && list?.scrollTop === 0) shouldFollowPlaybackRef.current = true;
3224
3254
  }, [isCollapsed]);
3225
- (0, import_react18.useEffect)(
3255
+ (0, import_react19.useEffect)(
3226
3256
  () => () => {
3227
3257
  if (userScrollIntentTimerRef.current !== null) clearTimeout(userScrollIntentTimerRef.current);
3228
3258
  },
@@ -3276,25 +3306,25 @@ function TrackListSidebar({
3276
3306
  }
3277
3307
  };
3278
3308
  const positionClasses = layout === "inline" ? "relative w-full flex h-full flex-col" : "fixed right-0 w-[50rem] flex flex-col";
3279
- return trackList ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
3309
+ return trackList ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
3280
3310
  "div",
3281
3311
  {
3282
3312
  "data-theme": "dark",
3283
3313
  className: `track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${className2}`,
3284
3314
  style: layout === "fixed" ? { top: "63.5px", maxHeight: "calc(100vh - 63.5px - 79px)" } : void 0,
3285
3315
  children: [
3286
- /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
3287
- /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
3288
- /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "origin flex text-xl", children: [
3289
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
3290
- /* @__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 }) })
3316
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
3317
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
3318
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "origin flex text-xl", children: [
3319
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
3320
+ /* @__PURE__ */ (0, import_jsx_runtime41.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_runtime41.jsx)("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
3291
3321
  ] }),
3292
- /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
3322
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
3293
3323
  trackList.origin.type === import_genre_tree30.TrackListOriginType.GENRE_PLAYLIST ? "Genre \u2022 " : trackList.origin.type === import_genre_tree30.TrackListOriginType.ARTIST ? "Artist \u2022 " : `${trackNoun.singular} playlist \u2022 `,
3294
3324
  `${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`
3295
3325
  ] })
3296
3326
  ] }),
3297
- (isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3327
+ (isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3298
3328
  "button",
3299
3329
  {
3300
3330
  type: "button",
@@ -3303,11 +3333,11 @@ function TrackListSidebar({
3303
3333
  "aria-expanded": !isCollapsed,
3304
3334
  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"}`,
3305
3335
  onClick: isCollapsed ? onExpand : onMinimize,
3306
- children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react14.ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
3336
+ children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_lucide_react14.ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
3307
3337
  }
3308
3338
  )
3309
3339
  ] }),
3310
- !isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
3340
+ !isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
3311
3341
  "ul",
3312
3342
  {
3313
3343
  ref: trackListRef,
@@ -3317,7 +3347,7 @@ function TrackListSidebar({
3317
3347
  onPointerDown: markUserScrollIntent,
3318
3348
  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",
3319
3349
  children: [
3320
- trackList.tracks.map((track, index) => /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3350
+ trackList.tracks.map((track, index) => /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3321
3351
  TrackItem,
3322
3352
  {
3323
3353
  track,
@@ -3328,7 +3358,7 @@ function TrackListSidebar({
3328
3358
  renderSongLink
3329
3359
  }
3330
3360
  ) }, track.uuid)),
3331
- hasMore && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
3361
+ hasMore && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
3332
3362
  ]
3333
3363
  }
3334
3364
  )
@@ -3338,7 +3368,7 @@ function TrackListSidebar({
3338
3368
  }
3339
3369
 
3340
3370
  // src/genre-tree/PlayerPanel.tsx
3341
- var import_react19 = require("react");
3371
+ var import_react20 = require("react");
3342
3372
  var import_tailwind_merge = require("tailwind-merge");
3343
3373
  var import_player13 = require("@behindthemusictree/app-kit/player");
3344
3374
  var import_genre_tree32 = require("@behindthemusictree/app-kit/genre-tree");
@@ -3348,7 +3378,7 @@ var import_ui18 = require("@behindthemusictree/ui");
3348
3378
  var import_lucide_react15 = require("lucide-react");
3349
3379
  var import_genre_tree31 = require("@behindthemusictree/app-kit/genre-tree");
3350
3380
  var import_player12 = require("@behindthemusictree/app-kit/player");
3351
- var import_jsx_runtime41 = require("react/jsx-runtime");
3381
+ var import_jsx_runtime42 = require("react/jsx-runtime");
3352
3382
  function PlayerControls() {
3353
3383
  const {
3354
3384
  duration,
@@ -3367,15 +3397,15 @@ function PlayerControls() {
3367
3397
  const safeDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;
3368
3398
  const safeCurrentTime = Number.isFinite(currentTime) ? Math.max(0, Math.min(currentTime, safeDuration)) : 0;
3369
3399
  const isPlaying = playState === import_player12.PlayStates.PLAYING;
3370
- return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
3400
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
3371
3401
  "section",
3372
3402
  {
3373
3403
  "aria-label": "Player controls",
3374
3404
  className: "player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200",
3375
3405
  children: [
3376
- /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
3377
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { "aria-label": "Elapsed time", children: (0, import_genre_tree31.formatTime)(safeCurrentTime) }),
3378
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3406
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
3407
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { "aria-label": "Elapsed time", children: (0, import_genre_tree31.formatTime)(safeCurrentTime) }),
3408
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
3379
3409
  "input",
3380
3410
  {
3381
3411
  "aria-label": "Seek",
@@ -3389,11 +3419,11 @@ function PlayerControls() {
3389
3419
  onChange: (event) => seekTo(Number(event.currentTarget.value))
3390
3420
  }
3391
3421
  ),
3392
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { "aria-label": "Duration", children: (0, import_genre_tree31.formatTime)(safeDuration) })
3422
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { "aria-label": "Duration", children: (0, import_genre_tree31.formatTime)(safeDuration) })
3393
3423
  ] }),
3394
- /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
3395
- /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
3396
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3424
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
3425
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
3426
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
3397
3427
  "button",
3398
3428
  {
3399
3429
  type: "button",
@@ -3402,10 +3432,10 @@ function PlayerControls() {
3402
3432
  disabled: !isReady || !hasPreviousTrack,
3403
3433
  onClick: playPreviousTrack,
3404
3434
  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",
3405
- children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_lucide_react15.SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
3435
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react15.SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
3406
3436
  }
3407
3437
  ),
3408
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3438
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
3409
3439
  "button",
3410
3440
  {
3411
3441
  type: "button",
@@ -3414,10 +3444,10 @@ function PlayerControls() {
3414
3444
  disabled: !isReady,
3415
3445
  onClick: handlePlayPauseAction,
3416
3446
  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",
3417
- 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" })
3447
+ children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react15.Pause, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react15.Play, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
3418
3448
  }
3419
3449
  ),
3420
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3450
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
3421
3451
  "button",
3422
3452
  {
3423
3453
  type: "button",
@@ -3426,14 +3456,14 @@ function PlayerControls() {
3426
3456
  disabled: !isReady || !hasNextTrack,
3427
3457
  onClick: playNextTrack,
3428
3458
  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",
3429
- children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_lucide_react15.SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
3459
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react15.SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
3430
3460
  }
3431
3461
  )
3432
3462
  ] }),
3433
- /* @__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: [
3434
- 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" }),
3435
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "sr-only", children: "Volume" }),
3436
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3463
+ /* @__PURE__ */ (0, import_jsx_runtime42.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: [
3464
+ volume === 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react15.VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react15.Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
3465
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "sr-only", children: "Volume" }),
3466
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
3437
3467
  "input",
3438
3468
  {
3439
3469
  "aria-label": "Volume",
@@ -3455,7 +3485,7 @@ function PlayerControls() {
3455
3485
  }
3456
3486
 
3457
3487
  // src/genre-tree/PlayerPanel.tsx
3458
- var import_jsx_runtime42 = require("react/jsx-runtime");
3488
+ var import_jsx_runtime43 = require("react/jsx-runtime");
3459
3489
  var PANEL_ID = "player-panel";
3460
3490
  var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
3461
3491
  function PlayerPanel({
@@ -3469,12 +3499,12 @@ function PlayerPanel({
3469
3499
  }) {
3470
3500
  const { playerTrackObject, retryPlayback } = (0, import_player13.usePlayer)();
3471
3501
  const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0, import_genre_tree32.useTrackListSidebarVisibility)();
3472
- const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = (0, import_react19.useState)("");
3502
+ const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = (0, import_react20.useState)("");
3473
3503
  const loadedTrackTitle = playerTrackObject?.track.title;
3474
- (0, import_react19.useEffect)(() => {
3504
+ (0, import_react20.useEffect)(() => {
3475
3505
  if (loadedTrackTitle) setLastLoadedTrackTitle(loadedTrackTitle);
3476
3506
  }, [loadedTrackTitle]);
3477
- const [uncontrolledExtended, setUncontrolledExtended] = (0, import_react19.useState)(false);
3507
+ const [uncontrolledExtended, setUncontrolledExtended] = (0, import_react20.useState)(false);
3478
3508
  const isExtendRequested = expanded ?? uncontrolledExtended;
3479
3509
  const setIsExtendRequested = (next) => {
3480
3510
  setUncontrolledExtended(next);
@@ -3495,7 +3525,7 @@ function PlayerPanel({
3495
3525
  onClick: () => setIsExtendRequested(true),
3496
3526
  expanded: false
3497
3527
  } : null;
3498
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
3528
+ return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(
3499
3529
  "div",
3500
3530
  {
3501
3531
  id: PANEL_ID,
@@ -3508,7 +3538,7 @@ function PlayerPanel({
3508
3538
  ),
3509
3539
  style: isExtended ? { width: extendedWidth } : void 0,
3510
3540
  children: [
3511
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
3541
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
3512
3542
  import_ui18.PanelEdgeTab,
3513
3543
  {
3514
3544
  side: "right",
@@ -3518,7 +3548,7 @@ function PlayerPanel({
3518
3548
  className: "absolute right-full top-1/2 -translate-y-1/2"
3519
3549
  }
3520
3550
  ),
3521
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
3551
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(
3522
3552
  "div",
3523
3553
  {
3524
3554
  className: (0, import_tailwind_merge.twMerge)(
@@ -3526,20 +3556,20 @@ function PlayerPanel({
3526
3556
  isExtended ? "flex-row" : "flex-col"
3527
3557
  ),
3528
3558
  children: [
3529
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
3530
- /* @__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 }) }),
3531
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "relative w-full shrink-0", children: [
3532
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_player13.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
3533
- playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: [
3559
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
3560
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
3561
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "relative w-full shrink-0", children: [
3562
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_player13.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
3563
+ playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: [
3534
3564
  playerTrackObject.loadError,
3535
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("button", { type: "button", className: "ml-2 underline", onClick: () => {
3565
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("button", { type: "button", className: "ml-2 underline", onClick: () => {
3536
3566
  void retryPlayback();
3537
3567
  }, children: "Retry" })
3538
3568
  ] })
3539
3569
  ] }),
3540
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(PlayerControls, {})
3570
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(PlayerControls, {})
3541
3571
  ] }),
3542
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
3572
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
3543
3573
  TrackListSidebar,
3544
3574
  {
3545
3575
  layout: "inline",
@@ -3566,11 +3596,11 @@ function PlayerPanel({
3566
3596
  var import_uuid = require("uuid");
3567
3597
  var import_ai = require("react-icons/ai");
3568
3598
  var import_genre_tree33 = require("@behindthemusictree/app-kit/genre-tree");
3569
- var import_jsx_runtime43 = require("react/jsx-runtime");
3599
+ var import_jsx_runtime44 = require("react/jsx-runtime");
3570
3600
  function Rating({ rating, handleChange }) {
3571
3601
  const instanceId = (0, import_uuid.v4)();
3572
- 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: [
3573
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
3602
+ return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
3603
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3574
3604
  "input",
3575
3605
  {
3576
3606
  id: `star-${instanceId}-${i}`,
@@ -3582,16 +3612,16 @@ function Rating({ rating, handleChange }) {
3582
3612
  onChange: handleChange
3583
3613
  }
3584
3614
  ),
3585
- /* @__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: [
3586
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
3615
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "flex", children: [
3616
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3587
3617
  import_ai.AiOutlineStar,
3588
3618
  {
3589
3619
  className: `w-7 h-7 absolute top-0 left-0 ${rating === import_genre_tree33.FORM_RATING_NULL_VALUE ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
3590
3620
  }
3591
3621
  ),
3592
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "flex-grow w-8" }),
3622
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { className: "flex-grow w-8" }),
3593
3623
  " "
3594
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
3624
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3595
3625
  import_ai.AiFillStar,
3596
3626
  {
3597
3627
  className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`