@behindthemusictree/app-kit-ui 30.0.0 → 31.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -30,20 +30,19 @@ __export(genre_tree_exports, {
30
30
  GenreTreeView: () => GenreTreeView,
31
31
  GenreTreeViewSkeleton: () => import_genre_tree_view6.GenreTreeViewSkeleton,
32
32
  GenreTreeWheelSkeleton: () => import_genre_tree_view6.GenreTreeWheelSkeleton,
33
- PanelEdgeTab: () => PanelEdgeTab,
34
33
  PlayerPanel: () => PlayerPanel,
35
34
  Rating: () => Rating,
36
35
  TrackItem: () => TrackItem,
37
36
  TrackListSidebar: () => TrackListSidebar,
38
37
  TrackPositionPlayPause: () => TrackPositionPlayPause,
39
- useGenrePlaylistPlayback: () => useGenrePlaylistPlayback,
40
- useLeftDetailPanelLayout: () => useLeftDetailPanelLayout
38
+ useGenrePlaylistPlayback: () => useGenrePlaylistPlayback
41
39
  });
42
40
  module.exports = __toCommonJS(genre_tree_exports);
43
41
 
44
42
  // src/genre-tree/GenreTreeView.tsx
45
- var import_react17 = require("react");
43
+ var import_react16 = require("react");
46
44
  var import_react_query3 = require("@tanstack/react-query");
45
+ var import_lucide_react11 = require("lucide-react");
47
46
  var import_lucide_react12 = require("lucide-react");
48
47
  var import_ui15 = require("@behindthemusictree/ui");
49
48
  var import_genre_tree_view4 = require("@behindthemusictree/genre-tree-view");
@@ -51,6 +50,32 @@ var import_genre_tree18 = require("@behindthemusictree/app-kit/genre-tree");
51
50
  var import_genre_tree19 = require("@behindthemusictree/app-kit/genre-tree");
52
51
  var import_popup5 = require("@behindthemusictree/app-kit/popup");
53
52
 
53
+ // src/genre-tree/playlist-tree/size-metric.ts
54
+ var hasViewsCount = (genrePlaylists) => genrePlaylists.every((genrePlaylist) => genrePlaylist.viewsCount !== void 0);
55
+ var genrePlaylistSize = (genrePlaylist, metric) => {
56
+ if (metric === "songs") return genrePlaylist.tracksCount;
57
+ if (genrePlaylist.viewsCount === void 0) {
58
+ throw new Error(`Genre playlist ${genrePlaylist.uuid} has no viewsCount to size by views`);
59
+ }
60
+ return genrePlaylist.viewsCount;
61
+ };
62
+
63
+ // src/genre-tree/playlist-tree/toTreeNodes.ts
64
+ function toTreeNodes(genrePlaylists, sizeMetric) {
65
+ return genrePlaylists.map((genrePlaylist) => ({
66
+ id: genrePlaylist.uuid,
67
+ parentId: genrePlaylist.parent?.uuid ?? null,
68
+ name: genrePlaylist.name,
69
+ itemCount: genrePlaylistSize(genrePlaylist, sizeMetric),
70
+ actionable: Boolean(genrePlaylist.criteria),
71
+ detached: !genrePlaylist.criteria,
72
+ side: genrePlaylist.criteria?.side ?? void 0,
73
+ wikidataId: genrePlaylist.criteria?.wikidataId ?? null,
74
+ umbrella: genrePlaylist.isUmbrella,
75
+ scene: genrePlaylist.scene ?? void 0
76
+ }));
77
+ }
78
+
54
79
  // src/genre-tree/playlist-tree/TreePerRoot.tsx
55
80
  var import_react3 = require("react");
56
81
  var import_genre_tree_view = require("@behindthemusictree/genre-tree-view");
