@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.
package/dist/index.cjs CHANGED
@@ -587,7 +587,7 @@ function AuthCallbackHandler({ renderStatus, ...options }) {
587
587
  }
588
588
 
589
589
  // src/genre-tree/GenreTreeView.tsx
590
- var import_react16 = require("react");
590
+ var import_react17 = require("react");
591
591
  var import_react_query3 = require("@tanstack/react-query");
592
592
  var import_lucide_react15 = require("lucide-react");
593
593
  var import_lucide_react16 = require("lucide-react");
@@ -1027,6 +1027,9 @@ function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
1027
1027
  ] });
1028
1028
  }
1029
1029
 
1030
+ // src/genre-tree/GenreTreeSearch.tsx
1031
+ var import_react9 = require("react");
1032
+
1030
1033
  // src/genre-tree/GenreSearch.tsx
1031
1034
  var import_lucide_react7 = require("lucide-react");
1032
1035
  var import_ui7 = require("@behindthemusictree/ui");
@@ -1227,6 +1230,44 @@ function GenreSearch({
1227
1230
  ) });
1228
1231
  }
1229
1232
 
1233
+ // src/genre-tree/GenreTreeSearch.tsx
1234
+ var import_jsx_runtime21 = require("react/jsx-runtime");
1235
+ function GenreTreeSearch({
1236
+ artistBaseUrl,
1237
+ songBaseUrl,
1238
+ onArtistSelect,
1239
+ onSongSelect,
1240
+ artistSearch,
1241
+ songSearch,
1242
+ SearchGroups,
1243
+ extraGroups = [],
1244
+ onQueryChange,
1245
+ onFocus,
1246
+ ...props
1247
+ }) {
1248
+ const [query, setQuery] = (0, import_react9.useState)("");
1249
+ const [focused, setFocused] = (0, import_react9.useState)(false);
1250
+ const deferredQuery = (0, import_react9.useDeferredValue)(query);
1251
+ const artistGroup = useArtistSearchGroup(artistBaseUrl, deferredQuery, onArtistSelect, artistSearch);
1252
+ const songGroup = useSongSearchGroup(songBaseUrl, deferredQuery, onSongSelect, songSearch);
1253
+ const render = (hostGroups) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1254
+ GenreSearch,
1255
+ {
1256
+ ...props,
1257
+ onQueryChange: (text) => {
1258
+ setQuery(text);
1259
+ onQueryChange?.(text);
1260
+ },
1261
+ onFocus: () => {
1262
+ setFocused(true);
1263
+ onFocus?.();
1264
+ },
1265
+ extraGroups: [...extraGroups, ...hostGroups, artistGroup, songGroup].filter((group) => group !== void 0)
1266
+ }
1267
+ );
1268
+ return SearchGroups ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(SearchGroups, { query: deferredQuery, focused, children: render }) : render([]);
1269
+ }
1270
+
1230
1271
  // src/genre-tree/GenreInfoPanelBody.tsx
1231
1272
  var import_genre_tree18 = require("@behindthemusictree/app-kit/genre-tree");
1232
1273
  var import_track_noun4 = require("@behindthemusictree/app-kit/track-noun");
@@ -1240,18 +1281,18 @@ var import_lucide_react10 = require("lucide-react");
1240
1281
  var import_popup4 = require("@behindthemusictree/app-kit/popup");
1241
1282
 
1242
1283
  // src/genre-tree/SharePopup.tsx
1243
- var import_react9 = require("react");
1284
+ var import_react10 = require("react");
1244
1285
  var import_lucide_react9 = require("lucide-react");
1245
1286
  var import_fa3 = require("react-icons/fa");
1246
1287
  var import_si = require("react-icons/si");
1247
1288
 
1248
1289
  // src/genre-tree/ShareDestinationButton.tsx
1249
- var import_jsx_runtime21 = require("react/jsx-runtime");
1290
+ var import_jsx_runtime22 = require("react/jsx-runtime");
1250
1291
  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";
1251
1292
  function Contents({ icon, label }) {
1252
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
1253
- /* @__PURE__ */ (0, import_jsx_runtime21.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 }),
1254
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "max-w-full truncate", children: label })
1293
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
1294
+ /* @__PURE__ */ (0, import_jsx_runtime22.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 }),
1295
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "max-w-full truncate", children: label })
1255
1296
  ] });
1256
1297
  }
1257
1298
  function ShareDestinationButton({
@@ -1263,29 +1304,29 @@ function ShareDestinationButton({
1263
1304
  ariaLabel
1264
1305
  }) {
1265
1306
  if (href) {
1266
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", "aria-label": ariaLabel, className, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Contents, { icon, label }) });
1307
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", "aria-label": ariaLabel, className, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Contents, { icon, label }) });
1267
1308
  }
1268
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("button", { type: "button", onClick, disabled, "aria-label": ariaLabel, className, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Contents, { icon, label }) });
1309
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("button", { type: "button", onClick, disabled, "aria-label": ariaLabel, className, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Contents, { icon, label }) });
1269
1310
  }
1270
1311
 
1271
1312
  // src/genre-tree/SharePreviewCard.tsx
1272
1313
  var import_lucide_react8 = require("lucide-react");
1273
- var import_jsx_runtime22 = require("react/jsx-runtime");
1314
+ var import_jsx_runtime23 = require("react/jsx-runtime");
1274
1315
  function SharePreviewCard({ target, url }) {
1275
1316
  const Icon = { genre: import_lucide_react8.Music2, artist: import_lucide_react8.UserRound, song: import_lucide_react8.Music }[target.kind];
1276
1317
  const typeLabel = { genre: "Genre", artist: "Artist", song: "Song" }[target.kind];
1277
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "rounded-3xl bg-[var(--color-surface-muted)] p-4", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex min-h-32 overflow-hidden rounded-2xl bg-[var(--color-surface)] shadow-lg", children: [
1278
- /* @__PURE__ */ (0, import_jsx_runtime22.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_runtime22.jsx)(Icon, { "aria-hidden": "true", className: "h-10 w-10" }) }),
1279
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col justify-center gap-1 p-4", children: [
1280
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "text-xs font-medium uppercase tracking-wide text-[var(--color-text-muted)]", children: typeLabel }),
1281
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "truncate text-base font-semibold text-[var(--color-text)]", title: target.name, children: target.name }),
1282
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "truncate text-sm text-[var(--color-primary)]", title: url, children: url })
1318
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "rounded-3xl bg-[var(--color-surface-muted)] p-4", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex min-h-32 overflow-hidden rounded-2xl bg-[var(--color-surface)] shadow-lg", children: [
1319
+ /* @__PURE__ */ (0, import_jsx_runtime23.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_runtime23.jsx)(Icon, { "aria-hidden": "true", className: "h-10 w-10" }) }),
1320
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col justify-center gap-1 p-4", children: [
1321
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "text-xs font-medium uppercase tracking-wide text-[var(--color-text-muted)]", children: typeLabel }),
1322
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "truncate text-base font-semibold text-[var(--color-text)]", title: target.name, children: target.name }),
1323
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "truncate text-sm text-[var(--color-primary)]", title: url, children: url })
1283
1324
  ] })
1284
1325
  ] }) });
1285
1326
  }
1286
1327
 
1287
1328
  // src/genre-tree/SharePopup.tsx
1288
- var import_jsx_runtime23 = require("react/jsx-runtime");
1329
+ var import_jsx_runtime24 = require("react/jsx-runtime");
1289
1330
  function resolveAbsoluteUrl(url) {
1290
1331
  try {
1291
1332
  const parsed = new URL(url, window.location.href);
@@ -1318,20 +1359,20 @@ async function copyText(text) {
1318
1359
  }
1319
1360
  }
1320
1361
  function SharePopup({ target, url, onClose }) {
1321
- const [absoluteUrl, setAbsoluteUrl] = (0, import_react9.useState)(url);
1322
- const [copyStatus, setCopyStatus] = (0, import_react9.useState)("idle");
1323
- const [canUseNativeShare, setCanUseNativeShare] = (0, import_react9.useState)(false);
1324
- const [shareError, setShareError] = (0, import_react9.useState)(null);
1325
- const [toastId, setToastId] = (0, import_react9.useState)(0);
1326
- (0, import_react9.useEffect)(() => {
1362
+ const [absoluteUrl, setAbsoluteUrl] = (0, import_react10.useState)(url);
1363
+ const [copyStatus, setCopyStatus] = (0, import_react10.useState)("idle");
1364
+ const [canUseNativeShare, setCanUseNativeShare] = (0, import_react10.useState)(false);
1365
+ const [shareError, setShareError] = (0, import_react10.useState)(null);
1366
+ const [toastId, setToastId] = (0, import_react10.useState)(0);
1367
+ (0, import_react10.useEffect)(() => {
1327
1368
  setAbsoluteUrl(resolveAbsoluteUrl(url) ?? "");
1328
1369
  setCopyStatus("idle");
1329
1370
  setShareError(null);
1330
1371
  }, [url]);
1331
- (0, import_react9.useEffect)(() => {
1372
+ (0, import_react10.useEffect)(() => {
1332
1373
  setCanUseNativeShare(typeof navigator !== "undefined" && typeof navigator.share === "function");
1333
1374
  }, []);
1334
- const destinationUrls = (0, import_react9.useMemo)(() => {
1375
+ const destinationUrls = (0, import_react10.useMemo)(() => {
1335
1376
  if (!absoluteUrl) return null;
1336
1377
  const message = `${target.name} \u2014 ${absoluteUrl}`;
1337
1378
  return {
@@ -1349,7 +1390,7 @@ function SharePopup({ target, url, onClose }) {
1349
1390
  setCopyStatus(copied ? "idle" : "failed");
1350
1391
  if (copied) setToastId((id) => id + 1);
1351
1392
  };
1352
- const dismissToast = (0, import_react9.useCallback)(() => setToastId(0), []);
1393
+ const dismissToast = (0, import_react10.useCallback)(() => setToastId(0), []);
1353
1394
  const handleNativeShare = async () => {
1354
1395
  if (!absoluteUrl || !navigator.share) return;
1355
1396
  setShareError(null);
@@ -1360,8 +1401,8 @@ function SharePopup({ target, url, onClose }) {
1360
1401
  setShareError("Could not open the share options.");
1361
1402
  }
1362
1403
  };
1363
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
1364
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1404
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
1405
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1365
1406
  BasePopup_default,
1366
1407
  {
1367
1408
  title: "Share",
@@ -1376,67 +1417,67 @@ function SharePopup({ target, url, onClose }) {
1376
1417
  titleTextClassName: "!text-xl !font-semibold !text-[var(--color-text)]",
1377
1418
  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)]",
1378
1419
  contentClassName: "!rounded-none !bg-transparent !px-6 !pb-6 !pt-2",
1379
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex flex-col gap-4", children: [
1380
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(SharePreviewCard, { target, url: absoluteUrl || url }),
1381
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "grid grid-cols-3 gap-1 rounded-3xl bg-[var(--color-surface)] p-3 sm:grid-cols-4", children: [
1382
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1420
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex flex-col gap-4", children: [
1421
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(SharePreviewCard, { target, url: absoluteUrl || url }),
1422
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "grid grid-cols-3 gap-1 rounded-3xl bg-[var(--color-surface)] p-3 sm:grid-cols-4", children: [
1423
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1383
1424
  ShareDestinationButton,
1384
1425
  {
1385
1426
  label: "Copy Link",
1386
- icon: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react9.Link2, { "aria-hidden": "true", className: "h-6 w-6" }),
1427
+ icon: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react9.Link2, { "aria-hidden": "true", className: "h-6 w-6" }),
1387
1428
  onClick: () => void handleCopy(),
1388
1429
  ariaLabel: "Copy link",
1389
1430
  disabled: !absoluteUrl
1390
1431
  }
1391
1432
  ),
1392
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1433
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1393
1434
  ShareDestinationButton,
1394
1435
  {
1395
1436
  label: "WhatsApp",
1396
- icon: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_fa3.FaWhatsapp, { "aria-hidden": "true", className: "h-9 w-9 text-[#25D366]" }),
1437
+ icon: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_fa3.FaWhatsapp, { "aria-hidden": "true", className: "h-9 w-9 text-[#25D366]" }),
1397
1438
  href: destinationUrls?.whatsapp,
1398
1439
  ariaLabel: `Share ${target.name} via WhatsApp`,
1399
1440
  disabled: !absoluteUrl
1400
1441
  }
1401
1442
  ),
1402
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1443
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1403
1444
  ShareDestinationButton,
1404
1445
  {
1405
1446
  label: "Gmail",
1406
- icon: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_si.SiGmail, { "aria-hidden": "true", className: "h-8 w-8 text-[#EA4335]" }),
1447
+ icon: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_si.SiGmail, { "aria-hidden": "true", className: "h-8 w-8 text-[#EA4335]" }),
1407
1448
  href: destinationUrls?.gmail,
1408
1449
  ariaLabel: `Email ${target.name} with Gmail`,
1409
1450
  disabled: !absoluteUrl
1410
1451
  }
1411
1452
  ),
1412
- canUseNativeShare && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1453
+ canUseNativeShare && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1413
1454
  ShareDestinationButton,
1414
1455
  {
1415
1456
  label: "More",
1416
- icon: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react9.Share2, { "aria-hidden": "true", className: "h-6 w-6" }),
1457
+ icon: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react9.Share2, { "aria-hidden": "true", className: "h-6 w-6" }),
1417
1458
  onClick: () => void handleNativeShare(),
1418
1459
  ariaLabel: "More sharing options",
1419
1460
  disabled: !absoluteUrl
1420
1461
  }
1421
1462
  )
1422
1463
  ] }),
1423
- copyStatus === "failed" && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: "Could not copy the link." }),
1424
- shareError && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: shareError })
1464
+ copyStatus === "failed" && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: "Could not copy the link." }),
1465
+ shareError && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: shareError })
1425
1466
  ] })
1426
1467
  }
1427
1468
  ),
1428
- toastId > 0 && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Toast, { message: "Link copied", onClose: dismissToast }, toastId)
1469
+ toastId > 0 && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(Toast, { message: "Link copied", onClose: dismissToast }, toastId)
1429
1470
  ] });
1430
1471
  }
1431
1472
 