@@ -303,7 +328,6 @@ function useGenrePlaylistPlayback({
303
328
  }
304
329
 
305
330
  // src/genre-tree/playlist-tree/TreePerRoot.tsx
306
- var import_track_noun = require("@behindthemusictree/app-kit/track-noun");
307
331
  var import_jsx_runtime5 = require("react/jsx-runtime");
308
332
  function GenrePlaylistTreePerRoot({
309
333
  scope,
@@ -315,24 +339,19 @@ function GenrePlaylistTreePerRoot({
315
339
  additionalActions,
316
340
  onNodeClick,
317
341
  onNodeHover,
318
- renderInfoPanelBody,
319
342
  selectedNodeId,
320
343
  onSelectedNodeChange,
321
- hideInfoPanelClose,
322
- hideInfoPanelTitle,
323
344
  readOnly = false,
324
- showToolbar,
325
345
  wheelZoom,
326
346
  defaultViewport,
327
347
  viewportRestore,
328
348
  onViewportChange,
329
349
  playback,
330
- ...infoPanelCuration
350
+ ...hostPanel
331
351
  }) {
332
352
  const { isPlaying, setIsPlaying } = (0, import_player.usePlayer)();
333
353
  const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists: genrePlaylistTreePerRoot });
334
354
  const { playingNodeId, play } = playback ?? defaultPlayback;
335
- const trackNoun = (0, import_track_noun.useTrackNoun)();
336
355
  const { showPopup } = (0, import_popup.usePopup)();
337
356
  const nodes = (0, import_react3.useMemo)(
338
357
  () => genrePlaylistTreePerRoot.map((genrePlaylist) => ({
@@ -397,7 +416,6 @@ function GenrePlaylistTreePerRoot({
397
416
  {
398
417
  className: className2,
399
418
  nodes,
400
- itemCountLabel: `${(0, import_track_noun.capitalize)(trackNoun.singular)} count`,
401
419
  rootColor: (0, import_genre_tree_view.getGenreTreeColor)(rootUuid),
402
420
  playingNodeId,
403
421
  playState: isPlaying ? "playing" : "paused",
@@ -408,18 +426,13 @@ function GenrePlaylistTreePerRoot({
408
426
  additionalActions,
409
427
  onNodeClick,
410
428
  onNodeHover,
411
- renderInfoPanelBody,
412
429
  selectedNodeId,
413
430
  onSelectedNodeChange,
414
- hideInfoPanelClose,
415
- hideInfoPanelTitle,
416
- showToolbar,
417
431
  wheelZoom,
418
432
  defaultViewport,
419
433
  viewportRestore,
420
434
  onViewportChange,
421
- ...infoPanelCuration,
422
- readOnly
435
+ ...hostPanel
423
436
  }
424
437
  );
425
438
  }
@@ -430,19 +443,6 @@ var import_genre_tree_view2 = require("@behindthemusictree/genre-tree-view");
430
443
  var import_player2 = require("@behindthemusictree/app-kit/player");
431
444
  var import_popup2 = require("@behindthemusictree/app-kit/popup");
432
445
  var import_transport2 = require("@behindthemusictree/app-kit/transport");
433
-
434
- // src/genre-tree/playlist-tree/size-metric.ts
435
- var hasViewsCount = (genrePlaylists) => genrePlaylists.every((genrePlaylist) => genrePlaylist.viewsCount !== void 0);
436
- var genrePlaylistSize = (genrePlaylist, metric) => {
437
- if (metric === "songs") return genrePlaylist.tracksCount;
438
- if (genrePlaylist.viewsCount === void 0) {
439
- throw new Error(`Genre playlist ${genrePlaylist.uuid} has no viewsCount to size by views`);
440
- }
441
- return genrePlaylist.viewsCount;
442
- };
443
-
444
- // src/genre-tree/playlist-tree/TreeWheel.tsx
445
- var import_track_noun2 = require("@behindthemusictree/app-kit/track-noun");
446
446
  var import_jsx_runtime6 = require("react/jsx-runtime");
447
447
  function GenrePlaylistTreeWheel({
448
448
  scope,
@@ -453,14 +453,10 @@ function GenrePlaylistTreeWheel({
453
453
  additionalActions,
454
454
  onNodeClick,
455
455
  onNodeHover,
456
- renderInfoPanelBody,
457
456
  selectedNodeId,
458
457
  onSelectedNodeChange,
459
- hideInfoPanelClose,
460
- hideInfoPanelTitle,
461
458
  readOnly = false,
462
459
  allowWheelRotation,
463
- showToolbar,
464
460
  sizeMetric = "songs",
465
461
  floatingControlsLeading,
466
462
  busy,
@@ -468,12 +464,11 @@ function GenrePlaylistTreeWheel({
468
464
  viewportRestore,
469
465
  onViewportChange,
470
466
  playback,
471
- ...infoPanelCuration
467
+ ...hostPanel
472
468
  }) {
473
469
  const { isPlaying, setIsPlaying } = (0, import_player2.usePlayer)();
474
470
  const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists });
475
471
  const { playingNodeId, play } = playback ?? defaultPlayback;
476
- const trackNoun = (0, import_track_noun2.useTrackNoun)();
477
472
  const { showPopup } = (0, import_popup2.usePopup)();
478
473
  const nodes = (0, import_react4.useMemo)(
479
474
  () => genrePlaylists.map((genrePlaylist) => ({
@@ -537,7 +532,6 @@ function GenrePlaylistTreeWheel({
537
532
  {
538
533
  className: className2,
539
534
  nodes,
540
- itemCountLabel: sizeMetric === "views" ? "Views" : `${(0, import_track_noun2.capitalize)(trackNoun.singular)} count`,
541
535
  playingNodeId,
542
536
  playState: isPlaying ? "playing" : "paused",
543
537
  onPlayPause: handlePlayPause,
@@ -547,20 +541,15 @@ function GenrePlaylistTreeWheel({
547
541
  additionalActions,
548
542
  onNodeClick,
549
543
  onNodeHover,
550
- renderInfoPanelBody,
551
544
  selectedNodeId,
552
545
  onSelectedNodeChange,
553
- hideInfoPanelClose,
554
- hideInfoPanelTitle,
555
546
  allowWheelRotation,
556
- showToolbar,
557
547
  floatingControlsLeading,
558
548
  busy,
559
549
  defaultViewport,
560
550
  viewportRestore,
561
551
  onViewportChange,
562
- ...infoPanelCuration,
563
- readOnly
552
+ ...hostPanel
564
553
  }
565
554
  );
566
555
  }
@@ -569,7 +558,6 @@ function GenrePlaylistTreeWheel({
569
558
  var import_react5 = require("react");
570
559
  var import_genre_tree_view3 = require("@behindthemusictree/genre-tree-view");
571
560
  var import_player3 = require("@behindthemusictree/app-kit/player");
572
- var import_track_noun3 = require("@behindthemusictree/app-kit/track-noun");
573
561
  var import_jsx_runtime7 = require("react/jsx-runtime");
574
562
  function GenrePlaylistTreeWheelRadialPopCore(props) {
575
563
  const {
@@ -581,14 +569,10 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
581
569
  additionalActions,
582
570
  onNodeClick,
583
571
  onNodeHover,
584
- renderInfoPanelBody,
585
572
  selectedNodeId,
586
573
  onSelectedNodeChange,
587
- hideInfoPanelClose,
588
- hideInfoPanelTitle,
589
574
  readOnly = false,
590
575
  allowWheelRotation,
591
- showToolbar,
592
576
  sizeMetric = "songs",
593
577
  floatingControlsLeading,
594
578
  busy,
@@ -599,33 +583,14 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
599
583
  playback
600
584
  } = props;
601
585
  const focus = props;
602
- const infoPanelCuration = {
603
- renderDangerZone: props.renderDangerZone,
604
- expanded: props.expanded,
605
- onExpandedChange: props.onExpandedChange,
606
- collapsed: props.collapsed,
607
- hideExpandButton: props.hideExpandButton,
608
- overlayPanel: props.overlayPanel
586
+ const hostPanel = {
587
+ obscuredInset: props.obscuredInset,
588
+ onNodeStyleResolverChange: props.onNodeStyleResolverChange
609
589
  };
610
590
  const { isPlaying, setIsPlaying } = (0, import_player3.usePlayer)();
611
591
  const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists });
612
592
  const { playingNodeId, play } = playback ?? defaultPlayback;
613
- const trackNoun = (0, import_track_noun3.useTrackNoun)();
614
- const nodes = (0, import_react5.useMemo)(
615
- () => genrePlaylists.map((genrePlaylist) => ({
616
- id: genrePlaylist.uuid,
617
- parentId: genrePlaylist.parent?.uuid ?? null,
618
- name: genrePlaylist.name,
619
- itemCount: genrePlaylistSize(genrePlaylist, sizeMetric),
620
- actionable: Boolean(genrePlaylist.criteria),
621
- detached: !genrePlaylist.criteria,
622
- side: genrePlaylist.criteria?.side ?? void 0,
623
- wikidataId: genrePlaylist.criteria?.wikidataId ?? null,
624
- umbrella: genrePlaylist.isUmbrella,
625
- scene: genrePlaylist.scene ?? void 0
626
- })),
627
- [genrePlaylists, sizeMetric]
628
- );
593
+ const nodes = (0, import_react5.useMemo)(() => toTreeNodes(genrePlaylists, sizeMetric), [genrePlaylists, sizeMetric]);
629
594
  const handlePlayPause = (0, import_react5.useCallback)(
630
595
  (nodeId) => {
631
596
  const genrePlaylist = genrePlaylists.find((g) => g.uuid === nodeId);
@@ -666,7 +631,6 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
666
631
  const treeProps = {
667
632
  className: className2,
668
633
  nodes,
669
- itemCountLabel: sizeMetric === "views" ? "Views" : `${(0, import_track_noun3.capitalize)(trackNoun.singular)} count`,
670
634
  playingNodeId,
671
635
  playState: isPlaying ? "playing" : "paused",
672
636
  onPlayPause: handlePlayPause,
@@ -676,14 +640,9 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
676
640
  additionalActions,
677
641
  onNodeClick,
678
642
  onNodeHover,
679
- renderInfoPanelBody,
680
643
  selectedNodeId,
681
644
  onSelectedNodeChange,
682
- hideInfoPanelClose,
683
- hideInfoPanelTitle,
684
- showToolbar,
685
- ...infoPanelCuration,
686
- readOnly
645
+ ...hostPanel
687
646
  };
688
647
  return outline ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_genre_tree_view3.GenreTreeOutline, { ...treeProps }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
689
648
  import_genre_tree_view3.GenreTreeWheelRadialPopCore,
@@ -803,7 +762,7 @@ function GenreSearch({
803
762
  type: "button",
804
763
  "aria-label": "Previous",
805
764
  title: "Previous",
806
- className: "genre-search-previous absolute left-2 top-1/2 z-10 hidden items-center justify-center p-1.5 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] [@media(pointer:coarse)]:min-w-[var(--layout-touch-target)] text-[var(--color-text-muted)] rounded-full -translate-y-1/2 hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-text)]",
765
+ className: "genre-search-previous absolute left-2 top-1/2 z-10 flex items-center justify-center p-1.5 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] [@media(pointer:coarse)]:min-w-[var(--layout-touch-target)] text-[var(--color-text-muted)] rounded-full -translate-y-1/2 hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-text)]",
807
766
  onClick: onPrevious,
808
767
  children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react3.ArrowLeft, { "aria-hidden": "true", className: "w-4 h-4" })
809
768
  }
@@ -825,7 +784,7 @@ function GenreSearch({
825
784
  onPick: handlePick,
826
785
  status,
827
786
  loading: loading ? "Searching\u2026" : void 0,
828
- inputClassName: "h-[var(--layout-search-h,3rem)] pl-6 pr-20 text-base transition-[padding] duration-200 ease-out motion-reduce:transition-none bg-[var(--color-surface)] border-[var(--color-border)] rounded-full shadow-md",
787
+ inputClassName: `h-[var(--layout-search-h,3rem)] ${onPrevious ? "pl-11" : "pl-6"} pr-20 text-base transition-[padding] duration-200 ease-out motion-reduce:transition-none bg-[var(--color-surface)] border-[var(--color-border)] rounded-full shadow-md`,
829
788
  className: "genre-search-results z-40 rounded-xl shadow-md",
830
789
  listClassName: "max-h-[min(20rem,50dvh)]",
831
790
  optionClassName: "[@media(pointer:coarse)]:min-h-[var(--layout-touch-target)]"
@@ -1289,7 +1248,7 @@ function MusicBrainzRecordingLink({
1289
1248
 
1290
1249
  // src/genre-tree/GenrePlaylistTracks.tsx
1291
1250
  var import_genre_tree8 = require("@behindthemusictree/app-kit/genre-tree");
1292
- var import_track_noun4 = require("@behindthemusictree/app-kit/track-noun");
1251
+ var import_track_noun = require("@behindthemusictree/app-kit/track-noun");
1293
1252
  var import_jsx_runtime18 = require("react/jsx-runtime");
1294
1253
  var CATALOGUE_PAGE_SIZE = 100;
1295
1254
  var SongPageSchema = (0, import_transport4.PaginatedResponseSchema)(import_genre_tree6.SongDetailedSchema).extend({
@@ -1323,7 +1282,7 @@ function GenrePlaylistTracks({
1323
1282
  const { session, sessionRestored } = (0, import_auth.useSession)();
1324
1283
  const { playerTrackObject, playState, handlePlayPauseAction } = (0, import_player4.usePlayer)();
1325
1284
  const { showPopup } = (0, import_popup4.usePopup)();
1326
- const trackNoun = (0, import_track_noun4.useTrackNoun)();
1285
+ const trackNoun = (0, import_track_noun.useTrackNoun)();
1327
1286
  const { uuid } = genrePlaylist;
1328
1287
  const { fetch } = (0, import_transport3.useFetchWrapper)(getBackendBaseUrl);
1329
1288
  const { data, isPending, isError, refetch, hasNextPage, isFetchingNextPage, fetchNextPage } = (0, import_react_query.useInfiniteQuery)({
@@ -1368,7 +1327,7 @@ function GenrePlaylistTracks({
1368
1327
  };
1369
1328
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "gtv-info-panel-children", children: [
1370
1329
  /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "gtv-info-panel-children-title", children: [
1371
- (0, import_track_noun4.capitalize)(trackNoun.plural),
1330
+ (0, import_track_noun.capitalize)(trackNoun.plural),
1372
1331
  total !== void 0 && ` (${total})`
1373
1332
  ] }),
1374
1333
  renderTracksToolbar?.(),
@@ -1439,7 +1398,7 @@ function GenrePlaylistTracks({
1439
1398
  }
1440
1399
 
1441
1400
  // src/genre-tree/GenreDetailExtras.tsx
1442
- var import_track_noun5 = require("@behindthemusictree/app-kit/track-noun");
1401
+ var import_track_noun2 = require("@behindthemusictree/app-kit/track-noun");
1443
1402
  var import_si2 = require("react-icons/si");
1444
1403
  var import_jsx_runtime19 = require("react/jsx-runtime");
1445
1404
  function GenreDetailExtras({
@@ -1457,7 +1416,7 @@ function GenreDetailExtras({
1457
1416
  family,
1458
1417
  ...trackSlots
1459
1418
  }) {
1460
- const trackNoun = (0, import_track_noun5.useTrackNoun)();
1419
+ const trackNoun = (0, import_track_noun2.useTrackNoun)();
1461
1420
  const { data: overview, isPending } = (0, import_genre_tree9.useFetchGenreOverview)(
1462
1421
  genreUuid,
1463
1422
  scope,
@@ -1591,66 +1550,10 @@ function GenreInfoPanelBody({
1591
1550
  ] });
1592
1551
  }
1593
1552
 
1594
- // src/genre-tree/PanelEdgeTab.tsx
1595
- var import_lucide_react8 = require("lucide-react");
1596
- var import_tailwind_merge = require("tailwind-merge");
1597
- var import_jsx_runtime21 = require("react/jsx-runtime");
1598
- var buttonClassName = "flex h-12 w-full items-center justify-center p-0 text-[var(--color-text-muted)] hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-text)] focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--color-primary)]";
1599
- function PanelEdgeTab({ side, edge, center, controls, className: className2 }) {
1600
- if (!edge && !center) return null;
1601
- const isLeft = side === "left";
1602
- const EdgeIcon = isLeft ? import_lucide_react8.ChevronLeft : import_lucide_react8.ChevronRight;
1603
- const CenterIcon = isLeft ? import_lucide_react8.ChevronRight : import_lucide_react8.ChevronLeft;
1604
- const renderButton = (action, icon) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1605
- "button",
1606
- {
1607
- type: "button",
1608
- "aria-label": action.label,
1609
- title: action.label,
1610
- "aria-controls": controls,
1611
- "aria-expanded": action.expanded,
1612
- className: buttonClassName,
1613
- onClick: action.onClick,
1614
- children: icon
1615
- }
1616
- );
1617
- const edgeButton = edge && renderButton(edge, /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(EdgeIcon, { "aria-hidden": "true", className: "h-5 w-5" }));
1618
- const centerButton = center && renderButton(center, /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(CenterIcon, { "aria-hidden": "true", className: "h-5 w-5" }));
1619
- const buttons = isLeft ? [edgeButton, centerButton] : [centerButton, edgeButton];
1620
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
1621
- "div",
1622
- {
1623
- className: (0, import_tailwind_merge.twMerge)(
1624
- "panel-edge-tab flex w-[var(--layout-edge-tab-w)] flex-col overflow-hidden bg-[var(--color-surface)] shadow-[0_1px_2px_rgba(60,64,67,0.3),0_2px_6px_2px_rgba(60,64,67,0.15)] [clip-path:inset(-0.5rem_-0.5rem_-0.5rem_0)]",
1625
- isLeft ? "rounded-r-lg" : "rounded-l-lg [clip-path:inset(-0.5rem_0_-0.5rem_-0.5rem)]",
1626
- className2
1627
- ),
1628
- children: [
1629
- buttons[0],
1630
- buttons[0] && buttons[1] && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { role: "presentation", className: "h-px shrink-0 bg-[var(--color-border)]" }),
1631
- buttons[1]
1632
- ]
1633
- }
1634
- );
1635
- }
1636
-
1637
- // src/genre-tree/useLeftDetailPanelLayout.ts
1638
- var import_react13 = require("react");
1639
- function useLeftDetailPanelLayout(isPanelShown, enabled = true) {
1640
- (0, import_react13.useEffect)(() => {
1641
- if (!enabled || isPanelShown) return void 0;
1642
- const root = document.documentElement;
1643
- root.style.setProperty("--layout-left-w", "0px");
1644
- return () => {
1645
- root.style.removeProperty("--layout-left-w");
1646
- };
1647
- }, [enabled, isPanelShown]);
1648
- }
1649
-
1650
1553
  // src/genre-tree/ChangeParentPopup.tsx
1651
- var import_react14 = require("react");
1554
+ var import_react13 = require("react");
1652
1555
  var import_genre_tree10 = require("@behindthemusictree/app-kit/genre-tree");
1653
- var import_jsx_runtime22 = require("react/jsx-runtime");
1556
+ var import_jsx_runtime21 = require("react/jsx-runtime");
1654
1557
  function ChangeParentPopup({
1655
1558
  genrePlaylist,
1656
1559
  genrePlaylists,
@@ -1660,7 +1563,7 @@ function ChangeParentPopup({
1660
1563
  onParentChanged
1661
1564
  }) {
1662
1565
  const { mutate, isPending, formErrors } = (0, import_genre_tree10.useUpdateGenre)(scope, getBackendBaseUrl);
1663
- const candidates = (0, import_react14.useMemo)(
1566
+ const candidates = (0, import_react13.useMemo)(
1664
1567
  () => (0, import_genre_tree10.getReparentCandidates)(genrePlaylists, genrePlaylist.uuid),
1665
1568
  [genrePlaylists, genrePlaylist.uuid]
1666
1569
  );
@@ -1678,19 +1581,19 @@ function ChangeParentPopup({
1678
1581
  }
1679
1582
  );
1680
1583
  };
1681
- (0, import_react14.useEffect)(() => {
1584
+ (0, import_react13.useEffect)(() => {
1682
1585
  const handleKeyDown = (event) => {
1683
1586
  if (event.key === "Escape") onClose();
1684
1587
  };
1685
1588
  window.addEventListener("keydown", handleKeyDown);
1686
1589
  return () => window.removeEventListener("keydown", handleKeyDown);
1687
1590
  }, [onClose]);
1688
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
1689
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("p", { className: "mb-3 text-sm text-[var(--color-text-muted)]", children: [
1591
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
1592
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("p", { className: "mb-3 text-sm text-[var(--color-text-muted)]", children: [
1690
1593
  "Current parent: ",
1691
1594
  genrePlaylist.parent?.name ?? "none"
1692
1595
  ] }),
1693
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1596
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1694
1597
  GenreSearch,
1695
1598
  {
1696
1599
  genrePlaylists: candidates,
@@ -1698,7 +1601,7 @@ function ChangeParentPopup({
1698
1601
  placeholder: "Search a new parent\u2026"
1699
1602
  }
1700
1603
  ) }),
1701
- formErrors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { role: "alert", className: "mt-3 text-sm text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
1604
+ formErrors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { role: "alert", className: "mt-3 text-sm text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
1702
1605
  ] });
1703
1606
  }
1704
1607
 
@@ -1710,12 +1613,12 @@ var import_genre_tree15 = require("@behindthemusictree/app-kit/genre-tree");
1710
1613
  var import_ui7 = require("@behindthemusictree/ui");
1711
1614
 
1712
1615
  // src/genre-tree/artist/ArtistLinks.tsx
1713
- var import_lucide_react9 = require("lucide-react");
1616
+ var import_lucide_react8 = require("lucide-react");
1714
1617
  var import_si3 = require("react-icons/si");
1715
1618
  var import_ui6 = require("@behindthemusictree/ui");
1716
- var import_jsx_runtime23 = require("react/jsx-runtime");
1619
+ var import_jsx_runtime22 = require("react/jsx-runtime");
1717
1620
  var LINK_KINDS = {
1718
- official: { label: "Official website", Icon: import_lucide_react9.Globe },
1621
+ official: { label: "Official website", Icon: import_lucide_react8.Globe },
1719
1622
  wikipedia: { label: "Wikipedia", Icon: import_si3.SiWikipedia },
1720
1623
  wikidata: { label: "Wikidata", Icon: import_si3.SiWikidata },
1721
1624
  spotify: { label: "Spotify", Icon: import_si3.SiSpotify },
@@ -1723,7 +1626,7 @@ var LINK_KINDS = {
1723
1626
  bandcamp: { label: "Bandcamp", Icon: import_si3.SiBandcamp },
1724
1627
  soundcloud: { label: "SoundCloud", Icon: import_si3.SiSoundcloud },
1725
1628
  discogs: { label: "Discogs", Icon: import_si3.SiDiscogs },
1726
- social: { label: "Social", Icon: import_lucide_react9.Share2 }
1629
+ social: { label: "Social", Icon: import_lucide_react8.Share2 }
1727
1630
  };
1728
1631
  function socialLabel(url) {
1729
1632
  try {
@@ -1750,7 +1653,7 @@ function artistLinks(artist) {
1750
1653
  return links;
1751
1654
  }
1752
1655
  function ArtistLinks({ links }) {
1753
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ui6.Chip, { href: url, icon: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
1656
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ui6.Chip, { href: url, icon: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
1754
1657
  }
1755
1658
 
1756
1659
  // src/genre-tree/artist/formatYears.ts
@@ -1761,7 +1664,7 @@ function formatYears(beginYear, endYear, ended) {
1761
1664
  }
1762
1665
 
1763
1666
  // src/genre-tree/artist/ArtistHeader.tsx
1764
- var import_jsx_runtime24 = require("react/jsx-runtime");
1667
+ var import_jsx_runtime23 = require("react/jsx-runtime");
1765
1668
  var countryNames = new Intl.DisplayNames(["en"], { type: "region" });
1766
1669
  function ArtistHeader({ artist }) {
1767
1670
  const fields = [];
@@ -1771,35 +1674,35 @@ function ArtistHeader({ artist }) {
1771
1674
  const years = formatYears(artist.beginYear, artist.endYear, artist.ended);
1772
1675
  if (years) fields.push({ label: "Active", value: years });
1773
1676
  const links = artistLinks(artist);
1774
- if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ArtistLinks, { links }) });
1677
+ if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ArtistLinks, { links }) });
1775
1678
  if (fields.length === 0) return null;
1776
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ui7.FieldList, { items: fields });
1679
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ui7.FieldList, { items: fields });
1777
1680
  }
1778
1681
 
1779
1682
  // src/genre-tree/artist/ArtistPhoto.tsx
1780
- var import_jsx_runtime25 = require("react/jsx-runtime");
1683
+ var import_jsx_runtime24 = require("react/jsx-runtime");
1781
1684
  function ExternalLink({ href, children }) {
1782
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
1685
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
1783
1686
  }
1784
1687
  function ArtistPhoto({ image, name }) {
1785
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
1786
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
1787
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("figcaption", { className: "text-xs text-[var(--color-text-muted)]", children: [
1688
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
1689
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
1690
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("figcaption", { className: "text-xs text-[var(--color-text-muted)]", children: [
1788
1691
  "Photo: ",
1789
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
1692
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
1790
1693
  ",",
1791
1694
  " ",
1792
- image.licenseUrl ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
1695
+ image.licenseUrl ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
1793
1696
  ] })
1794
1697
  ] });
1795
1698
  }
1796
1699
 
1797
1700
  // src/genre-tree/artist/ArtistMembers.tsx
1798
1701
  var import_ui8 = require("@behindthemusictree/ui");
1799
- var import_jsx_runtime26 = require("react/jsx-runtime");
1702
+ var import_jsx_runtime25 = require("react/jsx-runtime");
1800
1703
  function MembershipList({ title, memberships, renderArtistLink }) {
1801
1704
  if (memberships.length === 0) return null;
1802
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_ui8.PanelGroup, { title, children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("ul", { children: memberships.map((membership, index) => {
1705
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_ui8.PanelGroup, { title, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("ul", { children: memberships.map((membership, index) => {
1803
1706
  const details = [
1804
1707
  formatYears(membership.beginYear, membership.endYear, membership.ended),
1805
1708
  ...membership.roles
@@ -1807,9 +1710,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
1807
1710
  const detailsText = details.filter(Boolean).join(" \xB7 ");
1808
1711
  return (
1809
1712
  // A member can have several periods in the same group.
1810
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
1811
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
1812
- detailsText && /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
1713
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
1714
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
1715
+ detailsText && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
1813
1716
  " \u2014 ",
1814
1717
  detailsText
1815
1718
  ] })
@@ -1820,9 +1723,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
1820
1723
  function ArtistMembers({ title, memberships, renderArtistLink }) {
1821
1724
  if (memberships.length === 0) return null;
1822
1725
  const isCurrent = (membership) => !membership.ended && membership.endYear === null;
1823
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_ui8.PanelSection, { title, children: [
1824
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
1825
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1726
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_ui8.PanelSection, { title, children: [
1727
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
1728
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1826
1729
  MembershipList,
1827
1730
  {
1828
1731
  title: "Past",
@@ -1835,7 +1738,7 @@ function ArtistMembers({ title, memberships, renderArtistLink }) {
1835
1738
 
1836
1739
  // src/genre-tree/artist/ArtistDiscography.tsx
1837
1740
  var import_ui9 = require("@behindthemusictree/ui");
1838
- var import_jsx_runtime27 = require("react/jsx-runtime");
1741
+ var import_jsx_runtime26 = require("react/jsx-runtime");
1839
1742
  var GROUPS = ["Albums", "EPs", "Singles", "Live", "Compilations", "Other"];
1840
1743
  var PRIMARY_GROUPS = { album: "Albums", ep: "EPs", single: "Singles" };
1841
1744
  function discographyGroup({ primaryType, secondaryTypes }) {
@@ -1855,20 +1758,20 @@ function byYear(a, b) {
1855
1758
  function ArtistDiscography({ albums }) {
1856
1759
  if (albums.length === 0) return null;
1857
1760
  const sorted = [...albums].sort(byYear);
1858
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_ui9.PanelSection, { title: "Discography", children: GROUPS.map((group) => {
1761
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_ui9.PanelSection, { title: "Discography", children: GROUPS.map((group) => {
1859
1762
  const groupAlbums = sorted.filter((album) => discographyGroup(album) === group);
1860
1763
  if (groupAlbums.length === 0) return null;
1861
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_ui9.PanelGroup, { title: group, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
1862
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
1863
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
1764
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_ui9.PanelGroup, { title: group, children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
1765
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
1766
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
1864
1767
  ] }, album.uuid)) }) }, group);
1865
1768
  }) });
1866
1769
  }
1867
1770
 
1868
1771
  // src/genre-tree/artist/ArtistSongs.tsx
1869
- var import_react15 = require("react");
1772
+ var import_react14 = require("react");
1870
1773
  var import_react_query2 = require("@tanstack/react-query");
1871
- var import_lucide_react10 = require("lucide-react");
1774
+ var import_lucide_react9 = require("lucide-react");
1872
1775
  var import_ui10 = require("@behindthemusictree/ui");
1873
1776
  var import_player10 = require("@behindthemusictree/app-kit/player");
1874
1777
  var import_genre_tree14 = require("@behindthemusictree/app-kit/genre-tree");
@@ -1882,7 +1785,7 @@ var import_genre_tree11 = require("@behindthemusictree/app-kit/genre-tree");
1882
1785
  var import_fa3 = require("react-icons/fa");
1883
1786
  var import_player7 = require("@behindthemusictree/app-kit/player");
1884
1787
  var import_player8 = require("@behindthemusictree/app-kit/player");
1885
- var import_jsx_runtime28 = require("react/jsx-runtime");
1788
+ var import_jsx_runtime27 = require("react/jsx-runtime");
1886
1789
  function TrackPositionPlayPause({
1887
1790
  position,
1888
1791
  uuid,
@@ -1896,14 +1799,14 @@ function TrackPositionPlayPause({
1896
1799
  const isPlaying = isThisTrack && isConfirmedPlayable && playState === import_player8.PlayStates.PLAYING;
1897
1800
  const isPaused = isThisTrack && isConfirmedPlayable && playState === import_player8.PlayStates.PAUSED;
1898
1801
  const isCurrentTrackActive = isPlaying || isPaused;
1899
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
1802
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
1900
1803
  "div",
1901
1804
  {
1902
1805
  className: "track-position-play-pause relative flex items-center justify-center gap-2 text-lg w-16 cursor-pointer",
1903
1806
  style: { minWidth: "4rem", maxWidth: "4rem" },
1904
1807
  onClick: handlePlayPauseClick,
1905
1808
  children: [
1906
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
1809
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
1907
1810
  "span",
1908
1811
  {
1909
1812
  className: "track-position-play-pause__bars",
@@ -1911,18 +1814,18 @@ function TrackPositionPlayPause({
1911
1814
  role: "img",
1912
1815
  "aria-label": isPlaying ? "Playing" : "Paused",
1913
1816
  children: [
1914
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "track-position-play-pause__bar" }),
1915
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "track-position-play-pause__bar" }),
1916
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "track-position-play-pause__bar" })
1817
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "track-position-play-pause__bar" }),
1818
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "track-position-play-pause__bar" }),
1819
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "track-position-play-pause__bar" })
1917
1820
  ]
1918
1821
  }
1919
1822
  ) : position }),
1920
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1823
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1921
1824
  "div",
1922
1825
  {
1923
1826
  className: `hidden group-hover:flex items-center justify-center ${isCurrentTrackActive ? "" : "absolute inset-0"}`,
1924
1827
  "aria-hidden": "true",
1925
- children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_fa3.FaPause, {}) : /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_fa3.FaPlay, {})
1828
+ children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_fa3.FaPause, {}) : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_fa3.FaPlay, {})
1926
1829
  }
1927
1830
  )
1928
1831
  ]
@@ -1933,7 +1836,7 @@ function TrackPositionPlayPause({
1933
1836
  // src/genre-tree/track-list-sidebar/TrackItem.tsx
1934
1837
  var import_genre_tree12 = require("@behindthemusictree/app-kit/genre-tree");
1935
1838
  var import_genre_tree13 = require("@behindthemusictree/app-kit/genre-tree");
1936
- var import_jsx_runtime29 = require("react/jsx-runtime");
1839
+ var import_jsx_runtime28 = require("react/jsx-runtime");
1937
1840
  function TrackItem({
1938
1841
  track,
1939
1842
  position,
@@ -1957,12 +1860,12 @@ function TrackItem({
1957
1860
  loadTrackForPlayer(track.uuid);
1958
1861
  }
1959
1862
  };
1960
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
1863
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
1961
1864
  "div",
1962
1865
  {
1963
1866
  className: `track-item flex h-14 text-[var(--color-text)] ${isUnplayable ? "opacity-50" : "hover:bg-[var(--color-surface-muted)] group"}`,
1964
1867
  children: [
1965
- isUnplayable ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
1868
+ isUnplayable ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1966
1869
  "div",
1967
1870
  {
1968
1871
  className: "flex items-center justify-center text-lg w-16",
@@ -1970,7 +1873,7 @@ function TrackItem({
1970
1873
  "aria-disabled": "true",
1971
1874
  children: position
1972
1875
  }
1973
- ) : /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
1876
+ ) : /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1974
1877
  TrackPositionPlayPause,
1975
1878
  {
1976
1879
  position,
@@ -1979,12 +1882,12 @@ function TrackItem({
1979
1882
  isSelected: selectedTrack?.uuid === track.uuid
1980
1883
  }
1981
1884
  ),
1982
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
1983
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree12.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
1984
- track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_genre_tree13.ArtistNames, { artists: track.artists, renderArtistLink }) }) : ""
1885
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
1886
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree12.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
1887
+ track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_genre_tree13.ArtistNames, { artists: track.artists, renderArtistLink }) }) : ""
1985
1888
  ] }),
1986
- /* @__PURE__ */ (0, import_jsx_runtime29.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_runtime29.jsx)("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
1987
- renderDuration && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
1889
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "genre-name-container flex items-center justify-start w-[21.6%] min-w-0 shrink-0", children: track.genre ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
1890
+ renderDuration && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1988
1891
  "div",
1989
1892
  {
1990
1893
  className: "duration flex text-base w-16 items-center justify-center",
@@ -1992,22 +1895,22 @@ function TrackItem({
1992
1895
  children: renderDuration(track)
1993
1896
  }
1994
1897
  ),
1995
- renderActions && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
1898
+ renderActions && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
1996
1899
  ]
1997
1900
  }
1998
1901
  );
1999
1902
  }
2000
1903
 
2001
1904
  // src/genre-tree/artist/ArtistSongs.tsx
2002
- var import_jsx_runtime30 = require("react/jsx-runtime");
1905
+ var import_jsx_runtime29 = require("react/jsx-runtime");
2003
1906
  var SongPageSchema2 = (0, import_transport7.PaginatedResponseSchema)(import_genre_tree14.SongDetailedSchema);
2004
1907
  var SORTS = [
2005
1908
  { ordering: "title", label: "Title" },
2006
1909
  { ordering: "-albumYear", label: "Year" }
2007
1910
  ];
2008
1911
  function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLink }) {
2009
- const [page, setPage] = (0, import_react15.useState)(1);
2010
- const [ordering, setOrdering] = (0, import_react15.useState)("title");
1912
+ const [page, setPage] = (0, import_react14.useState)(1);
1913
+ const [ordering, setOrdering] = (0, import_react14.useState)("title");
2011
1914
  const { fetch } = (0, import_transport7.useFetchWrapper)(getBackendBaseUrl);
2012
1915
  const { playNewTrackListFromArtist } = (0, import_genre_tree14.useTrackList)();
2013
1916
  const { playerTrackObject } = (0, import_player10.usePlayer)();
@@ -2032,21 +1935,21 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
2032
1935
  event.stopPropagation();
2033
1936
  void play(song.uuid);
2034
1937
  };
2035
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui10.PanelSection, { title: "Songs", children: isPending ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui10.Skeleton, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_jsx_runtime30.Fragment, { children: [
2036
- /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
2037
- /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
1938
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui10.PanelSection, { title: "Songs", children: isPending ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui10.Skeleton, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(import_jsx_runtime29.Fragment, { children: [
1939
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
1940
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
2038
1941
  "button",
2039
1942
  {
2040
1943
  type: "button",
2041
1944
  onClick: () => void play(),
2042
1945
  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",
2043
1946
  children: [
2044
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_lucide_react10.Play, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
1947
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_lucide_react9.Play, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
2045
1948
  "Play all"
2046
1949
  ]
2047
1950
  }
2048
1951
  ),
2049
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1952
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
2050
1953
  "button",
2051
1954
  {
2052
1955
  type: "button",
@@ -2061,7 +1964,7 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
2061
1964
  sort.ordering
2062
1965
  )) })
2063
1966
  ] }),
2064
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1967
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
2065
1968
  TrackItem,
2066
1969
  {
2067
1970
  track: song,
@@ -2070,12 +1973,12 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
2070
1973
  renderSongLink
2071
1974
  }
2072
1975
  ) }, song.uuid)) }),
2073
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui10.Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
1976
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui10.Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
2074
1977
  ] }) });
2075
1978
  }
2076
1979
 
2077
1980
  // src/genre-tree/artist/useArtistPanel.tsx
2078
- var import_jsx_runtime31 = require("react/jsx-runtime");
1981
+ var import_jsx_runtime30 = require("react/jsx-runtime");
2079
1982
  function useArtistPanel({
2080
1983
  uuid,
2081
1984
  scope,
@@ -2096,26 +1999,26 @@ function useArtistPanel({
2096
1999
  return {
2097
2000
  ...panel,
2098
2001
  title: "Artist",
2099
- content: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2100
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ui11.Skeleton, { className: "h-3 w-16" }),
2101
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ui11.Skeleton, { className: "h-4 w-32" }),
2102
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ui11.Skeleton, { className: "h-3 w-16" }),
2103
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ui11.Skeleton, { className: "h-4 w-24" }),
2104
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ui11.Skeleton, { className: "h-16 mt-2.5" }),
2105
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ui11.Skeleton, { className: "h-16" })
2002
+ content: /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2003
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui11.Skeleton, { className: "h-3 w-16" }),
2004
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui11.Skeleton, { className: "h-4 w-32" }),
2005
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui11.Skeleton, { className: "h-3 w-16" }),
2006
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui11.Skeleton, { className: "h-4 w-24" }),
2007
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui11.Skeleton, { className: "h-16 mt-2.5" }),
2008
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui11.Skeleton, { className: "h-16" })
2106
2009
  ] })
2107
2010
  };
2108
2011
  case "not-found":
2109
2012
  return {
2110
2013
  ...panel,
2111
2014
  title: "Artist not found",
2112
- content: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This artist does not exist." })
2015
+ content: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This artist does not exist." })
2113
2016
  };
2114
2017
  case "error":
2115
2018
  return {
2116
2019
  ...panel,
2117
2020
  title: "Artist",
2118
- content: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
2021
+ content: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
2119
2022
  };
2120
2023
  case "success": {
2121
2024
  const { artist } = result;
@@ -2124,13 +2027,13 @@ function useArtistPanel({
2124
2027
  return {
2125
2028
  ...panel,
2126
2029
  title: artist.name,
2127
- content: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_jsx_runtime31.Fragment, { children: [
2128
- shareUrl && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: shareTarget && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl }) }) }),
2129
- artist.image && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ArtistPhoto, { image: artist.image, name: artist.name }),
2130
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ArtistHeader, { artist }),
2131
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
2132
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
2133
- artist.tracksCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
2030
+ content: /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_jsx_runtime30.Fragment, { children: [
2031
+ shareUrl && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: shareTarget && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl }) }) }),
2032
+ artist.image && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ArtistPhoto, { image: artist.image, name: artist.name }),
2033
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ArtistHeader, { artist }),
2034
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
2035
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
2036
+ artist.tracksCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
2134
2037
  ArtistSongs,
2135
2038
  {
2136
2039
  artist: { uuid: artist.uuid, name: artist.name },
@@ -2140,7 +2043,7 @@ function useArtistPanel({
2140
2043
  }
2141
2044
  )
2142
2045
  ] }),
2143
- extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ArtistDiscography, { albums: artist.albums }) : void 0
2046
+ extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ArtistDiscography, { albums: artist.albums }) : void 0
2144
2047
  };
2145
2048
  }
2146
2049
  }
@@ -2151,45 +2054,45 @@ var import_ui13 = require("@behindthemusictree/ui");
2151
2054
  var import_genre_tree17 = require("@behindthemusictree/app-kit/genre-tree");
2152
2055
 
2153
2056
  // src/genre-tree/song/SongPanelContent.tsx
2154
- var import_react16 = require("react");
2155
- var import_lucide_react11 = require("lucide-react");
2057
+ var import_react15 = require("react");
2058
+ var import_lucide_react10 = require("lucide-react");
2156
2059
  var import_ui12 = require("@behindthemusictree/ui");
2157
2060
  var import_player11 = require("@behindthemusictree/app-kit/player");
2158
2061
  var import_genre_tree16 = require("@behindthemusictree/app-kit/genre-tree");
2159
- var import_jsx_runtime32 = require("react/jsx-runtime");
2062
+ var import_jsx_runtime31 = require("react/jsx-runtime");
2160
2063
  var numberFormat = new Intl.NumberFormat("en");