1432
1473
  // src/genre-tree/ShareActionButton.tsx
1433
- var import_jsx_runtime24 = require("react/jsx-runtime");
1474
+ var import_jsx_runtime25 = require("react/jsx-runtime");
1434
1475
  function ShareActionButton({ target, url }) {
1435
1476
  const { showPopup, hidePopup } = (0, import_popup4.usePopup)();
1436
1477
  const openSharePopup = () => {
1437
- showPopup(/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(SharePopup, { target, url, onClose: hidePopup }));
1478
+ showPopup(/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(SharePopup, { target, url, onClose: hidePopup }));
1438
1479
  };
1439
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
1480
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
1440
1481
  "button",
1441
1482
  {
1442
1483
  type: "button",
@@ -1445,7 +1486,7 @@ function ShareActionButton({ target, url }) {
1445
1486
  "aria-label": `Share ${target.name}`,
1446
1487
  title: `Share ${target.name}`,
1447
1488
  children: [
1448
- /* @__PURE__ */ (0, import_jsx_runtime24.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_runtime24.jsx)(import_lucide_react10.Share2, { "aria-hidden": "true", className: "w-5 h-5" }) }),
1489
+ /* @__PURE__ */ (0, import_jsx_runtime25.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_runtime25.jsx)(import_lucide_react10.Share2, { "aria-hidden": "true", className: "w-5 h-5" }) }),
1449
1490
  "Share"
1450
1491
  ]
1451
1492
  }
@@ -1453,7 +1494,7 @@ function ShareActionButton({ target, url }) {
1453
1494
  }
1454
1495
 
1455
1496
  // src/genre-tree/GenreActionBand.tsx
1456
- var import_jsx_runtime25 = require("react/jsx-runtime");
1497
+ var import_jsx_runtime26 = require("react/jsx-runtime");
1457
1498
  function GenreActionBand({
1458
1499
  node,
1459
1500
  onPlayPause,
@@ -1475,7 +1516,7 @@ function GenreActionBand({
1475
1516
  items.push({
1476
1517
  key: "play-pause",
1477
1518
  label: isPlaying ? "Pause" : "Play",
1478
- icon: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react11.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react11.Play, { "aria-hidden": "true", className: "w-5 h-5" }),
1519
+ icon: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react11.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react11.Play, { "aria-hidden": "true", className: "w-5 h-5" }),
1479
1520
  onClick: onPlayPause
1480
1521
  });
1481
1522
  }
@@ -1483,24 +1524,24 @@ function GenreActionBand({
1483
1524
  items.push({
1484
1525
  key: "zoom-in",
1485
1526
  label: "Focus",
1486
- icon: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react11.ZoomIn, { "aria-hidden": "true", className: "w-5 h-5" }),
1527
+ icon: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react11.ZoomIn, { "aria-hidden": "true", className: "w-5 h-5" }),
1487
1528
  onClick: onZoomIn
1488
1529
  });
1489
1530
  }
1490
1531
  if (onAddChild) {
1491
- adminItems.push({ key: "add-child", label: "Add", icon: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react11.Plus, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onAddChild });
1532
+ adminItems.push({ key: "add-child", label: "Add", icon: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react11.Plus, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onAddChild });
1492
1533
  }
1493
1534
  if (onRename) {
1494
- adminItems.push({ key: "rename", label: "Rename", icon: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react11.Pencil, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onRename });
1535
+ adminItems.push({ key: "rename", label: "Rename", icon: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react11.Pencil, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onRename });
1495
1536
  }
1496
1537
  if (onChangeParent) {
1497
- adminItems.push({ key: "change-parent", label: "Parent", icon: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react11.MoveUpRight, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onChangeParent });
1538
+ adminItems.push({ key: "change-parent", label: "Parent", icon: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react11.MoveUpRight, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onChangeParent });
1498
1539
  }
1499
1540
  if (onToggleSide) {
1500
1541
  adminItems.push({
1501
1542
  key: "toggle-side",
1502
1543
  label: side === "pop" ? "Make core" : "Make pop",
1503
- icon: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react11.ArrowLeftRight, { "aria-hidden": "true", className: "w-5 h-5" }),
1544
+ icon: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react11.ArrowLeftRight, { "aria-hidden": "true", className: "w-5 h-5" }),
1504
1545
  onClick: onToggleSide,
1505
1546
  disabled: !!toggleSideDisabledReason,
1506
1547
  title: toggleSideDisabledReason
@@ -1516,7 +1557,7 @@ function GenreActionBand({
1516
1557
  });
1517
1558
  }
1518
1559
  if (items.length === 0 && (!shareTarget || !shareUrl) && adminItems.length === 0) return null;
1519
- const renderItems = (list) => list.map((item) => /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
1560
+ const renderItems = (list) => list.map((item) => /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
1520
1561
  "button",
1521
1562
  {
1522
1563
  type: "button",
@@ -1525,25 +1566,25 @@ function GenreActionBand({
1525
1566
  onClick: item.onClick,
1526
1567
  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",
1527
1568
  children: [
1528
- /* @__PURE__ */ (0, import_jsx_runtime25.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 }),
1569
+ /* @__PURE__ */ (0, import_jsx_runtime26.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 }),
1529
1570
  item.label
1530
1571
  ]
1531
1572
  },
1532
1573
  item.key
1533
1574
  ));
1534
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: [
1535
- (items.length > 0 || shareTarget && shareUrl) && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
1575
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: [
1576
+ (items.length > 0 || shareTarget && shareUrl) && /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
1536
1577
  renderItems(items),
1537
- shareTarget && shareUrl && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
1578
+ shareTarget && shareUrl && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
1538
1579
  ] }),
1539
- adminItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
1580
+ adminItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
1540
1581
  "section",
1541
1582
  {
1542
1583
  "aria-label": "Admin actions",
1543
1584
  className: `px-3.5 py-3 ${items.length > 0 ? "border-t border-[var(--color-border)]" : ""}`,
1544
1585
  children: [
1545
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("h3", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-[var(--color-text-muted)]", children: "Admin" }),
1546
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "flex flex-wrap gap-1", children: renderItems(adminItems) })
1586
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("h3", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-[var(--color-text-muted)]", children: "Admin" }),
1587
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "flex flex-wrap gap-1", children: renderItems(adminItems) })
1547
1588
  ]
1548
1589
  }
1549
1590
  )
@@ -1555,20 +1596,20 @@ var import_ui9 = require("@behindthemusictree/ui");
1555
1596
  var import_genre_tree17 = require("@behindthemusictree/app-kit/genre-tree");
1556
1597
 
1557
1598
  // src/genre-tree/ClampedText.tsx
1558
- var import_react10 = require("react");
1559
- var import_jsx_runtime26 = require("react/jsx-runtime");
1599
+ var import_react11 = require("react");
1600
+ var import_jsx_runtime27 = require("react/jsx-runtime");
1560
1601
  function ClampedText({ children, className: className2 = "" }) {
1561
- const ref = (0, import_react10.useRef)(null);
1562
- const [expanded, setExpanded] = (0, import_react10.useState)(false);
1563
- const [overflows, setOverflows] = (0, import_react10.useState)(false);
1564
- (0, import_react10.useLayoutEffect)(() => {
1602
+ const ref = (0, import_react11.useRef)(null);
1603
+ const [expanded, setExpanded] = (0, import_react11.useState)(false);
1604
+ const [overflows, setOverflows] = (0, import_react11.useState)(false);
1605
+ (0, import_react11.useLayoutEffect)(() => {
1565
1606
  const element = ref.current;
1566
1607
  if (!element || expanded) return;
1567
1608
  setOverflows(element.scrollHeight > element.clientHeight);
1568
1609
  }, [children, expanded]);
1569
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_jsx_runtime26.Fragment, { children: [
1570
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { ref, className: `${className2} ${expanded ? "" : "line-clamp-3"}`, children }),
1571
- (overflows || expanded) && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1610
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
1611
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { ref, className: `${className2} ${expanded ? "" : "line-clamp-3"}`, children }),
1612
+ (overflows || expanded) && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1572
1613
  "button",
1573
1614
  {
1574
1615
  type: "button",
@@ -1582,7 +1623,7 @@ function ClampedText({ children, className: className2 = "" }) {
1582
1623
  }
1583
1624
 
1584
1625
  // src/genre-tree/GenrePlaylistTracks.tsx
1585
- var import_react11 = require("react");
1626
+ var import_react12 = require("react");
1586
1627
  var import_react_query = require("@tanstack/react-query");
1587
1628
  var import_zod = require("zod");
1588
1629
  var import_fa4 = require("react-icons/fa");
@@ -1603,15 +1644,15 @@ var import_genre_tree15 = require("@behindthemusictree/app-kit/genre-tree");
1603
1644
 
1604
1645
  // src/genre-tree/MusicBrainzRecordingLink.tsx
1605
1646
  var import_genre_tree10 = require("@behindthemusictree/app-kit/genre-tree");
1606
- var import_jsx_runtime27 = require("react/jsx-runtime");
1647
+ var import_jsx_runtime28 = require("react/jsx-runtime");
1607
1648
  function MusicBrainzRecordingLink({
1608
1649
  song,
1609
1650
  children,
1610
1651
  className: className2 = ""
1611
1652
  }) {
1612
1653
  const url = (0, import_genre_tree10.musicBrainzRecordingUrl)(song);
1613
- if (!url) return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_jsx_runtime27.Fragment, { children });
1614
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1654
+ if (!url) return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_jsx_runtime28.Fragment, { children });
1655
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1615
1656
  "a",
1616
1657
  {
1617
1658
  href: url,
@@ -1628,7 +1669,7 @@ function MusicBrainzRecordingLink({
1628
1669
  // src/genre-tree/GenrePlaylistTracks.tsx
1629
1670
  var import_genre_tree16 = require("@behindthemusictree/app-kit/genre-tree");
1630
1671
  var import_track_noun2 = require("@behindthemusictree/app-kit/track-noun");
1631
- var import_jsx_runtime28 = require("react/jsx-runtime");
1672
+ var import_jsx_runtime29 = require("react/jsx-runtime");
1632
1673
  var CATALOGUE_PAGE_SIZE = 100;
1633
1674
  var SongPageSchema = (0, import_transport6.PaginatedResponseSchema)(import_genre_tree14.SongDetailedSchema).extend({
1634
1675
  overallTotal: import_zod.z.number().nullable(),
@@ -1684,8 +1725,8 @@ function GenrePlaylistTracks({
1684
1725
  getNextPageParam: (lastPage) => lastPage.nextPage ?? void 0,
1685
1726
  enabled: playback.ready && (scope !== "me" || sessionRestored && !!session?.accessToken)
1686
1727
  });
1687
- const [sentinel, setSentinel] = (0, import_react11.useState)(null);
1688
- (0, import_react11.useEffect)(() => {
1728
+ const [sentinel, setSentinel] = (0, import_react12.useState)(null);
1729
+ (0, import_react12.useEffect)(() => {
1689
1730
  if (!hasNextPage || isFetchingNextPage || !sentinel) return;
1690
1731
  const observer = new IntersectionObserver((entries) => {
1691
1732
  if (entries.some((entry) => entry.isIntersecting)) {
@@ -1707,35 +1748,35 @@ function GenrePlaylistTracks({
1707
1748
  }) : scope === "reference" ? playNewTrackListFromGenreCatalogue({ uuid: genreUuid, name: genrePlaylist.name }, scope, { startSongUuid, ...selection && { selection } }) : playNewTrackListFromGenrePlaylist(genrePlaylist, scope, { startSongUuid });
1708
1749
  started.catch((error) => {
1709
1750
  console.error("Failed to play genre playlist:", error);
1710
- showPopup(/* @__PURE__ */ (0, import_jsx_runtime28.jsx)(InternalErrorPopup, { errorCode: import_transport8.ErrorCode.CLIENT_UNKNOWN }));
1751
+ showPopup(/* @__PURE__ */ (0, import_jsx_runtime29.jsx)(InternalErrorPopup, { errorCode: import_transport8.ErrorCode.CLIENT_UNKNOWN }));
1711
1752
  });
1712
1753
  };
1713
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_ui8.PanelSection, { title: `${(0, import_track_noun2.capitalize)(trackNoun.plural)}${total !== void 0 ? ` (${total})` : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { children: [
1754
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui8.PanelSection, { title: `${(0, import_track_noun2.capitalize)(trackNoun.plural)}${total !== void 0 ? ` (${total})` : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { children: [
1714
1755
  renderTracksToolbar?.(),
1715
- isPending ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_ui8.Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("p", { role: "alert", children: [
1756
+ isPending ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui8.Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("p", { role: "alert", children: [
1716
1757
  "Couldn't load ",
1717
1758
  trackNoun.plural,
1718
1759
  ".",
1719
1760
  " ",
1720
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
1721
- ] }) : tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("p", { children: "\u2014" }) : /* @__PURE__ */ (0, import_jsx_runtime28.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: [
1761
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
1762
+ ] }) : tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { children: "\u2014" }) : /* @__PURE__ */ (0, import_jsx_runtime29.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: [
1722
1763
  tracks.map((track, index) => {
1723
1764
  const label = track.artists?.length ? `${track.title} \u2014 ${track.artists.map((artist2) => artist2.name).join(", ")}` : track.title;
1724
1765
  const isCurrent = playerTrackObject?.track.id === track.uuid;
1725
1766
  const isPlaying = isCurrent && playState === import_player5.PlayStates.PLAYING;
1726
- const text = /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { className: TEXT_CELL, children: [
1727
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: TITLE_CELL, children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree14.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
1728
- track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: SEPARATOR, children: " \u2014 " }) : null,
1729
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_genre_tree16.ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
1730
- track.album && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: ALBUM_CELL, children: track.album.name })
1767
+ const text = /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("span", { className: TEXT_CELL, children: [
1768
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: TITLE_CELL, children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree14.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
1769
+ track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: SEPARATOR, children: " \u2014 " }) : null,
1770
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_genre_tree16.ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
1771
+ track.album && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: ALBUM_CELL, children: track.album.name })
1731
1772
  ] });
1732
1773
  const leading = renderSongLeading?.(track, index);
1733
1774
  const actions = renderSongActions?.(track);
1734
1775
  const isUnplayable = (0, import_genre_tree14.isSong)(track) && (0, import_genre_tree14.songUnplayableReason)(track) !== null;
1735
1776
  if (isUnplayable) {
1736
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("li", { className: "flex min-h-8 items-center gap-1 px-1 opacity-50", children: [
1777
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("li", { className: "flex min-h-8 items-center gap-1 px-1 opacity-50", children: [
1737
1778
  leading,
1738
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1779
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
1739
1780
  "button",
1740
1781
  {
1741
1782
  type: "button",
@@ -1743,27 +1784,27 @@ function GenrePlaylistTracks({
1743
1784
  "aria-disabled": "true",
1744
1785
  "aria-label": label,
1745
1786
  className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full opacity-0 cursor-not-allowed",
1746
- children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_fa4.FaPlay, { size: 10 })
1787
+ children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_fa4.FaPlay, { size: 10 })
1747
1788
  }
1748
1789
  ),
1749
1790
  text,
1750
1791
  actions
1751
1792
  ] }, track.uuid);
1752
1793
  }
1753
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
1794
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
1754
1795
  "li",
1755
1796
  {
1756
1797
  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)]" : ""}`,
1757
1798
  children: [
1758
1799
  leading,
1759
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1800
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
1760
1801
  "button",
1761
1802
  {
1762
1803
  type: "button",
1763
1804
  "aria-label": `${isPlaying ? "Pause" : "Play"} ${label}`,
1764
1805
  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"}`,
1765
1806
  onClick: () => isCurrent ? handlePlayPauseAction() : play(track.uuid),
1766
- children: isCurrent && playState === import_player5.PlayStates.LOADING ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_ui8.RingLoader, { size: 10 }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_fa4.FaPause, { size: 10 }) : /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_fa4.FaPlay, { size: 10 })
1807
+ children: isCurrent && playState === import_player5.PlayStates.LOADING ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui8.RingLoader, { size: 10 }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_fa4.FaPause, { size: 10 }) : /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_fa4.FaPlay, { size: 10 })
1767
1808
  }
1768
1809
  ),
1769
1810
  text,
@@ -1773,7 +1814,7 @@ function GenrePlaylistTracks({
1773
1814
  track.uuid
1774
1815
  );
1775
1816
  }),
1776
- hasNextPage && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("li", { ref: setSentinel, "aria-hidden": "true", className: "h-4" })
1817
+ hasNextPage && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("li", { ref: setSentinel, "aria-hidden": "true", className: "h-4" })
1777
1818
  ] })
1778
1819
  ] }) });
1779
1820
  }
@@ -1781,7 +1822,7 @@ function GenrePlaylistTracks({
1781
1822
  // src/genre-tree/GenreDetailExtras.tsx
1782
1823
  var import_track_noun3 = require("@behindthemusictree/app-kit/track-noun");
1783
1824
  var import_si2 = require("react-icons/si");
1784
- var import_jsx_runtime29 = require("react/jsx-runtime");
1825
+ var import_jsx_runtime30 = require("react/jsx-runtime");
1785
1826
  function GenreDetailExtras({
1786
1827
  genreUuid,
1787
1828
  genrePlaylist,
@@ -1806,17 +1847,17 @@ function GenreDetailExtras({
1806
1847
  criteriaOverviewSchema
1807
1848
  );
1808
1849
  if (isPending) {
1809
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(import_jsx_runtime29.Fragment, { children: [
1810
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui9.PanelSection, { title: "Summary", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { "aria-busy": "true", "data-testid": "genre-detail-extras-skeleton", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui9.Skeleton, { className: "h-4 w-3/4" }) }) }),
1850
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_jsx_runtime30.Fragment, { children: [
1851
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui9.PanelSection, { title: "Summary", children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { "aria-busy": "true", "data-testid": "genre-detail-extras-skeleton", children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui9.Skeleton, { className: "h-4 w-3/4" }) }) }),
1811
1852
  family
1812
1853
  ] });
1813
1854
  }
1814
- if (!overview) return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_jsx_runtime29.Fragment, { children: family });
1855
+ if (!overview) return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_jsx_runtime30.Fragment, { children: family });
1815
1856
  const { summary, summarySourceUrl, essentialSongs } = overview;
1816
1857
  const wikidataUrl = wikidataId ? `https://www.wikidata.org/wiki/${encodeURIComponent(wikidataId)}` : null;
1817
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(import_jsx_runtime29.Fragment, { children: [
1818
- (summarySourceUrl || wikidataUrl) && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui9.PanelSection, { title: "Links", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex flex-wrap gap-x-3 gap-y-1 text-xs text-[var(--color-text)]", children: [
1819
- summarySourceUrl && /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
1858
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_jsx_runtime30.Fragment, { children: [
1859
+ (summarySourceUrl || wikidataUrl) && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui9.PanelSection, { title: "Links", children: /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "flex flex-wrap gap-x-3 gap-y-1 text-xs text-[var(--color-text)]", children: [
1860
+ summarySourceUrl && /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
1820
1861
  "a",
1821
1862
  {
1822
1863
  href: summarySourceUrl,
@@ -1824,12 +1865,12 @@ function GenreDetailExtras({
1824
1865
  rel: "noopener noreferrer",
1825
1866
  className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
1826
1867
  children: [
1827
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_si2.SiWikipedia, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1868
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_si2.SiWikipedia, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1828
1869
  "Wikipedia"
1829
1870
  ]
1830
1871
  }
1831
1872
  ),
1832
- wikidataUrl && /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
1873
+ wikidataUrl && /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
1833
1874
  "a",
1834
1875
  {
1835
1876
  href: wikidataUrl,
@@ -1837,17 +1878,17 @@ function GenreDetailExtras({
1837
1878
  rel: "noopener noreferrer",
1838
1879
  className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
1839
1880
  children: [
1840
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_si2.SiWikidata, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1881
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_si2.SiWikidata, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1841
1882
  "Wikidata"
1842
1883
  ]
1843
1884
  }
1844
1885
  )
1845
1886
  ] }) }),
1846
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui9.PanelSection, { title: "Summary", children: renderSummary ? renderSummary(overview) : /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(ClampedText, { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: summary ?? "\u2014" }, genreUuid) }),
1887
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui9.PanelSection, { title: "Summary", children: renderSummary ? renderSummary(overview) : /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ClampedText, { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: summary ?? "\u2014" }, genreUuid) }),
1847
1888
  family,
1848
1889
  renderGenreDetailExtras?.(overview),
1849
- (renderEssentialTracks || essentialSongs) && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui9.PanelSection, { title: `Essential ${trackNoun.plural}`, children: renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && essentialSongs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("ul", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: essentialSongs.map((track) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("li", { className: "py-1", children: track.title }, track.uuid)) }) : /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: "\u2014" }) }),
1850
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
1890
+ (renderEssentialTracks || essentialSongs) && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui9.PanelSection, { title: `Essential ${trackNoun.plural}`, children: renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && essentialSongs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("ul", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: essentialSongs.map((track) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("li", { className: "py-1", children: track.title }, track.uuid)) }) : /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("p", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: "\u2014" }) }),
1891
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1851
1892
  GenrePlaylistTracks,