2161
2064
  var dateFormat = new Intl.DateTimeFormat("en", { dateStyle: "medium" });
2162
2065
  var externalLink = "text-[var(--color-primary)] hover:underline focus-visible:underline";
2163
2066
  function SongHistory({ uuid, getBackendBaseUrl }) {
2164
- const [open, setOpen] = (0, import_react16.useState)(false);
2067
+ const [open, setOpen] = (0, import_react15.useState)(false);
2165
2068
  const history = (0, import_genre_tree16.useSongHistory)(uuid, getBackendBaseUrl, open);
2166
2069
  let body = null;
2167
- if (history.status === "loading") body = /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui12.Skeleton, { className: "h-10", "aria-busy": "true" });
2168
- else if (history.status === "error") body = /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "text-red-600", children: "Could not load the history." });
2070
+ if (history.status === "loading") body = /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ui12.Skeleton, { className: "h-10", "aria-busy": "true" });
2071
+ else if (history.status === "error") body = /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { className: "text-red-600", children: "Could not load the history." });
2169
2072
  else if (history.status === "success")
2170
- body = history.entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("li", { className: "py-1.5 text-sm", children: [
2171
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "font-medium", children: entry.action }),
2172
- entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
2073
+ body = history.entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("li", { className: "py-1.5 text-sm", children: [
2074
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "font-medium", children: entry.action }),
2075
+ entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
2173
2076
  " ",
2174
2077
  "\u2014 ",
2175
2078
  entry.oldValue ?? "none",
2176
2079
  " \u2192 ",
2177
2080
  entry.newValue ?? "none"
2178
2081
  ] }) : null,
2179
- /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("span", { className: "block text-xs text-[var(--color-text-muted)]", children: [
2082
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("span", { className: "block text-xs text-[var(--color-text-muted)]", children: [
2180
2083
  entry.actorPseudo ?? "Anonymous",
2181
2084
  " \xB7 ",
2182
2085
  dateFormat.format(new Date(entry.createdOn))
2183
2086
  ] })
2184
2087
  ] }, entry.uuid)) });
2185
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
2088
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
2186
2089
  "details",
2187
2090
  {
2188
2091
  onToggle: (event) => setOpen(event.currentTarget.open),
2189
2092
  className: "px-6 pb-2.5 mt-2.5 border-t border-[var(--color-border)]",
2190
2093
  children: [
2191
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("summary", { className: "mt-2.5 cursor-pointer text-[0.6875rem] font-semibold uppercase text-[var(--color-text-muted)]", children: "History" }),
2192
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "mt-1.5", children: body })
2094
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("summary", { className: "mt-2.5 cursor-pointer text-[0.6875rem] font-semibold uppercase text-[var(--color-text-muted)]", children: "History" }),
2095
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "mt-1.5", children: body })
2193
2096
  ]
2194
2097
  }
2195
2098
  );
@@ -2215,13 +2118,13 @@ function SongPanelContent({
2215
2118
  const path = genrePath.length > 0 ? genrePath : null;
2216
2119
  fields.push({
2217
2120
  label: "Genre",
2218
- value: path ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("span", { children: [
2219
- index > 0 && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { "aria-hidden": "true", children: "\u203A " }),
2220
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("button", { type: "button", onClick: () => onSelectGenre(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
2121
+ value: path ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("span", { children: [
2122
+ index > 0 && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { "aria-hidden": "true", children: "\u203A " }),
2123
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("button", { type: "button", onClick: () => onSelectGenre(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
2221
2124
  ] }, genrePlaylist.uuid)) }) : song.genre.name
2222
2125
  });
2223
2126
  } else {
2224
- fields.push({ label: "Genre", value: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
2127
+ fields.push({ label: "Genre", value: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
2225
2128
  }
2226
2129
  if (song.firstReleaseYear) fields.push({ label: "First released", value: song.firstReleaseYear });
2227
2130
  const viewCount = song.youtubeVideo?.viewCount;
@@ -2231,11 +2134,11 @@ function SongPanelContent({
2231
2134
  const links = [];
2232
2135
  if (musicBrainzUrl)
2233
2136
  links.push(
2234
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
2137
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
2235
2138
  );
2236
2139
  if (song.youtubeVideo)
2237
2140
  links.push(
2238
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
2141
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
2239
2142
  "a",
2240
2143
  {
2241
2144
  href: `https://www.youtube.com/watch?v=${encodeURIComponent(song.youtubeVideo.id)}`,
@@ -2250,33 +2153,33 @@ function SongPanelContent({
2250
2153
  if (links.length > 0)
2251
2154
  fields.push({
2252
2155
  label: "Links",
2253
- value: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "flex gap-3", children: links })
2156
+ value: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "flex gap-3", children: links })
2254
2157
  });
2255
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_jsx_runtime32.Fragment, { children: [
2256
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
2257
- unplayableReason ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "flex items-center px-2.5 text-sm text-[var(--color-text-muted)]", children: unplayableReason }) : /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
2158
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_jsx_runtime31.Fragment, { children: [
2159
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
2160
+ unplayableReason ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { className: "flex items-center px-2.5 text-sm text-[var(--color-text-muted)]", children: unplayableReason }) : /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
2258
2161
  "button",
2259
2162
  {
2260
2163
  type: "button",
2261
2164
  onClick: () => isCurrent ? handlePlayPauseAction() : playNewTrackListFromTrackUuid(song, scope),
2262
2165
  className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none",
2263
2166
  children: [
2264
- /* @__PURE__ */ (0, import_jsx_runtime32.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_runtime32.jsx)(import_lucide_react11.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react11.Play, { "aria-hidden": "true", className: "w-5 h-5" }) }),
2167
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "flex items-center justify-center w-10 h-10 rounded-full group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)] bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]", children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react10.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react10.Play, { "aria-hidden": "true", className: "w-5 h-5" }) }),
2265
2168
  isPlaying ? "Pause" : "Play"
2266
2169
  ]
2267
2170
  }
2268
2171
  ),
2269
- shareUrl && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
2172
+ shareUrl && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
2270
2173
  ] }) }),
2271
- song.artists && song.artists.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "px-6 pt-2.5 text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_genre_tree16.ArtistNames, { artists: song.artists, renderArtistLink }) }),
2272
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui12.FieldList, { items: fields }),
2273
- song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui12.PanelSection, { title: "Essential in", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
2274
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
2174
+ song.artists && song.artists.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { className: "px-6 pt-2.5 text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_genre_tree16.ArtistNames, { artists: song.artists, renderArtistLink }) }),
2175
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ui12.FieldList, { items: fields }),
2176
+ song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ui12.PanelSection, { title: "Essential in", children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
2177
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
2275
2178
  ] });
2276
2179
  }
2277
2180
 
2278
2181
  // src/genre-tree/song/useSongPanel.tsx
2279
- var import_jsx_runtime33 = require("react/jsx-runtime");
2182
+ var import_jsx_runtime32 = require("react/jsx-runtime");
2280
2183
  function useSongPanel({
2281
2184
  uuid,
2282
2185
  scope,
@@ -2298,24 +2201,24 @@ function useSongPanel({
2298
2201
  return {
2299
2202
  ...panel,
2300
2203
  title: "Song",
2301
- content: /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2302
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ui13.Skeleton, { className: "h-3 w-16" }),
2303
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ui13.Skeleton, { className: "h-4 w-32" }),
2304
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ui13.Skeleton, { className: "h-3 w-16" }),
2305
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ui13.Skeleton, { className: "h-4 w-24" })
2204
+ content: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2205
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui13.Skeleton, { className: "h-3 w-16" }),
2206
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui13.Skeleton, { className: "h-4 w-32" }),
2207
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui13.Skeleton, { className: "h-3 w-16" }),
2208
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui13.Skeleton, { className: "h-4 w-24" })
2306
2209
  ] })
2307
2210
  };
2308
2211
  case "not-found":
2309
2212
  return {
2310
2213
  ...panel,
2311
2214
  title: "Song not found",
2312
- content: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This song does not exist." })
2215
+ content: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This song does not exist." })
2313
2216
  };
2314
2217
  case "error":
2315
2218
  return {
2316
2219
  ...panel,
2317
2220
  title: "Song",
2318
- content: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this song." })
2221
+ content: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this song." })
2319
2222
  };
2320
2223
  case "success": {
2321
2224
  const { song } = result;
@@ -2323,7 +2226,7 @@ function useSongPanel({
2323
2226
  return {
2324
2227
  ...panel,
2325
2228
  title: song.title,
2326
- content: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
2229
+ content: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
2327
2230
  SongPanelContent,
2328
2231
  {
2329
2232
  song,
@@ -2343,12 +2246,12 @@ function useSongPanel({
2343
2246
  // src/genre-tree/SizeMetricToggle.tsx
2344
2247
  var import_md = require("react-icons/md");
2345
2248
  var import_ui14 = require("@behindthemusictree/ui");
2346
- var import_jsx_runtime34 = require("react/jsx-runtime");
2249
+ var import_jsx_runtime33 = require("react/jsx-runtime");
2347
2250
  function SizeMetricToggle({
2348
2251
  value,
2349
2252
  onChange
2350
2253
  }) {
2351
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2254
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
2352
2255
  import_ui14.SegmentedControl,
2353
2256
  {
2354
2257
  variant: "floating",
@@ -2356,26 +2259,19 @@ function SizeMetricToggle({
2356
2259
  value,
2357
2260
  onChange,
2358
2261
  options: [
2359
- { value: "songs", label: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_md.MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
2360
- { value: "views", label: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_md.MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
2262
+ { value: "songs", label: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_md.MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
2263
+ { value: "views", label: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_md.MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
2361
2264
  ]
2362
2265
  }
2363
2266
  );
2364
2267
  }
2365
2268
 
2366
2269
  // src/genre-tree/GenreTreeView.tsx
2367
- var import_jsx_runtime35 = require("react/jsx-runtime");
2368
- var PREVIOUS_BUTTON_CSS = `
2369
- .genre-tree-view .search-overlay .gtv-info-panel-previous { display: none; }
2370
- .genre-tree-view:has(.search-overlay .gtv-info-panel-previous) .genre-search input { padding-left: 2.75rem; }
2371
- .genre-tree-view:has(.search-overlay .gtv-info-panel-previous) .genre-search-previous { display: flex; }
2372
- @keyframes genre-tree-view-previous-in { from { opacity: 0; transform: translate(-0.5rem, -50%); } to { opacity: 1; transform: translate(0, -50%); } }
2373
- @media (prefers-reduced-motion: no-preference) {
2374
- .genre-search-previous { animation: genre-tree-view-previous-in 200ms ease-out; }
2375
- }
2376
- `;
2270
+ var import_jsx_runtime34 = require("react/jsx-runtime");
2377
2271
  var NO_POP_ROOT_TITLE = "This genre tree has no 'Mainstream Pop' root yet";
2378
- var REGIONS_CLASS = "[--gtv-center-left:var(--layout-center-left)] [--gtv-center-right:var(--layout-center-right)] [--gtv-center-top:var(--layout-band-top)] [&_.gtv-info-panel--overlay]:[--gtv-info-panel-header-inset:0px]";
2272
+ var REGIONS_CLASS = "[--gtv-center-left:var(--layout-center-left)] [--gtv-center-right:var(--layout-center-right)] [--gtv-center-top:var(--layout-band-top)]";
2273
+ var noop = () => {
2274
+ };
2379
2275
  function GenreTreeView({
2380
2276
  scope,
2381
2277
  handleGenreCreationAction,
@@ -2429,21 +2325,21 @@ function GenreTreeView({
2429
2325
  placement = "default",
2430
2326
  ...focus
2431
2327
  }) {
2432
- const getShareUrlRef = (0, import_react17.useRef)(getShareUrl);
2328
+ const getShareUrlRef = (0, import_react16.useRef)(getShareUrl);
2433
2329
  getShareUrlRef.current = getShareUrl;
2434
- const [internalArtistUuid, setInternalArtistUuid] = (0, import_react17.useState)(null);
2330
+ const [internalArtistUuid, setInternalArtistUuid] = (0, import_react16.useState)(null);
2435
2331
  const isArtistControlled = controlledArtistUuid !== void 0;
2436
2332
  const artistUuid = isArtistControlled ? controlledArtistUuid : internalArtistUuid;
2437
- const [internalSongUuid, setInternalSongUuid] = (0, import_react17.useState)(null);
2333
+ const [internalSongUuid, setInternalSongUuid] = (0, import_react16.useState)(null);
2438
2334
  const isSongControlled = controlledSongUuid !== void 0;
2439
2335
  const songUuid = isSongControlled ? controlledSongUuid : internalSongUuid;
2440
2336
  const changeSong = (uuid) => {
2441
2337
  if (!isSongControlled) setInternalSongUuid(uuid);
2442
2338
  onSongUuidChange?.(uuid);
2443
2339
  };
2444
- const [internalSizeMetric, setInternalSizeMetric] = (0, import_react17.useState)("songs");
2340
+ const [internalSizeMetric, setInternalSizeMetric] = (0, import_react16.useState)("songs");
2445
2341
  const sizeMetric = controlledSizeMetric ?? internalSizeMetric;
2446
- const deferredSizeMetric = (0, import_react17.useDeferredValue)(sizeMetric);
2342
+ const deferredSizeMetric = (0, import_react16.useDeferredValue)(sizeMetric);
2447
2343
  const changeSizeMetric = (metric) => {
2448
2344
  if (controlledSizeMetric === void 0) setInternalSizeMetric(metric);
2449
2345
  onSizeMetricChange?.(metric);
@@ -2480,7 +2376,7 @@ function GenreTreeView({
2480
2376
  artistUuid
2481
2377
  });
2482
2378
  const restoreRevision = viewportRestore?.revision;
2483
- const [stackedRestoreOwner, setStackedRestoreOwner] = (0, import_react17.useState)({ revision: restoreRevision, rootUuid: selectedGenrePlaylist?.root.uuid });
2379
+ const [stackedRestoreOwner, setStackedRestoreOwner] = (0, import_react16.useState)({ revision: restoreRevision, rootUuid: selectedGenrePlaylist?.root.uuid });
2484
2380
  let restoreRootUuid = stackedRestoreOwner.rootUuid;
2485
2381
  if (stackedRestoreOwner.revision !== restoreRevision) {
2486
2382
  restoreRootUuid = selectedGenrePlaylist?.root.uuid;
@@ -2496,7 +2392,7 @@ function GenreTreeView({
2496
2392
  const canSizeByViews = !isArtistFiltered && hasViewsCount(visibleGenrePlaylists);
2497
2393
  const artistResult = (0, import_genre_tree19.useArtist)(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
2498
2394
  const filterArtistName = artistResult.status === "success" ? artistResult.artist.name : null;
2499
- const filterArtist = (0, import_react17.useMemo)(
2395
+ const filterArtist = (0, import_react16.useMemo)(
2500
2396
  () => artistUuid !== null && filterArtistName !== null ? { uuid: artistUuid, name: filterArtistName } : null,
2501
2397
  [artistUuid, filterArtistName]
2502
2398
  );
@@ -2511,14 +2407,14 @@ function GenreTreeView({
2511
2407
  if (selectedNodeId !== null) clearSelection();
2512
2408
  changeSong(uuid);
2513
2409
  };
2514
- const openSongRef = (0, import_react17.useRef)(openSong);
2410
+ const openSongRef = (0, import_react16.useRef)(openSong);
2515
2411
  openSongRef.current = openSong;
2516
- const resolvedRenderSongLink = (0, import_react17.useCallback)(
2517
- (song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
2412
+ const resolvedRenderSongLink = (0, import_react16.useCallback)(
2413
+ (song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
2518
2414
  [renderSongLink]
2519
2415
  );
2520
- const [expandedSongUuid, setExpandedSongUuid] = (0, import_react17.useState)(null);
2521
- const [expandedArtistUuid, setExpandedArtistUuid] = (0, import_react17.useState)(null);
2416
+ const [expandedSongUuid, setExpandedSongUuid] = (0, import_react16.useState)(null);
2417
+ const [expandedArtistUuid, setExpandedArtistUuid] = (0, import_react16.useState)(null);
2522
2418
  const artistBaseUrl = artistSearchBaseUrl ?? getBackendBaseUrl;
2523
2419
  const songBaseUrl = getBackendBaseUrl;
2524
2420
  const artistPanel = useArtistPanel({
@@ -2526,7 +2422,7 @@ function GenreTreeView({
2526
2422
  scope,
2527
2423
  getBackendBaseUrl: artistBaseUrl,
2528
2424
  getShareUrl: getShareUrlRef.current,
2529
- renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
2425
+ renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
2530
2426
  expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
2531
2427
  onExpandedChange: (on) => {
2532
2428
  if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
@@ -2535,7 +2431,7 @@ function GenreTreeView({
2535
2431
  renderSongLink: resolvedRenderSongLink,
2536
2432
  onClose: () => changeArtist(null)
2537
2433
  });
2538
- const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
2434
+ const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
2539
2435
  const getGenrePath = (genre) => {
2540
2436
  const path = [];
2541
2437
  let current = visibleGenrePlaylists.find((playlist) => playlist.criteria?.uuid === genre.uuid);
@@ -2567,9 +2463,8 @@ function GenreTreeView({
2567
2463
  const builtInSongPanel = overlayPanel || hasSelectedGenreNode ? void 0 : songPanel;
2568
2464
  const builtInArtistPanel = overlayPanel || hasSelectedGenreNode || builtInSongPanel ? void 0 : artistPanel;
2569
2465
  const builtInPanel = builtInSongPanel ?? builtInArtistPanel;
2570
- const activeOverlayPanel = overlayPanel ?? (builtInPanel && { ...builtInPanel, hideTitle: true, hideClose: true });
2571
- const [uncontrolledCollapsed, setUncontrolledCollapsed] = (0, import_react17.useState)(false);
2572
- const [uncontrolledExpanded, setUncontrolledExpanded] = (0, import_react17.useState)(false);
2466
+ const [uncontrolledCollapsed, setUncontrolledCollapsed] = (0, import_react16.useState)(false);
2467
+ const [uncontrolledExpanded, setUncontrolledExpanded] = (0, import_react16.useState)(false);
2573
2468
  const isCollapsed = collapsed ?? uncontrolledCollapsed;
2574
2469
  const isExpanded = expanded ?? uncontrolledExpanded;
2575
2470
  const changeCollapsed = (next) => {
@@ -2580,55 +2475,56 @@ function GenreTreeView({
2580
2475
  setUncontrolledExpanded(next);
2581
2476
  onExpandedChange?.(next);
2582
2477
  };
2583
- (0, import_react17.useEffect)(() => {
2478
+ (0, import_react16.useEffect)(() => {
2584
2479
  setUncontrolledCollapsed(false);
2585
2480
  }, [selectedNodeId, artistUuid, songUuid]);
2586
- const isDetailHidden = isCollapsed || !hasSelectedGenreNode && !activeOverlayPanel;
2587
- useLeftDetailPanelLayout(!isDetailHidden, placement === "regions");
2588
- const detailOverlay = placement === "regions" && activeOverlayPanel?.onExpandedChange ? activeOverlayPanel : void 0;
2589
- const detailExpanded = detailOverlay ? !!detailOverlay.expanded : isExpanded;
2590
- const onDetailExpandedChange = detailOverlay ? detailOverlay.onExpandedChange : changeExpanded;
2591
- const infoPanelCuration = {
2592
- renderDangerZone,
2593
- expanded: placement === "regions" ? isExpanded : expanded,
2594
- onExpandedChange: placement === "regions" ? changeExpanded : onExpandedChange,
2595
- collapsed: placement === "regions" ? isCollapsed : void 0,
2596
- hideExpandButton: placement === "regions" ? true : void 0,
2597
- overlayPanel: detailOverlay ? { ...detailOverlay, collapsed: isCollapsed, hideExpandButton: true } : activeOverlayPanel
2598
- };
2599
- const [isWheelRevealed, setIsWheelRevealed] = (0, import_react17.useState)(false);
2481
+ const [isWheelRevealed, setIsWheelRevealed] = (0, import_react16.useState)(false);
2600
2482
  const usesHandoff = viewMode === "wheel" || viewMode === "pop-core";
2601
2483
  const isTreeReady = !isLoading && (!usesHandoff || isWheelRevealed);
2602
- const rootRef = (0, import_react17.useRef)(null);
2603
- const handlePrevious = () => {
2604
- rootRef.current?.querySelector(".search-overlay .gtv-info-panel-previous")?.click();
2605
- };
2606
- (0, import_react17.useEffect)(() => {
2607
- if (!isTreeReady || viewMode !== "pop-core" || !hasSelectedGenreNode || !selectedGenrePlaylist) return;
2608
- const root = rootRef.current;
2609
- const centerChip = root?.querySelector(".gtv-wheel-center-node .gtv-wheel-chip");
2610
- if (!centerChip || centerChip.getAttribute("aria-label") !== selectedGenrePlaylist.name) return;
2611
- const panelTitle = root?.querySelector(".gtv-info-panel-title");
2612
- if (panelTitle?.textContent?.trim().startsWith(selectedGenrePlaylist.name)) return;
2613
- centerChip.click();
2614
- }, [hasSelectedGenreNode, isTreeReady, selectedGenrePlaylist, viewMode]);
2484
+ const [resolveStyle, setResolveStyle] = (0, import_react16.useState)(null);
2485
+ const handleNodeStyleResolverChange = (0, import_react16.useCallback)(
2486
+ (resolver) => setResolveStyle(() => resolver),
2487
+ []
2488
+ );
2489
+ const panelRef = (0, import_react16.useRef)(null);
2490
+ const obscuredInset = (0, import_ui15.useObscuredInset)(panelRef);
2491
+ const hostPanel = { obscuredInset, onNodeStyleResolverChange: handleNodeStyleResolverChange };
2492
+ const nodeMetric = isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric;
2493
+ const treeNodes = (0, import_react16.useMemo)(() => toTreeNodes(visibleGenrePlaylists, nodeMetric), [visibleGenrePlaylists, nodeMetric]);
2494
+ const relations = (0, import_genre_tree_view4.useGenreNodeRelations)(
2495
+ treeNodes,
2496
+ !overlayPanel && hasSelectedGenreNode ? selectedNodeId : null,
2497
+ resolveStyle
2498
+ );
2499
+ const [selectionHistory, setSelectionHistory] = (0, import_react16.useState)({
2500
+ current: selectedNodeId,
2501
+ previous: null
2502
+ });
2503
+ if (selectionHistory.current !== selectedNodeId) {
2504
+ setSelectionHistory({
2505
+ current: selectedNodeId,
2506
+ previous: selectionHistory.current ?? selectionHistory.previous
2507
+ });
2508
+ }
2509
+ const previousPlaylist = selectionHistory.previous !== null && selectionHistory.previous !== selectedNodeId ? genrePlaylistByNodeId.get(selectionHistory.previous) : void 0;
2510
+ const handlePrevious = previousPlaylist && visibleGenrePlaylists.includes(previousPlaylist) ? () => handleGenreSearchSelect(previousPlaylist) : void 0;
2615
2511
  const { showPopup, hidePopup } = (0, import_popup5.usePopup)();
2616
- const [searchText, setSearchText] = (0, import_react17.useState)("");
2512
+ const [searchText, setSearchText] = (0, import_react16.useState)("");
2617
2513
  const artistSearchResult = (0, import_genre_tree18.useArtistSearch)(artistBaseUrl, artistSearch ? searchText : "");
2618
2514
  const artistGroup = artistSearch ? {
2619
2515
  key: "artists",
2620
2516
  label: "Artists",
2621
2517
  items: artistSearchResult.artists.map((artist) => ({
2622
2518
  key: artist.uuid,
2623
- content: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
2624
- /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "block truncate font-medium", children: [
2519
+ content: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2520
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "block truncate font-medium", children: [
2625
2521
  artist.name,
2626
- artist.disambiguation && /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "font-normal text-[var(--color-neutral-500)]", children: [
2522
+ artist.disambiguation && /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "font-normal text-[var(--color-neutral-500)]", children: [
2627
2523
  " \xB7 ",
2628
2524
  artist.disambiguation
2629
2525
  ] })
2630
2526
  ] }),
2631
- artist.matchedAlias && /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [
2527
+ artist.matchedAlias && /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [
2632
2528
  "also known as ",
2633
2529
  artist.matchedAlias
2634
2530
  ] })
@@ -2647,9 +2543,9 @@ function GenreTreeView({
2647
2543
  label: "Songs",
2648
2544
  items: songSearchResult.songs.map((song) => ({
2649
2545
  key: song.uuid,
2650
- content: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: (0, import_genre_tree18.isSongSearchResultPlayable)(song) ? void 0 : "opacity-60", children: [
2651
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "block truncate font-medium", children: song.title }),
2652
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [song.artists.map((artist) => artist.name).join(", "), song.genre?.name ?? "No genre"].filter(Boolean).join(" \xB7 ") })
2546
+ content: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: (0, import_genre_tree18.isSongSearchResultPlayable)(song) ? void 0 : "opacity-60", children: [
2547
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "block truncate font-medium", children: song.title }),
2548
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [song.artists.map((artist) => artist.name).join(", "), song.genre?.name ?? "No genre"].filter(Boolean).join(" \xB7 ") })
2653
2549
  ] }),
2654
2550
  onSelect: () => {
2655
2551
  openSong(song.uuid);
@@ -2675,36 +2571,49 @@ function GenreTreeView({
2675
2571
  const handleGenreSearchZoomIn = () => {
2676
2572
  if (selectedUuid) focus.onFocusGenreChange?.(selectedUuid, "zoom-in");
2677
2573
  };
2678
- const zoomInRef = (0, import_react17.useRef)(void 0);
2574
+ const zoomInRef = (0, import_react16.useRef)(void 0);
2679
2575
  zoomInRef.current = canZoomIn ? handleGenreSearchZoomIn : void 0;
2680
- const renderInfoPanelBody = (0, import_react17.useCallback)(
2681
- (node, { toolbar, ancestors, family }) => {
2682
- const genrePlaylist = genrePlaylistByNodeId.get(node.id);
2683
- const criteria = genrePlaylist?.criteria;
2684
- if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
2685
- toolbar,
2686
- family
2687
- ] }), aside: ancestors };
2688
- const genreUuid = criteria.uuid;
2689
- const parentPlaylist = visibleGenrePlaylists.find(({ uuid }) => uuid === genrePlaylist.parent?.uuid);
2690
- const canToggleSide = !readOnly && parentPlaylist !== void 0 && parentPlaylist.parent === null;
2691
- const side = criteria.side ?? "core";
2692
- const shareTarget = {
2693
- kind: "genre",
2694
- genrePlaylistUuid: genrePlaylist.uuid,
2695
- name: criteria.name,
2696
- scope,
2697
- artistUuid
2698
- };
2699
- const shareUrl = getShareUrlRef.current?.(shareTarget) ?? void 0;
2700
- const popSibling = canToggleSide && side === "core" ? visibleGenrePlaylists.find(
2701
- (sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist.uuid && sibling.criteria?.side === "pop"
2702
- ) : void 0;
2703
- return {
2704
- primary: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2576
+ const selectGenreFromPanel = (nodeId) => {
2577
+ const genrePlaylist = genrePlaylistByNodeId.get(nodeId);
2578
+ if (genrePlaylist) handleGenreSearchSelect(genrePlaylist);
2579
+ };
2580
+ const renderGenreBody = (relation) => {
2581
+ const { node } = relation;
2582
+ const family = /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2583
+ import_genre_tree_view4.GenreNodeFamily,
2584
+ {
2585
+ node,
2586
+ fill: relation.fill,
2587
+ textColor: relation.textColor,
2588
+ parentNode: relation.parentNode,
2589
+ childNodes: relation.childNodes,
2590
+ onSelectNode: selectGenreFromPanel
2591
+ }
2592
+ );
2593
+ const ancestors = /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_genre_tree_view4.GenreNodeAncestors, { ancestorNodes: relation.ancestorNodes, onSelectNode: selectGenreFromPanel });
2594
+ const genrePlaylist = genrePlaylistByNodeId.get(node.id);
2595
+ const criteria = genrePlaylist?.criteria;
2596
+ if (!genrePlaylist || !criteria) return { primary: family, aside: ancestors };
2597
+ const genreUuid = criteria.uuid;
2598
+ const parentPlaylist = visibleGenrePlaylists.find(({ uuid }) => uuid === genrePlaylist.parent?.uuid);
2599
+ const canToggleSide = !readOnly && parentPlaylist !== void 0 && parentPlaylist.parent === null;
2600
+ const side = criteria.side ?? "core";
2601
+ const shareTarget = {
2602
+ kind: "genre",
2603
+ genrePlaylistUuid: genrePlaylist.uuid,
2604
+ name: criteria.name,
2605
+ scope,
2606
+ artistUuid
2607
+ };
2608
+ const shareUrl = getShareUrlRef.current?.(shareTarget) ?? void 0;
2609
+ const popSibling = canToggleSide && side === "core" ? visibleGenrePlaylists.find(
2610
+ (sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist.uuid && sibling.criteria?.side === "pop"
2611
+ ) : void 0;
2612
+ return {
2613
+ primary: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2614
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2705
2615
  GenreInfoPanelBody,
2706
2616
  {
2707
- toolbar,
2708
2617
  family,
2709
2618
  genreUuid,
2710
2619
  genrePlaylist,
@@ -2720,7 +2629,7 @@ function GenreTreeView({
2720
2629
  onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
2721
2630
  onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
2722
2631
  onChangeParent: readOnly ? void 0 : () => showPopup(
2723
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2632
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2724
2633
  ChangeParentPopup,
2725
2634
  {
2726
2635
  genrePlaylist: { ...genrePlaylist, criteria },
@@ -2751,49 +2660,65 @@ function GenreTreeView({
2751
2660
  }
2752
2661
  }
2753
2662
  ),
2754
- aside: ancestors
2755
- };
2756
- },
2757
- [
2758
- genrePlaylistByNodeId,
2759
- additionalActions,
2760
- readOnly,
2761
- handleGenreCreationAction,
2762
- handleGenreRenameAction,
2763
- showPopup,
2764
- hidePopup,
2765
- onParentChanged,
2766
- visibleGenrePlaylists,
2767
- updateGenre,
2768
- playback,
2769
- filterArtist,
2770
- scope,
2771
- getBackendBaseUrl,
2772
- criteriaOverviewSchema,
2773
- renderGenreDetailExtras,
2774
- renderArtistLink,
2775
- resolvedRenderSongLink,
2776
- renderSummary,
2777
- renderEssentialTracks,
2778
- renderSongLeading,
2779
- renderSongActions,
2780
- renderTracksToolbar
2781
- ]
2782
- );
2663
+ renderDangerZone?.(node)
2664
+ ] }),
2665
+ aside: ancestors
2666
+ };
2667
+ };
2783
2668
  const isRotationControlled = controlledAllowWheelRotation !== void 0;
2784
2669
  const allowWheelRotation = controlledAllowWheelRotation ?? internalAllowWheelRotation;
2785
- const detailTab = placement === "regions" && (hasSelectedGenreNode || detailOverlay) ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2786
- PanelEdgeTab,
2670
+ let activePanel = null;
2671
+ if (overlayPanel) {
2672
+ activePanel = {
2673
+ title: overlayPanel.title,
2674
+ content: overlayPanel.content,
2675
+ aside: overlayPanel.extraContent,
2676
+ expanded: overlayPanel.expanded ?? false,
2677
+ onExpandedChange: overlayPanel.onExpandedChange,
2678
+ onClose: overlayPanel.onClose,
2679
+ compact: false
2680
+ };
2681
+ } else if (relations) {
2682
+ const { primary, aside } = renderGenreBody(relations);
2683
+ activePanel = {
2684
+ title: relations.node.name,
2685
+ content: primary,
2686
+ aside,
2687
+ expanded: isExpanded,
2688
+ onExpandedChange: changeExpanded,
2689
+ onClose: clearSelection,
2690
+ compact: true,
2691
+ headerAccent: { background: relations.fill, color: relations.textColor }
2692
+ };
2693
+ } else if (builtInPanel) {
2694
+ activePanel = {
2695
+ title: builtInPanel.title,
2696
+ content: builtInPanel.content,
2697
+ aside: builtInPanel.extraContent,
2698
+ expanded: builtInPanel.expanded,
2699
+ onExpandedChange: builtInPanel.onExpandedChange,
2700
+ onClose: builtInPanel.onClose,
2701
+ compact: true
2702
+ };
2703
+ }
2704
+ const isPanelShown = isTreeReady && activePanel !== null;
2705
+ const detailTab = placement === "regions" && activePanel?.onExpandedChange ? {
2706
+ edge: activePanel.expanded ? { label: "Shrink panel", onClick: () => activePanel.onExpandedChange?.(false) } : isCollapsed ? null : { label: "Hide panel", onClick: () => changeCollapsed(true) },
2707
+ center: activePanel.expanded ? null : isCollapsed ? { label: "Show panel", onClick: () => changeCollapsed(false) } : activePanel.onExpandedChange ? { label: "Extend panel", onClick: () => activePanel.onExpandedChange?.(true) } : null
2708
+ } : void 0;
2709
+ const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2710
+ import_ui15.IconButton,
2787
2711
  {
2788
- side: "left",
2789
- edge: detailExpanded ? { label: "Shrink panel", onClick: () => onDetailExpandedChange?.(false) } : isCollapsed ? null : { label: "Hide panel", onClick: () => changeCollapsed(true) },
2790
- center: detailExpanded ? null : isCollapsed ? { label: "Show panel", onClick: () => changeCollapsed(false) } : onDetailExpandedChange ? { label: "Extend panel", onClick: () => onDetailExpandedChange(true) } : null
2712
+ "aria-label": activePanel.expanded ? "Shrink panel" : "Extend panel",
2713
+ style: activePanel.headerAccent && { color: "inherit" },
2714
+ onClick: () => activePanel.onExpandedChange?.(!activePanel.expanded),
2715
+ children: activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react12.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react12.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
2791
2716
  }
2792
2717
  ) : null;
2793
2718
  const renderArtistFilter = () => {
2794
2719
  if (isLoading || !filterArtist) return null;
2795
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
2796
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2720
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
2721
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2797
2722
  "button",
2798
2723
  {
2799
2724
  type: "button",
@@ -2805,7 +2730,7 @@ function GenreTreeView({
2805
2730
  children: filterArtist.name
2806
2731
  }
2807
2732
  ),
2808
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2733
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2809
2734
  "button",
2810
2735
  {
2811
2736
  type: "button",
@@ -2817,22 +2742,22 @@ function GenreTreeView({
2817
2742
  )
2818
2743
  ] });
2819
2744
  };
2820
- const actions = /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
2821
- !isLoading && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2745
+ const actions = /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2746
+ !isLoading && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2822
2747
  GenreSearch,
2823
2748
  {
2824
2749
  genrePlaylists: visibleGenrePlaylists,
2825
2750
  onSelect: handleGenreSearchSelect,
2826
- selectedName: selectedGenrePlaylist?.name ?? builtInPanel?.title ?? null,
2751
+ selectedName: selectedGenrePlaylist?.name ?? (typeof builtInPanel?.title === "string" ? builtInPanel.title : null),
2827
2752
  onClear: builtInSongPanel ? () => changeSong(null) : builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
2828
2753
  onPrevious: handlePrevious,
2829
2754
  onQueryChange: artistSearch || songSearch ? setSearchText : void 0,
2830
2755
  extraGroups
2831
2756
  }
2832
2757
  ),
2833
- !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
2834
- /* @__PURE__ */ (0, import_jsx_runtime35.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: [
2835
- !isLoading && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2758
+ !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
2759
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "contents max-md:flex max-md:w-full max-md:items-center max-md:gap-2 max-md:overflow-x-auto max-md:[scrollbar-width:none] max-md:[&>*]:shrink-0", children: [
2760
+ !isLoading && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2836
2761
  import_ui15.SegmentedControl,
2837
2762
  {
2838
2763
  "aria-label": "Tree view mode",
@@ -2846,13 +2771,13 @@ function GenreTreeView({
2846
2771
  ]
2847
2772
  }
2848
2773
  ),
2849
- !isLoading && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2774
+ !isLoading && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2850
2775
  "div",
2851
2776
  {
2852
2777
  className: "flex items-center gap-1",
2853
2778
  role: "group",
2854
2779
  "aria-label": "Tree display options",
2855
- children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2780
+ children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2856
2781
  import_ui15.Button,
2857
2782
  {
2858
2783
  variant: allowWheelRotation ? "default" : "outline",
@@ -2863,52 +2788,44 @@ function GenreTreeView({
2863
2788
  )
2864
2789
  }
2865
2790
  ),
2866
- !isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2791
+ !isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2867
2792
  import_ui15.IconTextButton,
2868
2793
  {
2869
- icon: import_lucide_react12.Plus,
2794
+ icon: import_lucide_react11.Plus,
2870
2795
  text: "Add root",
2871
2796
  onClick: () => handleGenreCreationAction(null)
2872
2797
  }
2873
2798
  )
2874
2799
  ] })
2875
2800
  ] });
2876
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
2801
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
2877
2802
  "div",
2878
2803
  {
2879
- ref: rootRef,
2804
+ "data-left-panel": isPanelShown && !isCollapsed ? void 0 : "hidden",
2880
2805
  className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
2881
2806
  children: [
2882
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("style", { children: PREVIOUS_BUTTON_CSS }),
2883
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2807
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2884
2808
  "div",
2885
2809
  {
2886
2810
  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"}`,
2887
2811
  children: actions
2888
2812
  }
2889
2813
  ),
2890
- !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2814
+ !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2891
2815
  "div",
2892
2816
  {
2893
2817
  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"}`,
2894
2818
  children: renderArtistFilter()
2895
2819
  }
2896
2820
  ),
2897
- detailTab && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2898
- "div",
2899
- {
2900
- className: `absolute top-1/2 z-30 -translate-y-1/2 max-md:hidden transition-[opacity,visibility] duration-150 ease-out ${isTreeReady ? "opacity-100" : "pointer-events-none invisible opacity-0"} ${detailExpanded ? "right-[calc(var(--layout-center-right)-var(--layout-edge-tab-w))]" : isCollapsed ? "left-0" : "left-[calc(var(--layout-gutter)+var(--layout-left-w))]"}`,
2901
- children: detailTab
2902
- }
2903
- ),
2904
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
2905
- !isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
2906
- isLoading ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_genre_tree_view4.GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
2821
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
2822
+ !isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
2823
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_genre_tree_view4.GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
2907
2824
  // The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
2908
2825
  // and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
2909
2826
  // Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
2910
2827
  // The panel's own Zoom in button is hidden: the search bar carries it.
2911
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 [--gtv-info-panel-header-inset:calc(var(--layout-search-h,2.5rem)+var(--layout-panel-inset,0.5rem))] [&_.gtv-info-panel-header]:pb-2 [&_.gtv-info-panel-title--hidden]:h-0 [&_.gtv-toolbar]:hidden", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2828
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2912
2829
  GenrePlaylistTreeWheel,
2913
2830
  {
2914
2831
  scope,
@@ -2917,29 +2834,26 @@ function GenreTreeView({
2917
2834
  handleGenreRenameAction,
2918
2835
  additionalActions,
2919
2836
  onSelectedNodeChange: handleSelectedNodeChange,
2920
- hideInfoPanelClose: true,
2921
- hideInfoPanelTitle: true,
2922
2837
  onNodeHover: handleNodeHover,
2923
- renderInfoPanelBody,
2838
+ ...hostPanel,
2924
2839
  playback,
2925
2840
  selectedNodeId,
2926
2841
  readOnly,
2927
2842
  allowWheelRotation,
2928
2843
  sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
2929
2844
  busy,
2930
- floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "flex items-center gap-2", children: [
2845
+ floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex items-center gap-2", children: [
2931
2846
  floatingControlsLeading,
2932
- canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2847
+ canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2933
2848
  ] }) : void 0,
2934
2849
  defaultViewport,
2935
2850
  viewportRestore,
2936
- onViewportChange,
2937
- ...infoPanelCuration
2851
+ onViewportChange
2938
2852
  }
2939
2853
  ) }) })
2940
2854
  ) : viewMode === "pop-core" ? (
2941
2855
  // Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
2942
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 [--gtv-info-panel-header-inset:calc(var(--layout-search-h,2.5rem)+var(--layout-panel-inset,0.5rem))] [&_.gtv-info-panel-header]:pb-2 [&_.gtv-info-panel-title--hidden]:h-0 [&_.gtv-toolbar]:hidden [&_.gtv-info-panel-zoom-in]:hidden", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2856
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2943
2857
  GenrePlaylistTreeWheelRadialPopCore,
2944
2858
  {
2945
2859
  scope,
@@ -2948,28 +2862,25 @@ function GenreTreeView({
2948
2862
  handleGenreRenameAction,
2949
2863
  additionalActions,
2950
2864
  onSelectedNodeChange: handleSelectedNodeChange,
2951
- hideInfoPanelClose: true,
2952
- hideInfoPanelTitle: true,
2953
2865
  onNodeHover: handleNodeHover,
2954
- renderInfoPanelBody,
2866
+ ...hostPanel,
2955
2867
  playback,
2956
2868
  selectedNodeId,
2957
2869
  readOnly,
2958
2870
  allowWheelRotation,
2959
2871
  sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
2960
2872
  busy,
2961
- floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "flex items-center gap-2", children: [
2873
+ floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex items-center gap-2", children: [
2962
2874
  floatingControlsLeading,
2963
- canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2875
+ canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2964
2876
  ] }) : void 0,
2965
2877
  defaultViewport,
2966
2878
  viewportRestore,
2967
2879
  onViewportChange,
2968
- ...infoPanelCuration,
2969
2880
  ...focus
2970
2881
  }
2971
2882
  ) }) })
2972
- ) : viewMode === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative [--gtv-info-panel-header-inset:calc(var(--layout-search-h,2.5rem)+var(--layout-panel-inset,0.5rem)+0.75rem)] [&_.gtv-toolbar]:hidden", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2883
+ ) : viewMode === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2973
2884
  GenrePlaylistTreeWheelRadialPopCore,
2974
2885
  {
2975
2886
  outline: true,
@@ -2979,22 +2890,19 @@ function GenreTreeView({
2979
2890
  handleGenreRenameAction,
2980
2891
  additionalActions,
2981
2892
  onSelectedNodeChange: handleSelectedNodeChange,
2982
- hideInfoPanelClose: true,
2983
- hideInfoPanelTitle: true,
2984
2893
  onNodeHover: handleNodeHover,
2985
- renderInfoPanelBody,
2894
+ ...hostPanel,
2986
2895
  playback,
2987
2896
  selectedNodeId,
2988
- readOnly,
2989
- ...infoPanelCuration
2897
+ readOnly
2990
2898
  }
2991
- ) }) : /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "tree-container relative flex flex-col gap-4 w-full pt-14 overflow-x-auto overflow-y-auto text-[var(--color-text)] [&_.gtv-toolbar]:hidden", children: Object.entries(groupedGenrePlaylistsByRoot).map(
2992
- ([uuid, genrePlaylistTreePerRoot], rootIndex) => {
2993
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2899
+ ) }) : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "tree-container relative flex flex-col gap-4 w-full pt-14 overflow-x-auto overflow-y-auto text-[var(--color-text)]", children: Object.entries(groupedGenrePlaylistsByRoot).map(
2900
+ ([uuid, genrePlaylistTreePerRoot]) => {
2901
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2994
2902
  "div",
2995
2903
  {
2996
2904
  className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
2997
- children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2905
+ children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2998
2906
  GenrePlaylistTreePerRoot,
2999
2907
  {
3000
2908
  scope,
@@ -3004,26 +2912,45 @@ function GenreTreeView({
3004
2912
  handleGenreRenameAction,
3005
2913
  additionalActions,
3006
2914
  onSelectedNodeChange: handleSelectedNodeChange,
3007
- hideInfoPanelClose: true,
3008
- hideInfoPanelTitle: true,
3009
2915
  onNodeHover: handleNodeHover,
3010
- renderInfoPanelBody,
2916
+ ...hostPanel,
3011
2917
  playback,
3012
2918
  selectedNodeId: uuid === selectedGenrePlaylist?.root.uuid ? selectedNodeId : null,
3013
2919
  readOnly,
3014
2920
  wheelZoom: "modifier",
3015
2921
  defaultViewport: uuid === selectedGenrePlaylist?.root.uuid ? defaultViewport : void 0,
3016
2922
  viewportRestore: uuid === selectedGenrePlaylist?.root.uuid && uuid === restoreRootUuid ? viewportRestore : void 0,
3017
- onViewportChange: uuid === selectedGenrePlaylist?.root.uuid ? onViewportChange : void 0,
3018
- ...infoPanelCuration,
3019
- overlayPanel: rootIndex === 0 ? activeOverlayPanel : void 0
2923
+ onViewportChange: uuid === selectedGenrePlaylist?.root.uuid ? onViewportChange : void 0
3020
2924
  }
3021
2925
  ) })
3022
2926
  },
3023
2927
  uuid
3024
2928
  );
3025
2929
  }
3026
- ) })
2930
+ ) }),
2931
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2932
+ import_ui15.DetailPanel,
2933
+ {
2934
+ ref: panelRef,
2935
+ side: "left",
2936
+ title: activePanel?.title ?? "",
2937
+ hideTitle: activePanel?.compact,
2938
+ hideClose: activePanel?.compact,
2939
+ headerAccent: activePanel?.headerAccent,
2940
+ headerActions: expandButton,
2941
+ onClose: activePanel?.onClose ?? noop,
2942
+ collapsed: isCollapsed || !isTreeReady || !activePanel,
2943
+ expanded: activePanel?.expanded,
2944
+ onExpandedChange: activePanel?.onExpandedChange,
2945
+ aside: activePanel?.aside,
2946
+ edgeTab: detailTab,
2947
+ headerClassName: activePanel?.compact ? "pt-[calc(var(--layout-search-h)+var(--layout-panel-inset)+0.5rem)]" : void 0,
2948
+ children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2949
+ activePanel.content,
2950
+ activePanel.aside
2951
+ ] }) : activePanel?.content
2952
+ }
2953
+ )
3027
2954
  ] }) })