1852
1893
  {
1853
1894
  genreUuid,
@@ -1864,8 +1905,8 @@ function GenreDetailExtras({
1864
1905
  }
1865
1906
 
1866
1907
  // src/genre-tree/GenreInfoPanelBody.tsx
1867
- var import_jsx_runtime30 = require("react/jsx-runtime");
1868
- var import_react12 = require("react");
1908
+ var import_jsx_runtime31 = require("react/jsx-runtime");
1909
+ var import_react13 = require("react");
1869
1910
  function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
1870
1911
  const { isPlaying, setIsPlaying } = (0, import_player6.usePlayer)();
1871
1912
  const isCurrentTrackList = playback.playingNodeId === genrePlaylist.uuid;
@@ -1878,7 +1919,7 @@ function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
1878
1919
  console.error("Failed to load genre playlist tracks:", error);
1879
1920
  });
1880
1921
  };
1881
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1922
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
1882
1923
  GenreActionBand,
1883
1924
  {
1884
1925
  ...bandProps,
@@ -1907,24 +1948,24 @@ function GenreInfoPanelBody({
1907
1948
  const scopes = counts.genreAndSubgenresTracksCount;
1908
1949
  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`;
1909
1950
  };
1910
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_jsx_runtime30.Fragment, { children: [
1951
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_jsx_runtime31.Fragment, { children: [
1911
1952
  toolbar,
1912
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(GenreNodeActionBand, { ...band, genrePlaylist, playback }),
1913
- summary && catalogue && /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "px-3.5 py-3 text-sm text-[var(--color-text)]", children: [
1914
- /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("p", { children: [
1953
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(GenreNodeActionBand, { ...band, genrePlaylist, playback }),
1954
+ summary && catalogue && /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "px-3.5 py-3 text-sm text-[var(--color-text)]", children: [
1955
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("p", { children: [
1915
1956
  "Catalogue ",
1916
1957
  noun.plural,
1917
1958
  ": ",
1918
1959
  describeCounts(catalogue)
1919
1960
  ] }),
1920
- /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("p", { children: [
1961
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("p", { children: [
1921
1962
  "Playlist ",
1922
1963
  noun.plural,
1923
1964
  ": ",
1924
1965
  describeCounts(summary)
1925
1966
  ] })
1926
1967
  ] }),
1927
- /* @__PURE__ */ (0, import_react12.createElement)(
1968
+ /* @__PURE__ */ (0, import_react13.createElement)(
1928
1969
  GenreDetailExtras,
1929
1970
  {
1930
1971
  ...extras,
@@ -1942,9 +1983,9 @@ function GenreInfoPanelBody({
1942
1983
  }
1943
1984
 
1944
1985
  // src/genre-tree/ChangeParentPopup.tsx
1945
- var import_react13 = require("react");
1986
+ var import_react14 = require("react");
1946
1987
  var import_genre_tree19 = require("@behindthemusictree/app-kit/genre-tree");
1947
- var import_jsx_runtime31 = require("react/jsx-runtime");
1988
+ var import_jsx_runtime32 = require("react/jsx-runtime");
1948
1989
  function ChangeParentPopup({
1949
1990
  genrePlaylist,
1950
1991
  genrePlaylists,
@@ -1954,7 +1995,7 @@ function ChangeParentPopup({
1954
1995
  onParentChanged
1955
1996
  }) {
1956
1997
  const { mutate, isPending, formErrors } = (0, import_genre_tree19.useUpdateGenre)(scope, getBackendBaseUrl);
1957
- const candidates = (0, import_react13.useMemo)(
1998
+ const candidates = (0, import_react14.useMemo)(
1958
1999
  () => (0, import_genre_tree19.getReparentCandidates)(genrePlaylists, genrePlaylist.uuid),
1959
2000
  [genrePlaylists, genrePlaylist.uuid]
1960
2001
  );
@@ -1972,19 +2013,19 @@ function ChangeParentPopup({
1972
2013
  }
1973
2014
  );
1974
2015
  };
1975
- (0, import_react13.useEffect)(() => {
2016
+ (0, import_react14.useEffect)(() => {
1976
2017
  const handleKeyDown = (event) => {
1977
2018
  if (event.key === "Escape") onClose();
1978
2019
  };
1979
2020
  window.addEventListener("keydown", handleKeyDown);
1980
2021
  return () => window.removeEventListener("keydown", handleKeyDown);
1981
2022
  }, [onClose]);
1982
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
1983
- /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("p", { className: "mb-3 text-sm text-[var(--color-text-muted)]", children: [
2023
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
2024
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("p", { className: "mb-3 text-sm text-[var(--color-text-muted)]", children: [
1984
2025
  "Current parent: ",
1985
2026
  genrePlaylist.parent?.name ?? "none"
1986
2027
  ] }),
1987
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
2028
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1988
2029
  GenreSearch,
1989
2030
  {
1990
2031
  genrePlaylists: candidates,
@@ -1992,7 +2033,7 @@ function ChangeParentPopup({
1992
2033
  placeholder: "Search a new parent\u2026"
1993
2034
  }
1994
2035
  ) }),
1995
- formErrors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { role: "alert", className: "mt-3 text-sm text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
2036
+ formErrors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { role: "alert", className: "mt-3 text-sm text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
1996
2037
  ] });
1997
2038
  }
1998
2039
 
@@ -2007,7 +2048,7 @@ var import_ui11 = require("@behindthemusictree/ui");
2007
2048
  var import_lucide_react12 = require("lucide-react");
2008
2049
  var import_si3 = require("react-icons/si");
2009
2050
  var import_ui10 = require("@behindthemusictree/ui");
2010
- var import_jsx_runtime32 = require("react/jsx-runtime");
2051
+ var import_jsx_runtime33 = require("react/jsx-runtime");
2011
2052
  var LINK_KINDS = {
2012
2053
  official: { label: "Official website", Icon: import_lucide_react12.Globe },
2013
2054
  wikipedia: { label: "Wikipedia", Icon: import_si3.SiWikipedia },
@@ -2044,7 +2085,7 @@ function artistLinks(artist) {
2044
2085
  return links;
2045
2086
  }
2046
2087
  function ArtistLinks({ links }) {
2047
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui10.Chip, { href: url, icon: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
2088
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ui10.Chip, { href: url, icon: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
2048
2089
  }
2049
2090
 
2050
2091
  // src/genre-tree/artist/formatYears.ts
@@ -2055,7 +2096,7 @@ function formatYears(beginYear, endYear, ended) {
2055
2096
  }
2056
2097
 
2057
2098
  // src/genre-tree/artist/ArtistHeader.tsx
2058
- var import_jsx_runtime33 = require("react/jsx-runtime");
2099
+ var import_jsx_runtime34 = require("react/jsx-runtime");
2059
2100
  var countryNames = new Intl.DisplayNames(["en"], { type: "region" });
2060
2101
  function ArtistHeader({ artist }) {
2061
2102
  const fields = [];
@@ -2065,35 +2106,35 @@ function ArtistHeader({ artist }) {
2065
2106
  const years = formatYears(artist.beginYear, artist.endYear, artist.ended);
2066
2107
  if (years) fields.push({ label: "Active", value: years });
2067
2108
  const links = artistLinks(artist);
2068
- if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ArtistLinks, { links }) });
2109
+ if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ArtistLinks, { links }) });
2069
2110
  if (fields.length === 0) return null;
2070
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ui11.FieldList, { items: fields });
2111
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui11.FieldList, { items: fields });
2071
2112
  }
2072
2113
 
2073
2114
  // src/genre-tree/artist/ArtistPhoto.tsx
2074
- var import_jsx_runtime34 = require("react/jsx-runtime");
2115
+ var import_jsx_runtime35 = require("react/jsx-runtime");
2075
2116
  function ExternalLink({ href, children }) {
2076
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
2117
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
2077
2118
  }
2078
2119
  function ArtistPhoto({ image, name }) {
2079
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
2080
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
2081
- /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("figcaption", { className: "text-xs text-[var(--color-text-muted)]", children: [
2120
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
2121
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
2122
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("figcaption", { className: "text-xs text-[var(--color-text-muted)]", children: [
2082
2123
  "Photo: ",
2083
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
2124
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
2084
2125
  ",",
2085
2126
  " ",
2086
- image.licenseUrl ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
2127
+ image.licenseUrl ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
2087
2128
  ] })
2088
2129
  ] });
2089
2130
  }
2090
2131
 
2091
2132
  // src/genre-tree/artist/ArtistMembers.tsx
2092
2133
  var import_ui12 = require("@behindthemusictree/ui");
2093
- var import_jsx_runtime35 = require("react/jsx-runtime");
2134
+ var import_jsx_runtime36 = require("react/jsx-runtime");
2094
2135
  function MembershipList({ title, memberships, renderArtistLink }) {
2095
2136
  if (memberships.length === 0) return null;
2096
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui12.PanelGroup, { title, children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("ul", { children: memberships.map((membership, index) => {
2137
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui12.PanelGroup, { title, children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("ul", { children: memberships.map((membership, index) => {
2097
2138
  const details = [
2098
2139
  formatYears(membership.beginYear, membership.endYear, membership.ended),
2099
2140
  ...membership.roles
@@ -2101,9 +2142,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
2101
2142
  const detailsText = details.filter(Boolean).join(" \xB7 ");
2102
2143
  return (
2103
2144
  // A member can have several periods in the same group.
2104
- /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
2105
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
2106
- detailsText && /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
2145
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
2146
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
2147
+ detailsText && /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
2107
2148
  " \u2014 ",
2108
2149
  detailsText
2109
2150
  ] })
@@ -2114,9 +2155,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
2114
2155
  function ArtistMembers({ title, memberships, renderArtistLink }) {
2115
2156
  if (memberships.length === 0) return null;
2116
2157
  const isCurrent = (membership) => !membership.ended && membership.endYear === null;
2117
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_ui12.PanelSection, { title, children: [
2118
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
2119
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2158
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_ui12.PanelSection, { title, children: [
2159
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
2160
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2120
2161
  MembershipList,
2121
2162
  {
2122
2163
  title: "Past",
@@ -2129,7 +2170,7 @@ function ArtistMembers({ title, memberships, renderArtistLink }) {
2129
2170
 
2130
2171
  // src/genre-tree/artist/ArtistDiscography.tsx
2131
2172
  var import_ui13 = require("@behindthemusictree/ui");
2132
- var import_jsx_runtime36 = require("react/jsx-runtime");
2173
+ var import_jsx_runtime37 = require("react/jsx-runtime");
2133
2174
  var GROUPS = ["Albums", "EPs", "Singles", "Live", "Compilations", "Other"];
2134
2175
  var PRIMARY_GROUPS = { album: "Albums", ep: "EPs", single: "Singles" };
2135
2176
  function discographyGroup({ primaryType, secondaryTypes }) {
@@ -2149,18 +2190,18 @@ function byYear(a, b) {
2149
2190
  function ArtistDiscography({ albums }) {
2150
2191
  if (albums.length === 0) return null;
2151
2192
  const sorted = [...albums].sort(byYear);
2152
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.PanelSection, { title: "Discography", children: GROUPS.map((group) => {
2193
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui13.PanelSection, { title: "Discography", children: GROUPS.map((group) => {
2153
2194
  const groupAlbums = sorted.filter((album) => discographyGroup(album) === group);
2154
2195
  if (groupAlbums.length === 0) return null;
2155
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.PanelGroup, { title: group, children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
2156
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
2157
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
2196
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui13.PanelGroup, { title: group, children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
2197
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
2198
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
2158
2199
  ] }, album.uuid)) }) }, group);
2159
2200
  }) });
2160
2201
  }
2161
2202
 
2162
2203
  // src/genre-tree/artist/ArtistSongs.tsx
2163
- var import_react14 = require("react");
2204
+ var import_react15 = require("react");
2164
2205
  var import_react_query2 = require("@tanstack/react-query");
2165
2206
  var import_lucide_react13 = require("lucide-react");
2166
2207
  var import_ui14 = require("@behindthemusictree/ui");
@@ -2176,7 +2217,7 @@ var import_genre_tree20 = require("@behindthemusictree/app-kit/genre-tree");
2176
2217
  var import_fa5 = require("react-icons/fa");
2177
2218
  var import_player7 = require("@behindthemusictree/app-kit/player");
2178
2219
  var import_player8 = require("@behindthemusictree/app-kit/player");
2179
- var import_jsx_runtime37 = require("react/jsx-runtime");
2220
+ var import_jsx_runtime38 = require("react/jsx-runtime");
2180
2221
  function TrackPositionPlayPause({
2181
2222
  position,
2182
2223
  uuid,
@@ -2190,14 +2231,14 @@ function TrackPositionPlayPause({
2190
2231
  const isPlaying = isThisTrack && isConfirmedPlayable && playState === import_player8.PlayStates.PLAYING;
2191
2232
  const isPaused = isThisTrack && isConfirmedPlayable && playState === import_player8.PlayStates.PAUSED;
2192
2233
  const isCurrentTrackActive = isPlaying || isPaused;
2193
- return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
2234
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
2194
2235
  "div",
2195
2236
  {
2196
2237
  className: "track-position-play-pause relative flex items-center justify-center gap-2 text-lg w-16 cursor-pointer",
2197
2238
  style: { minWidth: "4rem", maxWidth: "4rem" },
2198
2239
  onClick: handlePlayPauseClick,
2199
2240
  children: [
2200
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
2241
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
2201
2242
  "span",
2202
2243
  {
2203
2244
  className: "track-position-play-pause__bars",
@@ -2205,18 +2246,18 @@ function TrackPositionPlayPause({
2205
2246
  role: "img",
2206
2247
  "aria-label": isPlaying ? "Playing" : "Paused",
2207
2248
  children: [
2208
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "track-position-play-pause__bar" }),
2209
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "track-position-play-pause__bar" }),
2210
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "track-position-play-pause__bar" })
2249
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "track-position-play-pause__bar" }),
2250
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "track-position-play-pause__bar" }),
2251
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "track-position-play-pause__bar" })
2211
2252
  ]
2212
2253
  }
2213
2254
  ) : position }),
2214
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
2255
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2215
2256
  "div",
2216
2257
  {
2217
2258
  className: `hidden group-hover:flex items-center justify-center ${isCurrentTrackActive ? "" : "absolute inset-0"}`,
2218
2259
  "aria-hidden": "true",
2219
- children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_fa5.FaPause, {}) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_fa5.FaPlay, {})
2260
+ children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_fa5.FaPause, {}) : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_fa5.FaPlay, {})
2220
2261
  }
2221
2262
  )
2222
2263
  ]
@@ -2227,7 +2268,7 @@ function TrackPositionPlayPause({
2227
2268
  // src/genre-tree/track-list-sidebar/TrackItem.tsx
2228
2269
  var import_genre_tree21 = require("@behindthemusictree/app-kit/genre-tree");
2229
2270
  var import_genre_tree22 = require("@behindthemusictree/app-kit/genre-tree");
2230
- var import_jsx_runtime38 = require("react/jsx-runtime");
2271
+ var import_jsx_runtime39 = require("react/jsx-runtime");
2231
2272
  function TrackItem({
2232
2273
  track,
2233
2274
  position,
@@ -2251,12 +2292,12 @@ function TrackItem({
2251
2292
  loadTrackForPlayer(track.uuid);
2252
2293
  }
2253
2294
  };
2254
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
2295
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
2255
2296
  "div",
2256
2297
  {
2257
2298
  className: `track-item flex h-14 text-[var(--color-text)] ${isUnplayable ? "opacity-50" : "hover:bg-[var(--color-surface-muted)] group"}`,
2258
2299
  children: [
2259
- isUnplayable ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2300
+ isUnplayable ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2260
2301
  "div",
2261
2302
  {
2262
2303
  className: "flex items-center justify-center text-lg w-16",
@@ -2264,7 +2305,7 @@ function TrackItem({
2264
2305
  "aria-disabled": "true",
2265
2306
  children: position
2266
2307
  }
2267
- ) : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2308
+ ) : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2268
2309
  TrackPositionPlayPause,
2269
2310
  {
2270
2311
  position,
@@ -2273,12 +2314,12 @@ function TrackItem({
2273
2314
  isSelected: selectedTrack?.uuid === track.uuid
2274
2315
  }
2275
2316
  ),
2276
- /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
2277
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime38.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_runtime38.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
2278
- track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_genre_tree22.ArtistNames, { artists: track.artists, renderArtistLink }) }) : ""
2317
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
2318
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime39.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_runtime39.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
2319
+ track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_genre_tree22.ArtistNames, { artists: track.artists, renderArtistLink }) }) : ""
2279
2320
  ] }),
2280
- /* @__PURE__ */ (0, import_jsx_runtime38.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_runtime38.jsx)("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
2281
- renderDuration && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2321
+ /* @__PURE__ */ (0, import_jsx_runtime39.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_runtime39.jsx)("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
2322
+ renderDuration && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2282
2323
  "div",
2283
2324
  {
2284
2325
  className: "duration flex text-base w-16 items-center justify-center",
@@ -2286,22 +2327,22 @@ function TrackItem({
2286
2327
  children: renderDuration(track)
2287
2328
  }
2288
2329
  ),
2289
- renderActions && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
2330
+ renderActions && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
2290
2331
  ]
2291
2332
  }
2292
2333
  );
2293
2334
  }
2294
2335
 
2295
2336
  // src/genre-tree/artist/ArtistSongs.tsx
2296
- var import_jsx_runtime39 = require("react/jsx-runtime");
2337
+ var import_jsx_runtime40 = require("react/jsx-runtime");
2297
2338
  var SongPageSchema2 = (0, import_transport9.PaginatedResponseSchema)(import_genre_tree23.SongDetailedSchema);
2298
2339
  var SORTS = [
2299
2340
  { ordering: "title", label: "Title" },
2300
2341
  { ordering: "-albumYear", label: "Year" }
2301
2342
  ];
2302
2343
  function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLink }) {
2303
- const [page, setPage] = (0, import_react14.useState)(1);
2304
- const [ordering, setOrdering] = (0, import_react14.useState)("title");
2344
+ const [page, setPage] = (0, import_react15.useState)(1);
2345
+ const [ordering, setOrdering] = (0, import_react15.useState)("title");
2305
2346
  const { fetch } = (0, import_transport9.useFetchWrapper)(getBackendBaseUrl);
2306
2347
  const { playNewTrackListFromArtist } = (0, import_genre_tree23.useTrackList)();
2307
2348
  const { playerTrackObject } = (0, import_player10.usePlayer)();
@@ -2326,21 +2367,21 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
2326
2367
  event.stopPropagation();
2327
2368
  void play(song.uuid);
2328
2369
  };
2329
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_ui14.PanelSection, { title: "Songs", children: isPending ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_ui14.Skeleton, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_jsx_runtime39.Fragment, { children: [
2330
- /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
2331
- /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
2370
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui14.PanelSection, { title: "Songs", children: isPending ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui14.Skeleton, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
2371
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
2372
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
2332
2373
  "button",
2333
2374
  {
2334
2375
  type: "button",
2335
2376
  onClick: () => void play(),
2336
2377
  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",
2337
2378
  children: [
2338
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_lucide_react13.Play, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
2379
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react13.Play, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
2339
2380
  "Play all"
2340
2381
  ]
2341
2382
  }
2342
2383
  ),
2343
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2384
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
2344
2385
  "button",
2345
2386
  {
2346
2387
  type: "button",
@@ -2355,7 +2396,7 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
2355
2396
  sort.ordering
2356
2397
  )) })
2357
2398
  ] }),
2358
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2399
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
2359
2400
  TrackItem,
2360
2401
  {
2361
2402
  track: song,
@@ -2364,12 +2405,12 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
2364
2405
  renderSongLink
2365
2406
  }
2366
2407
  ) }, song.uuid)) }),
2367
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_ui14.Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
2408
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui14.Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
2368
2409
  ] }) });
2369
2410
  }
2370
2411
 
2371
2412
  // src/genre-tree/artist/useArtistPanel.tsx
2372
- var import_jsx_runtime40 = require("react/jsx-runtime");
2413
+ var import_jsx_runtime41 = require("react/jsx-runtime");
2373
2414
  function useArtistPanel({
2374
2415
  uuid,
2375
2416
  scope,
@@ -2390,26 +2431,26 @@ function useArtistPanel({
2390
2431
  return {
2391
2432
  ...panel,
2392
2433
  title: "Artist",
2393
- content: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2394
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui15.Skeleton, { className: "h-3 w-16" }),
2395
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui15.Skeleton, { className: "h-4 w-32" }),
2396
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui15.Skeleton, { className: "h-3 w-16" }),
2397
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui15.Skeleton, { className: "h-4 w-24" }),
2398
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui15.Skeleton, { className: "h-16 mt-2.5" }),
2399
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui15.Skeleton, { className: "h-16" })
2434
+ content: /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2435
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui15.Skeleton, { className: "h-3 w-16" }),
2436
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui15.Skeleton, { className: "h-4 w-32" }),
2437
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui15.Skeleton, { className: "h-3 w-16" }),
2438
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui15.Skeleton, { className: "h-4 w-24" }),
2439
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui15.Skeleton, { className: "h-16 mt-2.5" }),
2440
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui15.Skeleton, { className: "h-16" })
2400
2441
  ] })
2401
2442
  };
2402
2443
  case "not-found":
2403
2444
  return {
2404
2445
  ...panel,
2405
2446
  title: "Artist not found",
2406
- content: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This artist does not exist." })
2447
+ content: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This artist does not exist." })
2407
2448
  };
2408
2449
  case "error":
2409
2450
  return {
2410
2451
  ...panel,
2411
2452
  title: "Artist",
2412
- content: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
2453
+ content: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
2413
2454
  };
2414
2455
  case "success": {
2415
2456
  const { artist } = result;
@@ -2418,13 +2459,13 @@ function useArtistPanel({
2418
2459
  return {
2419
2460
  ...panel,
2420
2461
  title: artist.name,
2421
- content: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
2422
- shareUrl && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: shareTarget && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl }) }) }),
2423
- artist.image && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(ArtistPhoto, { image: artist.image, name: artist.name }),
2424
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(ArtistHeader, { artist }),
2425
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
2426
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
2427
- artist.tracksCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
2462
+ content: /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(import_jsx_runtime41.Fragment, { children: [
2463
+ shareUrl && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: shareTarget && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl }) }) }),
2464
+ artist.image && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(ArtistPhoto, { image: artist.image, name: artist.name }),
2465
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(ArtistHeader, { artist }),
2466
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
2467
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
2468
+ artist.tracksCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
2428
2469
  ArtistSongs,
2429
2470
  {
2430
2471
  artist: { uuid: artist.uuid, name: artist.name },
@@ -2434,7 +2475,7 @@ function useArtistPanel({
2434
2475
  }
2435
2476
  )
2436
2477
  ] }),
2437
- extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(ArtistDiscography, { albums: artist.albums }) : void 0
2478
+ extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(ArtistDiscography, { albums: artist.albums }) : void 0
2438
2479
  };
2439
2480
  }
2440
2481
  }
@@ -2445,45 +2486,45 @@ var import_ui17 = require("@behindthemusictree/ui");
2445
2486
  var import_genre_tree26 = require("@behindthemusictree/app-kit/genre-tree");
2446
2487
 
2447
2488
  // src/genre-tree/song/SongPanelContent.tsx
2448
- var import_react15 = require("react");
2489
+ var import_react16 = require("react");
2449
2490
  var import_lucide_react14 = require("lucide-react");
2450
2491
  var import_ui16 = require("@behindthemusictree/ui");
2451
2492
  var import_player11 = require("@behindthemusictree/app-kit/player");
2452
2493
  var import_genre_tree25 = require("@behindthemusictree/app-kit/genre-tree");
2453
- var import_jsx_runtime41 = require("react/jsx-runtime");
2494
+ var import_jsx_runtime42 = require("react/jsx-runtime");
2454
2495
  var numberFormat = new Intl.NumberFormat("en");
2455
2496
  var dateFormat = new Intl.DateTimeFormat("en", { dateStyle: "medium" });
2456
2497
  var externalLink = "text-[var(--color-primary)] hover:underline focus-visible:underline";
2457
2498
  function SongHistory({ uuid, getBackendBaseUrl }) {
2458
- const [open, setOpen] = (0, import_react15.useState)(false);
2499
+ const [open, setOpen] = (0, import_react16.useState)(false);
2459
2500
  const history = (0, import_genre_tree25.useSongHistory)(uuid, getBackendBaseUrl, open);
2460
2501
  let body = null;
2461
- if (history.status === "loading") body = /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui16.Skeleton, { className: "h-10", "aria-busy": "true" });
2462
- else if (history.status === "error") body = /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "text-red-600", children: "Could not load the history." });
2502
+ if (history.status === "loading") body = /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui16.Skeleton, { className: "h-10", "aria-busy": "true" });
2503
+ else if (history.status === "error") body = /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "text-red-600", children: "Could not load the history." });
2463
2504
  else if (history.status === "success")
2464
- body = history.entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("li", { className: "py-1.5 text-sm", children: [
2465
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "font-medium", children: entry.action }),
2466
- entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
2505
+ body = history.entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("li", { className: "py-1.5 text-sm", children: [
2506
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "font-medium", children: entry.action }),
2507
+ entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
2467
2508
  " ",
2468
2509
  "\u2014 ",
2469
2510
  entry.oldValue ?? "none",
2470
2511
  " \u2192 ",
2471
2512
  entry.newValue ?? "none"
2472
2513
  ] }) : null,
2473
- /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { className: "block text-xs text-[var(--color-text-muted)]", children: [
2514
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { className: "block text-xs text-[var(--color-text-muted)]", children: [
2474
2515
  entry.actorPseudo ?? "Anonymous",
2475
2516
  " \xB7 ",
2476
2517
  dateFormat.format(new Date(entry.createdOn))
2477
2518
  ] })
2478
2519
  ] }, entry.uuid)) });
2479
- return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
2520
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
2480
2521
  "details",
2481
2522
  {
2482
2523
  onToggle: (event) => setOpen(event.currentTarget.open),
2483
2524
  className: "px-6 pb-2.5 mt-2.5 border-t border-[var(--color-border)]",
2484
2525
  children: [
2485
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("summary", { className: "mt-2.5 cursor-pointer text-[0.6875rem] font-semibold uppercase text-[var(--color-text-muted)]", children: "History" }),
2486
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "mt-1.5", children: body })
2526
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("summary", { className: "mt-2.5 cursor-pointer text-[0.6875rem] font-semibold uppercase text-[var(--color-text-muted)]", children: "History" }),
2527
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "mt-1.5", children: body })
2487
2528
  ]
2488
2529
  }
2489
2530
  );
@@ -2509,13 +2550,13 @@ function SongPanelContent({
2509
2550
  const path = genrePath.length > 0 ? genrePath : null;
2510
2551
  fields.push({
2511
2552
  label: "Genre",
2512
- value: path ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { children: [
2513
- index > 0 && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { "aria-hidden": "true", children: "\u203A " }),
2514
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("button", { type: "button", onClick: () => onSelectGenre(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
2553
+ value: path ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { children: [
2554
+ index > 0 && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { "aria-hidden": "true", children: "\u203A " }),
2555
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("button", { type: "button", onClick: () => onSelectGenre(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
2515
2556
  ] }, genrePlaylist.uuid)) }) : song.genre.name
2516
2557
  });
2517
2558
  } else {
2518
- fields.push({ label: "Genre", value: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
2559
+ fields.push({ label: "Genre", value: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
2519
2560
  }
2520
2561
  if (song.firstReleaseYear) fields.push({ label: "First released", value: song.firstReleaseYear });
2521
2562
  const viewCount = song.youtubeVideo?.viewCount;
@@ -2525,11 +2566,11 @@ function SongPanelContent({
2525
2566
  const links = [];
2526
2567
  if (musicBrainzUrl)
2527
2568
  links.push(
2528
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
2569
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
2529
2570
  );
2530
2571
  if (song.youtubeVideo)
2531
2572
  links.push(
2532
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
2573
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
2533
2574
  "a",
2534
2575
  {
2535
2576
  href: `https://www.youtube.com/watch?v=${encodeURIComponent(song.youtubeVideo.id)}`,
@@ -2544,33 +2585,33 @@ function SongPanelContent({
2544
2585
  if (links.length > 0)
2545
2586
  fields.push({
2546
2587
  label: "Links",
2547
- value: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "flex gap-3", children: links })
2588
+ value: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "flex gap-3", children: links })
2548
2589
  });
2549
- return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(import_jsx_runtime41.Fragment, { children: [
2550
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
2551
- unplayableReason ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "flex items-center px-2.5 text-sm text-[var(--color-text-muted)]", children: unplayableReason }) : /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
2590
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_jsx_runtime42.Fragment, { children: [
2591
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
2592
+ unplayableReason ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "flex items-center px-2.5 text-sm text-[var(--color-text-muted)]", children: unplayableReason }) : /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
2552
2593
  "button",
2553
2594
  {
2554
2595
  type: "button",
2555
2596
  onClick: () => isCurrent ? handlePlayPauseAction() : playNewTrackListFromTrackUuid(song, scope),
2556
2597
  className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none",
2557
2598
  children: [
2558
- /* @__PURE__ */ (0, import_jsx_runtime41.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_runtime41.jsx)(import_lucide_react14.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_lucide_react14.Play, { "aria-hidden": "true", className: "w-5 h-5" }) }),
2599
+ /* @__PURE__ */ (0, import_jsx_runtime42.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_runtime42.jsx)(import_lucide_react14.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react14.Play, { "aria-hidden": "true", className: "w-5 h-5" }) }),
2559
2600
  isPlaying ? "Pause" : "Play"