3028
2955
  ]
3029
2956
  }
@@ -3031,12 +2958,12 @@ function GenreTreeView({
3031
2958
  }
3032
2959
 
3033
2960
  // src/genre-tree/GenreDetailPanel.tsx
3034
- var import_react18 = require("react");
2961
+ var import_react17 = require("react");
3035
2962
  var import_genre_tree_view5 = require("@behindthemusictree/genre-tree-view");
3036
2963
  var import_ui16 = require("@behindthemusictree/ui");
2964
+ var import_lucide_react13 = require("lucide-react");
3037
2965
  var import_genre_tree20 = require("@behindthemusictree/app-kit/genre-tree");
3038
- var import_jsx_runtime36 = require("react/jsx-runtime");
3039
- var ITEM_COUNT_LABEL = "Songs";
2966
+ var import_jsx_runtime35 = require("react/jsx-runtime");
3040
2967
  var RELATIVE_ITEM_COUNT = 1;
3041
2968
  function toNode(genre, parentId) {
3042
2969
  return {
@@ -3060,11 +2987,12 @@ function GenreDetailPanel({
3060
2987
  collapsed,
3061
2988
  expanded,
3062
2989
  onExpandedChange,
2990
+ edgeTab,
3063
2991
  extras
3064
2992
  }) {
3065
2993
  const { data: detail, isPending, isError } = (0, import_genre_tree20.useFetchGenreDetail)(genreUuid, scope, getBackendBaseUrl);
3066
2994
  const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: [] });
3067
- const view = (0, import_react18.useMemo)(() => {
2995
+ const view = (0, import_react17.useMemo)(() => {
3068
2996
  if (!detail) return null;
3069
2997
  const rootColor = (0, import_genre_tree_view5.getGenreTreeColor)(detail.root.wikidataId);
3070
2998
  const relative = (genre, parentId) => ({
@@ -3072,7 +3000,7 @@ function GenreDetailPanel({
3072
3000
  fill: (0, import_genre_tree_view5.tintSurface)(rootColor),
3073
3001
  textColor: import_genre_tree_view5.TEXT_COLOR
3074
3002
  });
3075
- const ancestors = [...detail.ascendants].sort((a, b) => b.degree - a.degree);
3003
+ const ancestors2 = [...detail.ascendants].sort((a, b) => b.degree - a.degree);
3076
3004
  return {
3077
3005
  node: {
3078
3006
  id: detail.uuid,
@@ -3084,57 +3012,72 @@ function GenreDetailPanel({
3084
3012
  fill: rootColor,
3085
3013
  parentNode: detail.parent ? relative(detail.parent, null) : null,
3086
3014
  childNodes: detail.children.map((child) => relative(child, detail.uuid)),
3087
- ancestorNodes: ancestors.map(
3088
- ({ ascendant }, index) => relative(ascendant, index === 0 ? null : ancestors[index - 1].ascendant.uuid)
3015
+ ancestorNodes: ancestors2.map(
3016
+ ({ ascendant }, index) => relative(ascendant, index === 0 ? null : ancestors2[index - 1].ascendant.uuid)
3089
3017
  )
3090
3018
  };
3091
3019
  }, [detail]);
3092
3020
  if (isError) {
3093
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
3021
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
3094
3022
  }
3095
3023
  if (isPending || !detail || !view) {
3096
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
3097
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui16.Skeleton, { className: "h-6 w-2/3" }),
3098
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui16.Skeleton, { className: "h-4 w-full" }),
3099
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui16.Skeleton, { className: "h-4 w-3/4" })
3024
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
3025
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui16.Skeleton, { className: "h-6 w-2/3" }),
3026
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui16.Skeleton, { className: "h-4 w-full" }),
3027
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui16.Skeleton, { className: "h-4 w-3/4" })
3100
3028
  ] });
3101
3029
  }
3102
- const renderBody = (node, sections) => ({
3103
- primary: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
3104
- GenreInfoPanelBody,
3105
- {
3106
- toolbar: sections.toolbar,
3107
- family: sections.family,
3108
- genreUuid: detail.uuid,
3109
- genrePlaylist: { ...detail.criteriaPlaylist, tracksCount: detail.tracksCount },
3110
- wikidataId: detail.wikidataId,
3111
- scope,
3112
- getBackendBaseUrl,
3113
- playback,
3114
- band: { node, onZoomIn, shareUrl, shareTarget: shareUrl ? shareTarget : void 0 },
3115
- extras
3116
- }
3117
- ),
3118
- aside: null
3119
- });
3120
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
3121
- import_genre_tree_view5.InfoPanel,
3030
+ const selectGenre = (nodeId) => onSelectGenre(nodeId);
3031
+ const family = /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
3032
+ import_genre_tree_view5.GenreNodeFamily,
3122
3033
  {
3123
3034
  node: view.node,
3124
3035
  fill: view.fill,
3125
3036
  textColor: import_genre_tree_view5.ACCENT_TEXT_COLOR,
3126
3037
  parentNode: view.parentNode,
3127
3038
  childNodes: view.childNodes,
3128
- ancestorNodes: view.ancestorNodes,
3039
+ onSelectNode: selectGenre
3040
+ }
3041
+ );
3042
+ const ancestors = /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_genre_tree_view5.GenreNodeAncestors, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
3043
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
3044
+ import_ui16.DetailPanel,
3045
+ {
3129
3046
  side: "left",
3047
+ title: view.node.name,
3048
+ headerAccent: { background: view.fill, color: import_genre_tree_view5.ACCENT_TEXT_COLOR },
3049
+ headerActions: onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
3050
+ import_ui16.IconButton,
3051
+ {
3052
+ "aria-label": expanded ? "Shrink panel" : "Extend panel",
3053
+ style: { color: "inherit" },
3054
+ onClick: () => onExpandedChange(!expanded),
3055
+ children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react13.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react13.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
3056
+ }
3057
+ ) : void 0,
3130
3058
  onClose,