2560
2601
  ]
2561
2602
  }
2562
2603
  ),
2563
- shareUrl && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
2604
+ shareUrl && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
2564
2605
  ] }) }),
2565
- song.artists && song.artists.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "px-6 pt-2.5 text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_genre_tree25.ArtistNames, { artists: song.artists, renderArtistLink }) }),
2566
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui16.FieldList, { items: fields }),
2567
- song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui16.PanelSection, { title: "Essential in", children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
2568
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
2606
+ song.artists && song.artists.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "px-6 pt-2.5 text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_genre_tree25.ArtistNames, { artists: song.artists, renderArtistLink }) }),
2607
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui16.FieldList, { items: fields }),
2608
+ song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui16.PanelSection, { title: "Essential in", children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
2609
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
2569
2610
  ] });
2570
2611
  }
2571
2612
 
2572
2613
  // src/genre-tree/song/useSongPanel.tsx
2573
- var import_jsx_runtime42 = require("react/jsx-runtime");
2614
+ var import_jsx_runtime43 = require("react/jsx-runtime");
2574
2615
  function useSongPanel({
2575
2616
  uuid,
2576
2617
  scope,
@@ -2592,24 +2633,24 @@ function useSongPanel({
2592
2633
  return {
2593
2634
  ...panel,
2594
2635
  title: "Song",
2595
- content: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2596
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui17.Skeleton, { className: "h-3 w-16" }),
2597
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui17.Skeleton, { className: "h-4 w-32" }),
2598
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui17.Skeleton, { className: "h-3 w-16" }),
2599
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui17.Skeleton, { className: "h-4 w-24" })
2636
+ content: /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2637
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_ui17.Skeleton, { className: "h-3 w-16" }),
2638
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_ui17.Skeleton, { className: "h-4 w-32" }),
2639
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_ui17.Skeleton, { className: "h-3 w-16" }),
2640
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_ui17.Skeleton, { className: "h-4 w-24" })
2600
2641
  ] })
2601
2642
  };
2602
2643
  case "not-found":
2603
2644
  return {
2604
2645
  ...panel,
2605
2646
  title: "Song not found",
2606
- content: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This song does not exist." })
2647
+ content: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This song does not exist." })
2607
2648
  };
2608
2649
  case "error":
2609
2650
  return {
2610
2651
  ...panel,
2611
2652
  title: "Song",
2612
- content: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this song." })
2653
+ content: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this song." })
2613
2654
  };
2614
2655
  case "success": {
2615
2656
  const { song } = result;
@@ -2617,7 +2658,7 @@ function useSongPanel({
2617
2658
  return {
2618
2659
  ...panel,
2619
2660
  title: song.title,
2620
- content: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
2661
+ content: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
2621
2662
  SongPanelContent,
2622
2663
  {
2623
2664
  song,
@@ -2637,12 +2678,12 @@ function useSongPanel({
2637
2678
  // src/genre-tree/SizeMetricToggle.tsx
2638
2679
  var import_md2 = require("react-icons/md");
2639
2680
  var import_ui18 = require("@behindthemusictree/ui");
2640
- var import_jsx_runtime43 = require("react/jsx-runtime");
2681
+ var import_jsx_runtime44 = require("react/jsx-runtime");
2641
2682
  function SizeMetricToggle({
2642
2683
  value,
2643
2684
  onChange
2644
2685
  }) {
2645
- return /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
2686
+ return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
2646
2687
  import_ui18.SegmentedControl,
2647
2688
  {
2648
2689
  variant: "floating",
@@ -2650,15 +2691,15 @@ function SizeMetricToggle({
2650
2691
  value,
2651
2692
  onChange,
2652
2693
  options: [
2653
- { value: "songs", label: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_md2.MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
2654
- { value: "views", label: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_md2.MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
2694
+ { value: "songs", label: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_md2.MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
2695
+ { value: "views", label: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_md2.MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
2655
2696
  ]
2656
2697
  }
2657
2698
  );
2658
2699
  }
2659
2700
 
2660
2701
  // src/genre-tree/GenreTreeView.tsx
2661
- var import_jsx_runtime44 = require("react/jsx-runtime");
2702
+ var import_jsx_runtime45 = require("react/jsx-runtime");
2662
2703
  var NO_POP_ROOT_TITLE = "This genre tree has no 'Mainstream Pop' root yet";
2663
2704
  var REGIONS_CLASS = "[--gtv-center-left:var(--layout-center-left)] [--gtv-center-right:var(--layout-center-right)] [--gtv-center-top:var(--layout-band-top)]";
2664
2705
  var noop = () => {
@@ -2693,6 +2734,7 @@ function GenreTreeView({
2693
2734
  onArtistExpandedChange,
2694
2735
  getShareUrl,
2695
2736
  extraSearchGroups = [],
2737
+ SearchGroups,
2696
2738
  onSearchQueryChange,
2697
2739
  onSearchFocus,
2698
2740
  artistSearch = true,
@@ -2719,21 +2761,21 @@ function GenreTreeView({
2719
2761
  placement = "default",
2720
2762
  ...focus
2721
2763
  }) {
2722
- const getShareUrlRef = (0, import_react16.useRef)(getShareUrl);
2764
+ const getShareUrlRef = (0, import_react17.useRef)(getShareUrl);
2723
2765
  getShareUrlRef.current = getShareUrl;
2724
- const [internalArtistUuid, setInternalArtistUuid] = (0, import_react16.useState)(null);
2766
+ const [internalArtistUuid, setInternalArtistUuid] = (0, import_react17.useState)(null);
2725
2767
  const isArtistControlled = controlledArtistUuid !== void 0;
2726
2768
  const artistUuid = isArtistControlled ? controlledArtistUuid : internalArtistUuid;
2727
- const [internalSongUuid, setInternalSongUuid] = (0, import_react16.useState)(null);
2769
+ const [internalSongUuid, setInternalSongUuid] = (0, import_react17.useState)(null);
2728
2770
  const isSongControlled = controlledSongUuid !== void 0;
2729
2771
  const songUuid = isSongControlled ? controlledSongUuid : internalSongUuid;
2730
2772
  const changeSong = (uuid) => {
2731
2773
  if (!isSongControlled) setInternalSongUuid(uuid);
2732
2774
  onSongUuidChange?.(uuid);
2733
2775
  };
2734
- const [internalSizeMetric, setInternalSizeMetric] = (0, import_react16.useState)("songs");
2776
+ const [internalSizeMetric, setInternalSizeMetric] = (0, import_react17.useState)("songs");
2735
2777
  const sizeMetric = controlledSizeMetric ?? internalSizeMetric;
2736
- const deferredSizeMetric = (0, import_react16.useDeferredValue)(sizeMetric);
2778
+ const deferredSizeMetric = (0, import_react17.useDeferredValue)(sizeMetric);
2737
2779
  const changeSizeMetric = (metric) => {
2738
2780
  if (controlledSizeMetric === void 0) setInternalSizeMetric(metric);
2739
2781
  onSizeMetricChange?.(metric);
@@ -2770,7 +2812,7 @@ function GenreTreeView({
2770
2812
  artistUuid
2771
2813
  });
2772
2814
  const restoreRevision = viewportRestore?.revision;
2773
- const [stackedRestoreOwner, setStackedRestoreOwner] = (0, import_react16.useState)({ revision: restoreRevision, rootUuid: selectedGenrePlaylist?.root.uuid });
2815
+ const [stackedRestoreOwner, setStackedRestoreOwner] = (0, import_react17.useState)({ revision: restoreRevision, rootUuid: selectedGenrePlaylist?.root.uuid });
2774
2816
  let restoreRootUuid = stackedRestoreOwner.rootUuid;
2775
2817
  if (stackedRestoreOwner.revision !== restoreRevision) {
2776
2818
  restoreRootUuid = selectedGenrePlaylist?.root.uuid;
@@ -2786,7 +2828,7 @@ function GenreTreeView({
2786
2828
  const canSizeByViews = !isArtistFiltered && hasViewsCount(visibleGenrePlaylists);
2787
2829
  const artistResult = (0, import_genre_tree27.useArtist)(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
2788
2830
  const filterArtistName = artistResult.status === "success" ? artistResult.artist.name : null;
2789
- const filterArtist = (0, import_react16.useMemo)(
2831
+ const filterArtist = (0, import_react17.useMemo)(
2790
2832
  () => artistUuid !== null && filterArtistName !== null ? { uuid: artistUuid, name: filterArtistName } : null,
2791
2833
  [artistUuid, filterArtistName]
2792
2834
  );
@@ -2801,14 +2843,14 @@ function GenreTreeView({
2801
2843
  if (selectedNodeId !== null) clearSelection();
2802
2844
  changeSong(uuid);
2803
2845
  };
2804
- const openSongRef = (0, import_react16.useRef)(openSong);
2846
+ const openSongRef = (0, import_react17.useRef)(openSong);
2805
2847
  openSongRef.current = openSong;
2806
- const resolvedRenderSongLink = (0, import_react16.useCallback)(
2807
- (song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
2848
+ const resolvedRenderSongLink = (0, import_react17.useCallback)(
2849
+ (song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
2808
2850
  [renderSongLink]
2809
2851
  );
2810
- const [expandedSongUuid, setExpandedSongUuid] = (0, import_react16.useState)(null);
2811
- const [expandedArtistUuid, setExpandedArtistUuid] = (0, import_react16.useState)(null);
2852
+ const [expandedSongUuid, setExpandedSongUuid] = (0, import_react17.useState)(null);
2853
+ const [expandedArtistUuid, setExpandedArtistUuid] = (0, import_react17.useState)(null);
2812
2854
  const artistBaseUrl = artistSearchBaseUrl ?? getBackendBaseUrl;
2813
2855
  const songBaseUrl = getBackendBaseUrl;
2814
2856
  const artistPanel = useArtistPanel({
@@ -2816,7 +2858,7 @@ function GenreTreeView({
2816
2858
  scope,
2817
2859
  getBackendBaseUrl: artistBaseUrl,
2818
2860
  getShareUrl: getShareUrlRef.current,
2819
- renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
2861
+ renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
2820
2862
  expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
2821
2863
  onExpandedChange: (on) => {
2822
2864
  if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
@@ -2825,7 +2867,7 @@ function GenreTreeView({
2825
2867
  renderSongLink: resolvedRenderSongLink,
2826
2868
  onClose: () => changeArtist(null)
2827
2869
  });
2828
- const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
2870
+ const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
2829
2871
  const getGenrePath = (genre) => {
2830
2872
  const path = [];
2831
2873
  let current = visibleGenrePlaylists.find((playlist) => playlist.criteria?.uuid === genre.uuid);
@@ -2857,8 +2899,8 @@ function GenreTreeView({
2857
2899
  const builtInSongPanel = overlayPanel || hasSelectedGenreNode ? void 0 : songPanel;
2858
2900
  const builtInArtistPanel = overlayPanel || hasSelectedGenreNode || builtInSongPanel ? void 0 : artistPanel;
2859
2901
  const builtInPanel = builtInSongPanel ?? builtInArtistPanel;
2860
- const [uncontrolledCollapsed, setUncontrolledCollapsed] = (0, import_react16.useState)(false);
2861
- const [uncontrolledExpanded, setUncontrolledExpanded] = (0, import_react16.useState)(false);
2902
+ const [uncontrolledCollapsed, setUncontrolledCollapsed] = (0, import_react17.useState)(false);
2903
+ const [uncontrolledExpanded, setUncontrolledExpanded] = (0, import_react17.useState)(false);
2862
2904
  const isCollapsed = collapsed ?? uncontrolledCollapsed;
2863
2905
  const isExpanded = expanded ?? uncontrolledExpanded;
2864
2906
  const changeCollapsed = (next) => {
@@ -2869,28 +2911,28 @@ function GenreTreeView({
2869
2911
  setUncontrolledExpanded(next);
2870
2912
  onExpandedChange?.(next);
2871
2913
  };
2872
- (0, import_react16.useEffect)(() => {
2914
+ (0, import_react17.useEffect)(() => {
2873
2915
  setUncontrolledCollapsed(false);
2874
2916
  }, [selectedNodeId, artistUuid, songUuid]);
2875
- const [isWheelRevealed, setIsWheelRevealed] = (0, import_react16.useState)(false);
2917
+ const [isWheelRevealed, setIsWheelRevealed] = (0, import_react17.useState)(false);
2876
2918
  const usesHandoff = viewMode === "wheel" || viewMode === "pop-core";
2877
2919
  const isTreeReady = !isLoading && (!usesHandoff || isWheelRevealed);
2878
- const [resolveStyle, setResolveStyle] = (0, import_react16.useState)(null);
2879
- const handleNodeStyleResolverChange = (0, import_react16.useCallback)(
2920
+ const [resolveStyle, setResolveStyle] = (0, import_react17.useState)(null);
2921
+ const handleNodeStyleResolverChange = (0, import_react17.useCallback)(
2880
2922
  (resolver) => setResolveStyle(() => resolver),
2881
2923
  []
2882
2924
  );
2883
- const panelRef = (0, import_react16.useRef)(null);
2925
+ const panelRef = (0, import_react17.useRef)(null);
2884
2926
  const obscuredInset = (0, import_ui19.useObscuredInset)(panelRef);
2885
2927
  const hostPanel = { obscuredInset, onNodeStyleResolverChange: handleNodeStyleResolverChange };
2886
2928
  const nodeMetric = isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric;
2887
- const treeNodes = (0, import_react16.useMemo)(() => toTreeNodes(visibleGenrePlaylists, nodeMetric), [visibleGenrePlaylists, nodeMetric]);
2929
+ const treeNodes = (0, import_react17.useMemo)(() => toTreeNodes(visibleGenrePlaylists, nodeMetric), [visibleGenrePlaylists, nodeMetric]);
2888
2930
  const relations = (0, import_genre_tree_view4.useGenreNodeRelations)(
2889
2931
  treeNodes,
2890
2932
  !overlayPanel && hasSelectedGenreNode ? selectedNodeId : null,
2891
2933
  resolveStyle
2892
2934
  );
2893
- const [selectionHistory, setSelectionHistory] = (0, import_react16.useState)({
2935
+ const [selectionHistory, setSelectionHistory] = (0, import_react17.useState)({
2894
2936
  current: selectedNodeId,
2895
2937
  previous: null
2896
2938
  });
@@ -2903,28 +2945,6 @@ function GenreTreeView({
2903
2945
  const previousPlaylist = selectionHistory.previous !== null && selectionHistory.previous !== selectedNodeId ? genrePlaylistByNodeId.get(selectionHistory.previous) : void 0;
2904
2946
  const handlePrevious = previousPlaylist && visibleGenrePlaylists.includes(previousPlaylist) ? () => handleGenreSearchSelect(previousPlaylist) : void 0;
2905
2947
  const { showPopup, hidePopup } = (0, import_popup6.usePopup)();
2906
- const [searchText, setSearchText] = (0, import_react16.useState)("");
2907
- const artistGroup = useArtistSearchGroup(
2908
- artistBaseUrl,
2909
- searchText,
2910
- (artist) => {
2911
- openArtist(artist.uuid);
2912
- onArtistSelect?.(artist);
2913
- },
2914
- Boolean(artistSearch)
2915
- );
2916
- const songGroup = useSongSearchGroup(
2917
- songBaseUrl,
2918
- searchText,
2919
- (song) => {
2920
- openSong(song.uuid);
2921
- onSongSelect?.(song);
2922
- },
2923
- Boolean(songSearch)
2924
- );
2925
- const extraGroups = [...extraSearchGroups, artistGroup, songGroup].filter(
2926
- (group) => group !== void 0
2927
- );
2928
2948
  const handleSelectedNodeChange = (node) => {
2929
2949
  if (node !== null && songUuid !== null) changeSong(null);
2930
2950
  selectNode(node);
@@ -2940,7 +2960,7 @@ function GenreTreeView({
2940
2960
  const handleGenreSearchZoomIn = () => {
2941
2961
  if (selectedUuid) focus.onFocusGenreChange?.(selectedUuid, "zoom-in");
2942
2962
  };
2943
- const zoomInRef = (0, import_react16.useRef)(void 0);
2963
+ const zoomInRef = (0, import_react17.useRef)(void 0);
2944
2964
  zoomInRef.current = canZoomIn ? handleGenreSearchZoomIn : void 0;
2945
2965
  const selectGenreFromPanel = (nodeId) => {
2946
2966
  const genrePlaylist = genrePlaylistByNodeId.get(nodeId);
@@ -2948,7 +2968,7 @@ function GenreTreeView({
2948
2968
  };
2949
2969
  const renderGenreBody = (relation) => {
2950
2970
  const { node } = relation;
2951
- const family = /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
2971
+ const family = /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
2952
2972
  import_genre_tree_view4.GenreNodeFamily,
2953
2973
  {
2954
2974
  node,
@@ -2959,10 +2979,10 @@ function GenreTreeView({
2959
2979
  onSelectNode: selectGenreFromPanel
2960
2980
  }
2961
2981
  );
2962
- const ancestors = /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_genre_tree_view4.GenreNodeAncestors, { ancestorNodes: relation.ancestorNodes, onSelectNode: selectGenreFromPanel });
2982
+ const ancestors = /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_genre_tree_view4.GenreNodeAncestors, { ancestorNodes: relation.ancestorNodes, onSelectNode: selectGenreFromPanel });
2963
2983
  const genrePlaylist = genrePlaylistByNodeId.get(node.id);
2964
2984
  const criteria = genrePlaylist?.criteria;
2965
- if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(import_jsx_runtime44.Fragment, { children: [
2985
+ if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(import_jsx_runtime45.Fragment, { children: [
2966
2986
  family,
2967
2987
  ancestors
2968
2988
  ] }) };
@@ -2982,11 +3002,11 @@ function GenreTreeView({
2982
3002
  (sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist.uuid && sibling.criteria?.side === "pop"
2983
3003
  ) : void 0;
2984
3004
  return {
2985
- primary: /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(import_jsx_runtime44.Fragment, { children: [
2986
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3005
+ primary: /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(import_jsx_runtime45.Fragment, { children: [
3006
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
2987
3007
  GenreInfoPanelBody,
2988
3008
  {
2989
- family: /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(import_jsx_runtime44.Fragment, { children: [
3009
+ family: /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(import_jsx_runtime45.Fragment, { children: [
2990
3010
  family,
2991
3011
  ancestors
2992
3012
  ] }),
@@ -3005,7 +3025,7 @@ function GenreTreeView({
3005
3025
  onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
3006
3026
  onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
3007
3027
  onChangeParent: readOnly ? void 0 : () => showPopup(
3008
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3028
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3009
3029
  ChangeParentPopup,
3010
3030
  {
3011
3031
  genrePlaylist: { ...genrePlaylist, criteria },
@@ -3080,19 +3100,19 @@ function GenreTreeView({
3080
3100
  edge: activePanel.expanded ? { label: "Shrink panel", onClick: () => activePanel.onExpandedChange?.(false) } : isCollapsed ? null : { label: "Hide panel", onClick: () => changeCollapsed(true) },
3081
3101
  center: activePanel.expanded ? null : isCollapsed ? { label: "Show panel", onClick: () => changeCollapsed(false) } : activePanel.onExpandedChange ? { label: "Extend panel", onClick: () => activePanel.onExpandedChange?.(true) } : null
3082
3102
  } : void 0;
3083
- const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3103
+ const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3084
3104
  import_ui19.IconButton,
3085
3105
  {
3086
3106
  "aria-label": activePanel.expanded ? "Shrink panel" : "Extend panel",
3087
3107
  style: activePanel.headerAccent && { color: "inherit" },
3088
3108
  onClick: () => activePanel.onExpandedChange?.(!activePanel.expanded),
3089
- children: activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_lucide_react16.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_lucide_react16.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
3109
+ children: activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_lucide_react16.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_lucide_react16.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
3090
3110
  }
3091
3111
  ) : null;
3092
3112
  const renderArtistFilter = () => {
3093
3113
  if (isLoading || !filterArtist) return null;
3094
- return /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
3095
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3114
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
3115
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3096
3116
  "button",
3097
3117
  {
3098
3118
  type: "button",
@@ -3104,7 +3124,7 @@ function GenreTreeView({
3104
3124
  children: filterArtist.name
3105
3125
  }
3106
3126
  ),
3107
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3127
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3108
3128
  "button",
3109
3129
  {
3110
3130
  type: "button",
@@ -3116,26 +3136,36 @@ function GenreTreeView({
3116
3136
  )
3117
3137
  ] });
3118
3138
  };
3119
- const actions = /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(import_jsx_runtime44.Fragment, { children: [
3120
- !isLoading && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3121
- GenreSearch,
3139
+ const actions = /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(import_jsx_runtime45.Fragment, { children: [
3140
+ !isLoading && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3141
+ GenreTreeSearch,
3122
3142
  {
3143
+ artistBaseUrl,
3144
+ songBaseUrl,
3145
+ artistSearch: Boolean(artistSearch),
3146
+ songSearch: Boolean(songSearch),
3147
+ onArtistSelect: (artist) => {
3148
+ openArtist(artist.uuid);
3149
+ onArtistSelect?.(artist);
3150
+ },
3151
+ onSongSelect: (song) => {
3152
+ openSong(song.uuid);
3153
+ onSongSelect?.(song);
3154
+ },
3155
+ SearchGroups,
3123
3156
  genrePlaylists: visibleGenrePlaylists,
3124
3157
  onSelect: handleGenreSearchSelect,
3125
3158
  selectedName: selectedGenrePlaylist?.name ?? (typeof builtInPanel?.title === "string" ? builtInPanel.title : null),
3126
3159
  onClear: builtInSongPanel ? () => changeSong(null) : builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
3127
3160
  onPrevious: handlePrevious,
3128
- onQueryChange: (text) => {
3129
- setSearchText(text);
3130
- onSearchQueryChange?.(text);
3131
- },
3161
+ onQueryChange: onSearchQueryChange,
3132
3162
  onFocus: onSearchFocus,
3133
- extraGroups
3163
+ extraGroups: extraSearchGroups
3134
3164
  }
3135
3165
  ),
3136
- !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
3137
- /* @__PURE__ */ (0, import_jsx_runtime44.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: [
3138
- !isLoading && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3166
+ !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
3167
+ /* @__PURE__ */ (0, import_jsx_runtime45.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: [
3168
+ !isLoading && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3139
3169
  import_ui19.SegmentedControl,
3140
3170
  {
3141
3171
  "aria-label": "Tree view mode",
@@ -3149,13 +3179,13 @@ function GenreTreeView({
3149
3179
  ]
3150
3180
  }
3151
3181
  ),
3152
- !isLoading && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3182
+ !isLoading && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3153
3183
  "div",
3154
3184
  {
3155
3185
  className: "flex items-center gap-1",
3156
3186
  role: "group",
3157
3187
  "aria-label": "Tree display options",
3158
- children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3188
+ children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3159
3189
  import_ui19.Button,
3160
3190
  {
3161
3191
  variant: allowWheelRotation ? "default" : "outline",
@@ -3166,7 +3196,7 @@ function GenreTreeView({
3166
3196
  )
3167
3197
  }
3168
3198
  ),
3169
- !isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3199
+ !isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3170
3200
  import_ui19.IconTextButton,
3171
3201
  {
3172
3202
  icon: import_lucide_react15.Plus,
@@ -3176,34 +3206,34 @@ function GenreTreeView({
3176
3206
  )
3177
3207
  ] })
3178
3208
  ] });
3179
- return /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(
3209
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(
3180
3210
  "div",
3181
3211
  {
3182
3212
  "data-left-panel": isPanelShown && !isCollapsed ? void 0 : "hidden",
3183
3213
  className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
3184
3214
  children: [
3185
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3215
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3186
3216
  "div",
3187
3217
  {
3188
3218
  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"}`,
3189
3219
  children: actions
3190
3220
  }
3191
3221
  ),
3192
- !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3222
+ !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3193
3223
  "div",
3194
3224
  {
3195
3225
  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"}`,
3196
3226
  children: renderArtistFilter()
3197
3227
  }
3198
3228
  ),
3199
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
3200
- !isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
3201
- isLoading ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_genre_tree_view4.GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
3229
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
3230
+ !isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
3231
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_genre_tree_view4.GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
3202
3232
  // The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
3203
3233
  // and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
3204
3234
  // Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
3205
3235
  // The panel's own Zoom in button is hidden: the search bar carries it.
3206
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3236
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3207
3237
  GenrePlaylistTreeWheel,
3208
3238
  {
3209
3239
  scope,
@@ -3220,9 +3250,9 @@ function GenreTreeView({
3220
3250
  allowWheelRotation,
3221
3251
  sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
3222
3252
  busy,
3223
- floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "flex items-center gap-2", children: [
3253
+ floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("div", { className: "flex items-center gap-2", children: [
3224
3254
  floatingControlsLeading,
3225
- canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
3255
+ canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
3226
3256
  ] }) : void 0,
3227
3257
  defaultViewport,
3228
3258
  viewportRestore,
@@ -3231,7 +3261,7 @@ function GenreTreeView({
3231
3261
  ) }) })
3232
3262
  ) : viewMode === "pop-core" ? (
3233
3263
  // Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
3234
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3264
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3235
3265
  GenrePlaylistTreeWheelRadialPopCore,
3236
3266
  {
3237
3267
  scope,
@@ -3248,9 +3278,9 @@ function GenreTreeView({
3248
3278
  allowWheelRotation,
3249
3279
  sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
3250
3280
  busy,
3251
- floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "flex items-center gap-2", children: [
3281
+ floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("div", { className: "flex items-center gap-2", children: [
3252
3282
  floatingControlsLeading,
3253
- canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
3283
+ canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
3254
3284
  ] }) : void 0,
3255
3285
  defaultViewport,
3256
3286
  viewportRestore,
@@ -3258,7 +3288,7 @@ function GenreTreeView({
3258
3288
  ...focus
3259
3289
  }
3260
3290
  ) }) })
3261
- ) : viewMode === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3291
+ ) : viewMode === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3262
3292
  GenrePlaylistTreeWheelRadialPopCore,
3263
3293
  {
3264
3294
  outline: true,
@@ -3274,13 +3304,13 @@ function GenreTreeView({
3274
3304
  selectedNodeId,
3275
3305
  readOnly
3276
3306
  }
3277
- ) }) : /* @__PURE__ */ (0, import_jsx_runtime44.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(
3307
+ ) }) : /* @__PURE__ */ (0, import_jsx_runtime45.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(
3278
3308
  ([uuid, genrePlaylistTreePerRoot]) => {
3279
- return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3309
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3280
3310
  "div",
3281
3311
  {
3282
3312
  className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
3283
- children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3313
+ children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3284
3314
  GenrePlaylistTreePerRoot,
3285
3315
  {
3286
3316
  scope,
@@ -3306,7 +3336,7 @@ function GenreTreeView({
3306
3336
  );
3307
3337
  }
3308
3338
  ) }),
3309
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3339
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3310
3340
  import_ui19.DetailPanel,
3311
3341
  {
3312
3342
  ref: panelRef,
@@ -3323,7 +3353,7 @@ function GenreTreeView({
3323
3353
  aside: activePanel?.aside,
3324
3354
  edgeTab: detailTab,
3325
3355
  headerClassName: activePanel?.compact ? "pt-[calc(var(--layout-search-h)+var(--layout-panel-inset)+0.5rem)]" : void 0,
3326
- children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(import_jsx_runtime44.Fragment, { children: [
3356
+ children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(import_jsx_runtime45.Fragment, { children: [
3327
3357
  activePanel.content,
3328
3358
  activePanel.aside
3329
3359
  ] }) : activePanel?.content
@@ -3336,12 +3366,12 @@ function GenreTreeView({
3336
3366
  }
3337
3367
 
3338
3368
  // src/genre-tree/GenreDetailPanel.tsx
3339
- var import_react17 = require("react");
3369
+ var import_react18 = require("react");
3340
3370
  var import_genre_tree_view5 = require("@behindthemusictree/genre-tree-view");
3341
3371
  var import_ui20 = require("@behindthemusictree/ui");
3342
3372
  var import_lucide_react17 = require("lucide-react");
3343
3373
  var import_genre_tree28 = require("@behindthemusictree/app-kit/genre-tree");
3344
- var import_jsx_runtime45 = require("react/jsx-runtime");
3374
+ var import_jsx_runtime46 = require("react/jsx-runtime");
3345
3375
  var RELATIVE_ITEM_COUNT = 1;
3346
3376
  function toNode(genre, parentId) {
3347
3377
  return {
@@ -3370,7 +3400,7 @@ function GenreDetailPanel({
3370
3400
  }) {
3371
3401
  const { data: detail, isPending, isError } = (0, import_genre_tree28.useFetchGenreDetail)(genreUuid, scope, getBackendBaseUrl);
3372
3402
  const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: [] });
3373
- const view = (0, import_react17.useMemo)(() => {
3403
+ const view = (0, import_react18.useMemo)(() => {
3374
3404
  if (!detail) return null;
3375
3405
  const rootColor = (0, import_genre_tree_view5.getGenreTreeColor)(detail.root.wikidataId);
3376
3406
  const relative = (genre, parentId) => ({
@@ -3396,17 +3426,17 @@ function GenreDetailPanel({
3396
3426
  };
3397
3427
  }, [detail, scope]);
3398
3428
  if (isError) {
3399
- return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
3429
+ return /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
3400
3430
  }
3401
3431
  if (isPending || !detail || !view) {
3402
- return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
3403
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_ui20.Skeleton, { className: "h-6 w-2/3" }),
3404
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_ui20.Skeleton, { className: "h-4 w-full" }),
3405
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_ui20.Skeleton, { className: "h-4 w-3/4" })
3432
+ return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
3433
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_ui20.Skeleton, { className: "h-6 w-2/3" }),
3434
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_ui20.Skeleton, { className: "h-4 w-full" }),
3435
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_ui20.Skeleton, { className: "h-4 w-3/4" })
3406
3436
  ] });
3407
3437
  }
3408
3438
  const selectGenre = (nodeId) => onSelectGenre(nodeId);
3409
- const family = /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3439
+ const family = /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
3410
3440
  import_genre_tree_view5.GenreNodeFamily,
3411
3441
  {
3412
3442
  node: view.node,
@@ -3417,20 +3447,20 @@ function GenreDetailPanel({
3417
3447
  onSelectNode: selectGenre
3418
3448
  }
3419
3449
  );
3420
- const ancestors = /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_genre_tree_view5.GenreNodeAncestors, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
3421
- return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(
3450
+ const ancestors = /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_genre_tree_view5.GenreNodeAncestors, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
3451
+ return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
3422
3452
  import_ui20.DetailPanel,
3423
3453
  {
3424
3454
  side: "left",
3425
3455
  title: view.node.name,
3426
3456
  headerAccent: { background: view.fill, color: import_genre_tree_view5.ACCENT_TEXT_COLOR },
3427
- headerActions: onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3457
+ headerActions: onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
3428
3458
  import_ui20.IconButton,
3429
3459
  {
3430
3460
  "aria-label": expanded ? "Shrink panel" : "Extend panel",
3431
3461
  style: { color: "inherit" },
3432
3462
  onClick: () => onExpandedChange(!expanded),
3433
- children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_lucide_react17.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_lucide_react17.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
3463
+ children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_lucide_react17.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_lucide_react17.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
3434
3464
  }
3435
3465
  ) : void 0,
3436
3466
  onClose,
@@ -3439,10 +3469,10 @@ function GenreDetailPanel({
3439
3469
  onExpandedChange,
3440
3470
  edgeTab,
3441
3471
  children: [
3442
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3472
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
3443
3473
  GenreInfoPanelBody,
3444
3474
  {
3445
- family: /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(import_jsx_runtime45.Fragment, { children: [
3475
+ family: /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(import_jsx_runtime46.Fragment, { children: [
3446
3476
  family,
3447
3477
  ancestors
3448
3478
  ] }),
@@ -3467,13 +3497,13 @@ function GenreDetailPanel({
3467
3497
  var import_genre_tree_view6 = require("@behindthemusictree/genre-tree-view");
3468
3498
 
3469
3499
  // src/genre-tree/track-list-sidebar/TrackListSidebar.tsx
3470
- var import_react18 = require("react");
3500
+ var import_react19 = require("react");
3471
3501
  var import_lucide_react18 = require("lucide-react");
3472
3502
  var import_genre_tree29 = require("@behindthemusictree/app-kit/genre-tree");
3473
3503
  var import_genre_tree30 = require("@behindthemusictree/app-kit/genre-tree");
3474
3504
  var import_track_noun5 = require("@behindthemusictree/app-kit/track-noun");
3475
- var import_jsx_runtime46 = require("react/jsx-runtime");
3476
- var useBrowserLayoutEffect = typeof window === "undefined" ? import_react18.useEffect : import_react18.useLayoutEffect;
3505
+ var import_jsx_runtime47 = require("react/jsx-runtime");
3506
+ var useBrowserLayoutEffect = typeof window === "undefined" ? import_react19.useEffect : import_react19.useLayoutEffect;
3477
3507
  function TrackListSidebar({
3478
3508
  className: className2 = "",
3479
3509
  renderDuration,
@@ -3487,16 +3517,16 @@ function TrackListSidebar({
3487
3517
  }) {
3488
3518
  const { trackList, loadMore, autoAdvanceScrollRequest } = (0, import_genre_tree29.useTrackList)();
3489
3519
  const trackNoun = (0, import_track_noun5.useTrackNoun)();
3490
- const trackListRef = (0, import_react18.useRef)(null);
3491
- const previousOriginRef = (0, import_react18.useRef)(trackList?.origin ?? null);
3492
- const handledAutoAdvanceRequestIdRef = (0, import_react18.useRef)(null);
3493
- const shouldFollowPlaybackRef = (0, import_react18.useRef)(true);
3494
- const programmaticScrollTopRef = (0, import_react18.useRef)(null);
3495
- const userScrollIntentRef = (0, import_react18.useRef)(false);
3496
- const userScrollIntentTimerRef = (0, import_react18.useRef)(null);
3497
- const loadMoreSentinelRef = (0, import_react18.useRef)(null);
3520
+ const trackListRef = (0, import_react19.useRef)(null);
3521
+ const previousOriginRef = (0, import_react19.useRef)(trackList?.origin ?? null);
3522
+ const handledAutoAdvanceRequestIdRef = (0, import_react19.useRef)(null);
3523
+ const shouldFollowPlaybackRef = (0, import_react19.useRef)(true);
3524
+ const programmaticScrollTopRef = (0, import_react19.useRef)(null);
3525
+ const userScrollIntentRef = (0, import_react19.useRef)(false);
3526
+ const userScrollIntentTimerRef = (0, import_react19.useRef)(null);
3527
+ const loadMoreSentinelRef = (0, import_react19.useRef)(null);
3498
3528
  const hasMore = !!trackList && trackList.nextPage !== null;
3499
- (0, import_react18.useEffect)(() => {
3529
+ (0, import_react19.useEffect)(() => {
3500
3530
  const sentinel = loadMoreSentinelRef.current;
3501
3531
  if (!hasMore || !sentinel) return;
3502
3532
  const observer = new IntersectionObserver((entries) => {
@@ -3516,11 +3546,11 @@ function TrackListSidebar({
3516
3546
  programmaticScrollTopRef.current = null;
3517
3547
  if (trackListRef.current) trackListRef.current.scrollTop = 0;
3518
3548
  }, [trackList?.origin]);
3519
- (0, import_react18.useEffect)(() => {
3549
+ (0, import_react19.useEffect)(() => {
3520
3550
  const list = trackListRef.current;
3521
3551
  if (!isCollapsed && list?.scrollTop === 0) shouldFollowPlaybackRef.current = true;
3522
3552
  }, [isCollapsed]);
3523
- (0, import_react18.useEffect)(
3553
+ (0, import_react19.useEffect)(
3524
3554
  () => () => {
3525
3555
  if (userScrollIntentTimerRef.current !== null) clearTimeout(userScrollIntentTimerRef.current);
3526
3556
  },
@@ -3574,25 +3604,25 @@ function TrackListSidebar({
3574
3604
  }
3575
3605
  };
3576
3606
  const positionClasses = layout === "inline" ? "relative w-full flex h-full flex-col" : "fixed right-0 w-[50rem] flex flex-col";
3577
- return trackList ? /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
3607
+ return trackList ? /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(
3578
3608
  "div",
3579
3609
  {
3580
3610
  "data-theme": "dark",
3581
3611
  className: `track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${className2}`,
3582
3612
  style: layout === "fixed" ? { top: "63.5px", maxHeight: "calc(100vh - 63.5px - 79px)" } : void 0,
3583
3613
  children: [
3584
- /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
3585
- /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
3586
- /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "origin flex text-xl", children: [
3587
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
3588
- /* @__PURE__ */ (0, import_jsx_runtime46.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_runtime46.jsx)("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
3614
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
3615
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
3616
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { className: "origin flex text-xl", children: [
3617
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
3618
+ /* @__PURE__ */ (0, import_jsx_runtime47.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_runtime47.jsx)("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
3589
3619
  ] }),
3590
- /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
3620
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
3591
3621
  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 `,
3592
3622
  `${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`
3593
3623
  ] })
3594
3624
  ] }),
3595
- (isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
3625
+ (isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
3596
3626
  "button",
3597
3627
  {
3598
3628
  type: "button",
@@ -3601,11 +3631,11 @@ function TrackListSidebar({
3601
3631
  "aria-expanded": !isCollapsed,
3602
3632
  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"}`,
3603
3633
  onClick: isCollapsed ? onExpand : onMinimize,
3604
- children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_lucide_react18.ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
3634
+ children: /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_lucide_react18.ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
3605
3635
  }
3606
3636
  )
3607
3637
  ] }),
3608
- !isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
3638
+ !isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(
3609
3639
  "ul",
3610
3640
  {
3611
3641
  ref: trackListRef,
@@ -3615,7 +3645,7 @@ function TrackListSidebar({
3615
3645
  onPointerDown: markUserScrollIntent,
3616
3646
  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",
3617
3647
  children: [
3618
- trackList.tracks.map((track, index) => /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
3648
+ trackList.tracks.map((track, index) => /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
3619
3649
  TrackItem,
3620
3650
  {
3621
3651
  track,
@@ -3626,7 +3656,7 @@ function TrackListSidebar({
3626
3656
  renderSongLink
3627
3657
  }
3628
3658
  ) }, track.uuid)),
3629
- hasMore && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
3659
+ hasMore && /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
3630
3660
  ]
3631
3661
  }
3632
3662
  )
@@ -3636,7 +3666,7 @@ function TrackListSidebar({
3636
3666
  }
3637
3667
 
3638
3668
  // src/genre-tree/PlayerPanel.tsx
3639
- var import_react19 = require("react");
3669
+ var import_react20 = require("react");
3640
3670
  var import_tailwind_merge = require("tailwind-merge");
3641
3671
  var import_player13 = require("@behindthemusictree/app-kit/player");
3642
3672
  var import_genre_tree32 = require("@behindthemusictree/app-kit/genre-tree");
@@ -3646,7 +3676,7 @@ var import_ui21 = require("@behindthemusictree/ui");
3646
3676
  var import_lucide_react19 = require("lucide-react");
3647
3677
  var import_genre_tree31 = require("@behindthemusictree/app-kit/genre-tree");
3648
3678
  var import_player12 = require("@behindthemusictree/app-kit/player");
3649
- var import_jsx_runtime47 = require("react/jsx-runtime");
3679
+ var import_jsx_runtime48 = require("react/jsx-runtime");
3650
3680
  function PlayerControls() {
3651
3681
  const {
3652
3682
  duration,
@@ -3665,15 +3695,15 @@ function PlayerControls() {
3665
3695
  const safeDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;
3666
3696
  const safeCurrentTime = Number.isFinite(currentTime) ? Math.max(0, Math.min(currentTime, safeDuration)) : 0;
3667
3697
  const isPlaying = playState === import_player12.PlayStates.PLAYING;
3668
- return /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(
3698
+ return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(
3669
3699
  "section",
3670
3700
  {
3671
3701
  "aria-label": "Player controls",
3672
3702
  className: "player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200",
3673
3703
  children: [
3674
- /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
3675
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("span", { "aria-label": "Elapsed time", children: (0, import_genre_tree31.formatTime)(safeCurrentTime) }),
3676
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
3704
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
3705
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { "aria-label": "Elapsed time", children: (0, import_genre_tree31.formatTime)(safeCurrentTime) }),
3706
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
3677
3707
  "input",
3678
3708
  {
3679
3709
  "aria-label": "Seek",
@@ -3687,11 +3717,11 @@ function PlayerControls() {
3687
3717
  onChange: (event) => seekTo(Number(event.currentTarget.value))
3688
3718
  }
3689
3719
  ),
3690
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("span", { "aria-label": "Duration", children: (0, import_genre_tree31.formatTime)(safeDuration) })
3720
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { "aria-label": "Duration", children: (0, import_genre_tree31.formatTime)(safeDuration) })
3691
3721
  ] }),
3692
- /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
3693
- /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
3694
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
3722
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
3723
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
3724
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
3695
3725
  "button",
3696
3726
  {
3697
3727
  type: "button",
@@ -3700,10 +3730,10 @@ function PlayerControls() {
3700
3730
  disabled: !isReady || !hasPreviousTrack,
3701
3731
  onClick: playPreviousTrack,
3702
3732
  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",
3703
- children: /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_lucide_react19.SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
3733
+ children: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_lucide_react19.SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
3704
3734
  }
3705
3735
  ),
3706
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
3736
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
3707
3737
  "button",
3708
3738
  {
3709
3739
  type: "button",
@@ -3712,10 +3742,10 @@ function PlayerControls() {
3712
3742
  disabled: !isReady,
3713
3743
  onClick: handlePlayPauseAction,
3714
3744
  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",
3715
- children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_lucide_react19.Pause, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_lucide_react19.Play, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
3745
+ children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_lucide_react19.Pause, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_lucide_react19.Play, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
3716
3746
  }
3717
3747
  ),
3718
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
3748
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
3719
3749
  "button",
3720
3750
  {
3721
3751
  type: "button",
@@ -3724,14 +3754,14 @@ function PlayerControls() {
3724
3754
  disabled: !isReady || !hasNextTrack,
3725
3755
  onClick: playNextTrack,
3726
3756
  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",
3727
- children: /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_lucide_react19.SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
3757
+ children: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_lucide_react19.SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
3728
3758
  }
3729
3759
  )
3730
3760
  ] }),
3731
- /* @__PURE__ */ (0, import_jsx_runtime47.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: [
3732
- volume === 0 ? /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_lucide_react19.VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_lucide_react19.Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
3733
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("span", { className: "sr-only", children: "Volume" }),
3734
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
3761
+ /* @__PURE__ */ (0, import_jsx_runtime48.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: [
3762
+ volume === 0 ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_lucide_react19.VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_lucide_react19.Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
3763
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "sr-only", children: "Volume" }),
3764
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
3735
3765
  "input",
3736
3766
  {
3737
3767
  "aria-label": "Volume",
@@ -3753,7 +3783,7 @@ function PlayerControls() {
3753
3783
  }
3754
3784
 
3755
3785
  // src/genre-tree/PlayerPanel.tsx
3756
- var import_jsx_runtime48 = require("react/jsx-runtime");
3786
+ var import_jsx_runtime49 = require("react/jsx-runtime");
3757
3787
  var PANEL_ID = "player-panel";
3758
3788
  var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
3759
3789
  function PlayerPanel({
@@ -3767,12 +3797,12 @@ function PlayerPanel({
3767
3797
  }) {
3768
3798
  const { playerTrackObject, retryPlayback } = (0, import_player13.usePlayer)();
3769
3799
  const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0, import_genre_tree32.useTrackListSidebarVisibility)();
3770
- const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = (0, import_react19.useState)("");
3800
+ const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = (0, import_react20.useState)("");
3771
3801
  const loadedTrackTitle = playerTrackObject?.track.title;
3772
- (0, import_react19.useEffect)(() => {
3802
+ (0, import_react20.useEffect)(() => {
3773
3803
  if (loadedTrackTitle) setLastLoadedTrackTitle(loadedTrackTitle);
3774
3804
  }, [loadedTrackTitle]);
3775
- const [uncontrolledExtended, setUncontrolledExtended] = (0, import_react19.useState)(false);
3805
+ const [uncontrolledExtended, setUncontrolledExtended] = (0, import_react20.useState)(false);
3776
3806
  const isExtendRequested = expanded ?? uncontrolledExtended;
3777
3807
  const setIsExtendRequested = (next) => {
3778
3808
  setUncontrolledExtended(next);
@@ -3793,7 +3823,7 @@ function PlayerPanel({
3793
3823
  onClick: () => setIsExtendRequested(true),
3794
3824
  expanded: false
3795
3825
  } : null;
3796
- return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(
3826
+ return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(
3797
3827
  "div",
3798
3828
  {
3799
3829
  id: PANEL_ID,
@@ -3806,7 +3836,7 @@ function PlayerPanel({
3806
3836
  ),
3807
3837
  style: isExtended ? { width: extendedWidth } : void 0,
3808
3838
  children: [
3809
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
3839
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3810
3840
  import_ui21.PanelEdgeTab,
3811
3841
  {
3812
3842
  side: "right",
@@ -3816,7 +3846,7 @@ function PlayerPanel({
3816
3846
  className: "absolute right-full top-1/2 -translate-y-1/2"
3817
3847
  }
3818
3848
  ),
3819
- /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(
3849
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(
3820
3850
  "div",
3821
3851
  {
3822
3852
  className: (0, import_tailwind_merge.twMerge)(
@@ -3824,20 +3854,20 @@ function PlayerPanel({
3824
3854
  isExtended ? "flex-row" : "flex-col"
3825
3855
  ),
3826
3856
  children: [
3827
- /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
3828
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
3829
- /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "relative w-full shrink-0", children: [
3830
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_player13.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
3831
- playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: [
3857
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
3858
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
3859
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: "relative w-full shrink-0", children: [
3860
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_player13.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
3861
+ playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: [
3832
3862
  playerTrackObject.loadError,
3833
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("button", { type: "button", className: "ml-2 underline", onClick: () => {
3863
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("button", { type: "button", className: "ml-2 underline", onClick: () => {
3834
3864
  void retryPlayback();
3835
3865
  }, children: "Retry" })
3836
3866
  ] })
3837
3867
  ] }),
3838
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(PlayerControls, {})
3868
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(PlayerControls, {})
3839
3869
  ] }),
3840
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
3870
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3841
3871
  TrackListSidebar,
3842
3872
  {
3843
3873
  layout: "inline",
@@ -3864,11 +3894,11 @@ function PlayerPanel({
3864
3894
  var import_uuid = require("uuid");
3865
3895
  var import_ai = require("react-icons/ai");
3866
3896
  var import_genre_tree33 = require("@behindthemusictree/app-kit/genre-tree");
3867
- var import_jsx_runtime49 = require("react/jsx-runtime");
3897
+ var import_jsx_runtime50 = require("react/jsx-runtime");
3868
3898
  function Rating({ rating, handleChange }) {
3869
3899
  const instanceId = (0, import_uuid.v4)();
3870
- return /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
3871
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3900
+ return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
3901
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
3872
3902
  "input",
3873
3903
  {
3874
3904
  id: `star-${instanceId}-${i}`,
@@ -3880,16 +3910,16 @@ function Rating({ rating, handleChange }) {
3880
3910
  onChange: handleChange
3881
3911
  }
3882
3912
  ),
3883
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: "flex", children: [
3884
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3913
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("div", { className: "flex", children: [
3914
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
3885
3915
  import_ai.AiOutlineStar,
3886
3916
  {
3887
3917
  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)]"}`
3888
3918
  }
3889
3919
  ),
3890
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: "flex-grow w-8" }),
3920
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("span", { className: "flex-grow w-8" }),
3891
3921
  " "
3892
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3922
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
3893
3923
  import_ai.AiFillStar,
3894
3924
  {
3895
3925
  className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`