3131
- onSelectNode: (nodeId) => onSelectGenre(nodeId),
3132
- itemCountLabel: ITEM_COUNT_LABEL,
3133
- renderInfoPanelBody: renderBody,
3059
+ collapsed,
3134
3060
  expanded,
3135
3061
  onExpandedChange,
3136
- collapsed,
3137
- hideExpandButton: onExpandedChange === void 0
3062
+ edgeTab,
3063
+ aside: ancestors,
3064
+ children: [
3065
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
3066
+ GenreInfoPanelBody,
3067
+ {
3068
+ family,
3069
+ genreUuid: detail.uuid,
3070
+ genrePlaylist: { ...detail.criteriaPlaylist, tracksCount: detail.tracksCount },
3071
+ wikidataId: detail.wikidataId,
3072
+ scope,
3073
+ getBackendBaseUrl,
3074
+ playback,
3075
+ band: { node: view.node, onZoomIn, shareUrl, shareTarget: shareUrl ? shareTarget : void 0 },
3076
+ extras
3077
+ }
3078
+ ),
3079
+ !expanded && ancestors
3080
+ ]
3138
3081
  }
3139
3082
  );
3140
3083
  }
@@ -3143,13 +3086,13 @@ function GenreDetailPanel({
3143
3086
  var import_genre_tree_view6 = require("@behindthemusictree/genre-tree-view");
3144
3087
 
3145
3088
  // src/genre-tree/track-list-sidebar/TrackListSidebar.tsx
3146
- var import_react19 = require("react");
3147
- var import_lucide_react13 = require("lucide-react");
3089
+ var import_react18 = require("react");
3090
+ var import_lucide_react14 = require("lucide-react");
3148
3091
  var import_genre_tree21 = require("@behindthemusictree/app-kit/genre-tree");
3149
3092
  var import_genre_tree22 = require("@behindthemusictree/app-kit/genre-tree");
3150
- var import_track_noun6 = require("@behindthemusictree/app-kit/track-noun");
3151
- var import_jsx_runtime37 = require("react/jsx-runtime");
3152
- var useBrowserLayoutEffect = typeof window === "undefined" ? import_react19.useEffect : import_react19.useLayoutEffect;
3093
+ var import_track_noun3 = require("@behindthemusictree/app-kit/track-noun");
3094
+ var import_jsx_runtime36 = require("react/jsx-runtime");
3095
+ var useBrowserLayoutEffect = typeof window === "undefined" ? import_react18.useEffect : import_react18.useLayoutEffect;
3153
3096
  function TrackListSidebar({
3154
3097
  className: className2 = "",
3155
3098
  renderDuration,
@@ -3162,17 +3105,17 @@ function TrackListSidebar({
3162
3105
  renderSongLink
3163
3106
  }) {
3164
3107
  const { trackList, loadMore, autoAdvanceScrollRequest } = (0, import_genre_tree21.useTrackList)();
3165
- const trackNoun = (0, import_track_noun6.useTrackNoun)();
3166
- const trackListRef = (0, import_react19.useRef)(null);
3167
- const previousOriginRef = (0, import_react19.useRef)(trackList?.origin ?? null);
3168
- const handledAutoAdvanceRequestIdRef = (0, import_react19.useRef)(null);
3169
- const shouldFollowPlaybackRef = (0, import_react19.useRef)(true);
3170
- const programmaticScrollTopRef = (0, import_react19.useRef)(null);
3171
- const userScrollIntentRef = (0, import_react19.useRef)(false);
3172
- const userScrollIntentTimerRef = (0, import_react19.useRef)(null);
3173
- const loadMoreSentinelRef = (0, import_react19.useRef)(null);
3108
+ const trackNoun = (0, import_track_noun3.useTrackNoun)();
3109
+ const trackListRef = (0, import_react18.useRef)(null);
3110
+ const previousOriginRef = (0, import_react18.useRef)(trackList?.origin ?? null);
3111
+ const handledAutoAdvanceRequestIdRef = (0, import_react18.useRef)(null);
3112
+ const shouldFollowPlaybackRef = (0, import_react18.useRef)(true);
3113
+ const programmaticScrollTopRef = (0, import_react18.useRef)(null);
3114
+ const userScrollIntentRef = (0, import_react18.useRef)(false);
3115
+ const userScrollIntentTimerRef = (0, import_react18.useRef)(null);
3116
+ const loadMoreSentinelRef = (0, import_react18.useRef)(null);
3174
3117
  const hasMore = !!trackList && trackList.nextPage !== null;
3175
- (0, import_react19.useEffect)(() => {
3118
+ (0, import_react18.useEffect)(() => {
3176
3119
  const sentinel = loadMoreSentinelRef.current;
3177
3120
  if (!hasMore || !sentinel) return;
3178
3121
  const observer = new IntersectionObserver((entries) => {
@@ -3192,11 +3135,11 @@ function TrackListSidebar({
3192
3135
  programmaticScrollTopRef.current = null;
3193
3136
  if (trackListRef.current) trackListRef.current.scrollTop = 0;
3194
3137
  }, [trackList?.origin]);
3195
- (0, import_react19.useEffect)(() => {
3138
+ (0, import_react18.useEffect)(() => {
3196
3139
  const list = trackListRef.current;
3197
3140
  if (!isCollapsed && list?.scrollTop === 0) shouldFollowPlaybackRef.current = true;
3198
3141
  }, [isCollapsed]);
3199
- (0, import_react19.useEffect)(
3142
+ (0, import_react18.useEffect)(
3200
3143
  () => () => {
3201
3144
  if (userScrollIntentTimerRef.current !== null) clearTimeout(userScrollIntentTimerRef.current);
3202
3145
  },
@@ -3250,25 +3193,25 @@ function TrackListSidebar({
3250
3193
  }
3251
3194
  };
3252
3195
  const positionClasses = layout === "inline" ? "relative w-full flex h-full flex-col" : "fixed right-0 w-[50rem] flex flex-col";
3253
- return trackList ? /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
3196
+ return trackList ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
3254
3197
  "div",
3255
3198
  {
3256
3199
  "data-theme": "dark",
3257
3200
  className: `track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${className2}`,
3258
3201
  style: layout === "fixed" ? { top: "63.5px", maxHeight: "calc(100vh - 63.5px - 79px)" } : void 0,
3259
3202
  children: [
3260
- /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
3261
- /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
3262
- /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "origin flex text-xl", children: [
3263
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
3264
- /* @__PURE__ */ (0, import_jsx_runtime37.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_runtime37.jsx)("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
3203
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
3204
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
3205
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "origin flex text-xl", children: [
3206
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
3207
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "name-container flex flex-col justify-center items-center text-[var(--color-text)] font-bold pr-2 max-w-[27.5rem]", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
3265
3208
  ] }),
3266
- /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
3209
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
3267
3210
  trackList.origin.type === import_genre_tree22.TrackListOriginType.GENRE_PLAYLIST ? "Genre \u2022 " : trackList.origin.type === import_genre_tree22.TrackListOriginType.ARTIST ? "Artist \u2022 " : `${trackNoun.singular} playlist \u2022 `,
3268
3211
  `${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`
3269
3212
  ] })
3270
3213
  ] }),
3271
- (isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
3214
+ (isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
3272
3215
  "button",
3273
3216
  {
3274
3217
  type: "button",
@@ -3277,11 +3220,11 @@ function TrackListSidebar({
3277
3220
  "aria-expanded": !isCollapsed,
3278
3221
  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"}`,
3279
3222
  onClick: isCollapsed ? onExpand : onMinimize,
3280
- children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react13.ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
3223
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_lucide_react14.ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
3281
3224
  }
3282
3225
  )
3283
3226
  ] }),
3284
- !isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
3227
+ !isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
3285
3228
  "ul",
3286
3229
  {
3287
3230
  ref: trackListRef,
@@ -3291,7 +3234,7 @@ function TrackListSidebar({
3291
3234
  onPointerDown: markUserScrollIntent,
3292
3235
  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",
3293
3236
  children: [
3294
- trackList.tracks.map((track, index) => /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
3237
+ trackList.tracks.map((track, index) => /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
3295
3238
  TrackItem,
3296
3239
  {
3297
3240
  track,
@@ -3302,7 +3245,7 @@ function TrackListSidebar({
3302
3245
  renderSongLink
3303
3246
  }
3304
3247
  ) }, track.uuid)),
3305
- hasMore && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
3248
+ hasMore && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
3306
3249
  ]
3307
3250
  }
3308
3251
  )
@@ -3312,16 +3255,17 @@ function TrackListSidebar({
3312
3255
  }
3313
3256
 
3314
3257
  // src/genre-tree/PlayerPanel.tsx
3315
- var import_react20 = require("react");
3316
- var import_tailwind_merge2 = require("tailwind-merge");
3258
+ var import_react19 = require("react");
3259
+ var import_tailwind_merge = require("tailwind-merge");
3317
3260
  var import_player13 = require("@behindthemusictree/app-kit/player");
3318
3261
  var import_genre_tree24 = require("@behindthemusictree/app-kit/genre-tree");
3262
+ var import_ui17 = require("@behindthemusictree/ui");
3319
3263
 
3320
3264
  // src/genre-tree/PlayerControls.tsx
3321
- var import_lucide_react14 = require("lucide-react");
3265
+ var import_lucide_react15 = require("lucide-react");
3322
3266
  var import_genre_tree23 = require("@behindthemusictree/app-kit/genre-tree");
3323
3267
  var import_player12 = require("@behindthemusictree/app-kit/player");
3324
- var import_jsx_runtime38 = require("react/jsx-runtime");
3268
+ var import_jsx_runtime37 = require("react/jsx-runtime");
3325
3269
  function PlayerControls() {
3326
3270
  const {
3327
3271
  duration,
@@ -3340,15 +3284,15 @@ function PlayerControls() {
3340
3284
  const safeDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;
3341
3285
  const safeCurrentTime = Number.isFinite(currentTime) ? Math.max(0, Math.min(currentTime, safeDuration)) : 0;
3342
3286
  const isPlaying = playState === import_player12.PlayStates.PLAYING;
3343
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
3287
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
3344
3288
  "section",
3345
3289
  {
3346
3290
  "aria-label": "Player controls",
3347
3291
  className: "player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200",
3348
3292
  children: [
3349
- /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
3350
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { "aria-label": "Elapsed time", children: (0, import_genre_tree23.formatTime)(safeCurrentTime) }),
3351
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
3293
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
3294
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { "aria-label": "Elapsed time", children: (0, import_genre_tree23.formatTime)(safeCurrentTime) }),
3295
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
3352
3296
  "input",
3353
3297
  {
3354
3298
  "aria-label": "Seek",
@@ -3362,11 +3306,11 @@ function PlayerControls() {
3362
3306
  onChange: (event) => seekTo(Number(event.currentTarget.value))
3363
3307
  }
3364
3308
  ),
3365
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { "aria-label": "Duration", children: (0, import_genre_tree23.formatTime)(safeDuration) })
3309
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { "aria-label": "Duration", children: (0, import_genre_tree23.formatTime)(safeDuration) })
3366
3310
  ] }),
3367
- /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
3368
- /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
3369
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
3311
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
3312
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
3313
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
3370
3314
  "button",
3371
3315
  {
3372
3316
  type: "button",
@@ -3375,10 +3319,10 @@ function PlayerControls() {
3375
3319
  disabled: !isReady || !hasPreviousTrack,
3376
3320
  onClick: playPreviousTrack,
3377
3321
  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",
3378
- children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_lucide_react14.SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
3322
+ children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react15.SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
3379
3323
  }
3380
3324
  ),
3381
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
3325
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
3382
3326
  "button",
3383
3327
  {
3384
3328
  type: "button",
@@ -3387,10 +3331,10 @@ function PlayerControls() {
3387
3331
  disabled: !isReady,
3388
3332
  onClick: handlePlayPauseAction,
3389
3333
  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",
3390
- children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_lucide_react14.Pause, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_lucide_react14.Play, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
3334
+ children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react15.Pause, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react15.Play, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
3391
3335
  }
3392
3336
  ),
3393
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
3337
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
3394
3338
  "button",
3395
3339
  {
3396
3340
  type: "button",
@@ -3399,14 +3343,14 @@ function PlayerControls() {
3399
3343
  disabled: !isReady || !hasNextTrack,
3400
3344
  onClick: playNextTrack,
3401
3345
  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",
3402
- children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_lucide_react14.SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
3346
+ children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react15.SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
3403
3347
  }
3404
3348
  )
3405
3349
  ] }),
3406
- /* @__PURE__ */ (0, import_jsx_runtime38.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: [
3407
- volume === 0 ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_lucide_react14.VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_lucide_react14.Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
3408
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "sr-only", children: "Volume" }),
3409
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
3350
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("label", { className: "col-start-3 flex w-[4.2rem] shrink-0 items-center gap-[0.35rem] justify-self-end text-zinc-400 sm:w-[4.9rem]", children: [
3351
+ volume === 0 ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react15.VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react15.Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
3352
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "sr-only", children: "Volume" }),
3353
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
3410
3354
  "input",
3411
3355
  {
3412
3356
  "aria-label": "Volume",
@@ -3428,7 +3372,7 @@ function PlayerControls() {
3428
3372
  }
3429
3373
 
3430
3374
  // src/genre-tree/PlayerPanel.tsx
3431
- var import_jsx_runtime39 = require("react/jsx-runtime");
3375
+ var import_jsx_runtime38 = require("react/jsx-runtime");
3432
3376
  var PANEL_ID = "player-panel";
3433
3377
  var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
3434
3378
  function PlayerPanel({
@@ -3442,12 +3386,12 @@ function PlayerPanel({
3442
3386
  }) {
3443
3387
  const { playerTrackObject } = (0, import_player13.usePlayer)();
3444
3388
  const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0, import_genre_tree24.useTrackListSidebarVisibility)();
3445
- const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = (0, import_react20.useState)("");
3389
+ const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = (0, import_react19.useState)("");
3446
3390
  const loadedTrackTitle = playerTrackObject?.track.title;
3447
- (0, import_react20.useEffect)(() => {
3391
+ (0, import_react19.useEffect)(() => {
3448
3392
  if (loadedTrackTitle) setLastLoadedTrackTitle(loadedTrackTitle);
3449
3393
  }, [loadedTrackTitle]);
3450
- const [uncontrolledExtended, setUncontrolledExtended] = (0, import_react20.useState)(false);
3394
+ const [uncontrolledExtended, setUncontrolledExtended] = (0, import_react19.useState)(false);
3451
3395
  const isExtendRequested = expanded ?? uncontrolledExtended;
3452
3396
  const setIsExtendRequested = (next) => {
3453
3397
  setUncontrolledExtended(next);
@@ -3468,11 +3412,11 @@ function PlayerPanel({
3468
3412
  onClick: () => setIsExtendRequested(true),
3469
3413
  expanded: false
3470
3414
  } : null;
3471
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
3415
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
3472
3416
  "div",
3473
3417
  {
3474
3418
  id: PANEL_ID,
3475
- className: (0, import_tailwind_merge2.twMerge)(
3419
+ className: (0, import_tailwind_merge.twMerge)(
3476
3420
  "fixed right-[var(--layout-gutter)] top-[var(--layout-band-top)] z-40 max-h-[calc(100vh-var(--layout-band-top)-3.5rem)]",
3477
3421
  !isExtended && "w-[var(--layout-right-w)]",
3478
3422
  isTrackListSidebarVisible && "h-[calc(100vh-var(--layout-band-top)-3.5rem)]",
@@ -3481,8 +3425,8 @@ function PlayerPanel({
3481
3425
  ),
3482
3426
  style: isExtended ? { width: extendedWidth } : void 0,
3483
3427
  children: [
3484
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
3485
- PanelEdgeTab,
3428
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
3429
+ import_ui17.PanelEdgeTab,
3486
3430
  {
3487
3431
  side: "right",
3488
3432
  edge: tabEdge,
@@ -3491,30 +3435,30 @@ function PlayerPanel({
3491
3435
  className: "absolute right-full top-1/2 -translate-y-1/2"
3492
3436
  }
3493
3437
  ),
3494
- /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
3438
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
3495
3439
  "div",
3496
3440
  {
3497
- className: (0, import_tailwind_merge2.twMerge)(
3441
+ className: (0, import_tailwind_merge.twMerge)(
3498
3442
  "flex h-full max-h-[inherit] overflow-hidden rounded-[var(--radius-panel,1rem)] bg-zinc-950 text-zinc-100 shadow-[0_1px_2px_rgba(0,0,0,0.5),0_2px_6px_2px_rgba(0,0,0,0.3)]",
3499
3443
  isExtended ? "flex-row" : "flex-col"
3500
3444
  ),
3501
3445
  children: [
3502
- /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
3503
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
3504
- /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "relative w-full shrink-0", children: [
3505
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_player13.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
3506
- playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: playerTrackObject.loadError })
3446
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
3447
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
3448
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "relative w-full shrink-0", children: [
3449
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_player13.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
3450
+ playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: playerTrackObject.loadError })
3507
3451
  ] }),
3508
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(PlayerControls, {})
3452
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(PlayerControls, {})
3509
3453
  ] }),
3510
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
3454
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
3511
3455
  TrackListSidebar,
3512
3456
  {
3513
3457
  layout: "inline",
3514
3458
  isCollapsed: !isTrackListSidebarVisible,
3515
3459
  onExpand: toggleTrackListSidebar,
3516
3460
  onMinimize: hideSongList,
3517
- className: (0, import_tailwind_merge2.twMerge)(
3461
+ className: (0, import_tailwind_merge.twMerge)(
3518
3462
  "min-h-0 min-w-0 flex-1",
3519
3463
  isExtended && "border-l border-zinc-800"
3520
3464
  ),
@@ -3534,11 +3478,11 @@ function PlayerPanel({
3534
3478
  var import_uuid = require("uuid");
3535
3479
  var import_ai = require("react-icons/ai");
3536
3480
  var import_genre_tree25 = require("@behindthemusictree/app-kit/genre-tree");
3537
- var import_jsx_runtime40 = require("react/jsx-runtime");
3481
+ var import_jsx_runtime39 = require("react/jsx-runtime");
3538
3482
  function Rating({ rating, handleChange }) {
3539
3483
  const instanceId = (0, import_uuid.v4)();
3540
- return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
3541
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3484
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
3485
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
3542
3486
  "input",
3543
3487
  {
3544
3488
  id: `star-${instanceId}-${i}`,
@@ -3550,16 +3494,16 @@ function Rating({ rating, handleChange }) {
3550
3494
  onChange: handleChange
3551
3495
  }
3552
3496
  ),
3553
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "flex", children: [
3554
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3497
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex", children: [
3498
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
3555
3499
  import_ai.AiOutlineStar,
3556
3500
  {
3557
3501
  className: `w-7 h-7 absolute top-0 left-0 ${rating === import_genre_tree25.FORM_RATING_NULL_VALUE ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
3558
3502
  }
3559
3503
  ),
3560
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "flex-grow w-8" }),
3504
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "flex-grow w-8" }),
3561
3505
  " "
3562
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3506
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
3563
3507
  import_ai.AiFillStar,
3564
3508
  {
3565
3509
  className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
@@ -3579,13 +3523,11 @@ function Rating({ rating, handleChange }) {
3579
3523
  GenreTreeView,
3580
3524
  GenreTreeViewSkeleton,
3581
3525
  GenreTreeWheelSkeleton,
3582
- PanelEdgeTab,
3583
3526
  PlayerPanel,
3584
3527
  Rating,
3585
3528
  TrackItem,
3586
3529
  TrackListSidebar,
3587
3530
  TrackPositionPlayPause,
3588
- useGenrePlaylistPlayback,
3589
- useLeftDetailPanelLayout
3531
+ useGenrePlaylistPlayback
3590
3532
  });
3591
3533
  //# sourceMappingURL=index.cjs.map